From 03547477ede923b77962e986b085df9c223ce544 Mon Sep 17 00:00:00 2001 From: Andrew de Quincy Date: Fri, 12 Mar 2004 17:30:04 +0000 Subject: Hardening of the code to cope with nastier CAM insert/removal situations --- linux/drivers/media/dvb/dvb-core/dvb_ca.c | 177 ++++++++++++++++++------------ linux/drivers/media/dvb/dvb-core/dvb_ca.h | 51 ++------- 2 files changed, 115 insertions(+), 113 deletions(-) (limited to 'linux/drivers/media/dvb/dvb-core') diff --git a/linux/drivers/media/dvb/dvb-core/dvb_ca.c b/linux/drivers/media/dvb/dvb-core/dvb_ca.c index 68b627500..51c0b4166 100644 --- a/linux/drivers/media/dvb/dvb-core/dvb_ca.c +++ b/linux/drivers/media/dvb/dvb-core/dvb_ca.c @@ -72,39 +72,6 @@ static u8* findstr(u8* haystack, int hlen, u8* needle, int nlen) { } -/* ******************************************************************************** */ -/* Modified version of wait_event_interruptible so we can check for errors */ - -#define __dvb_ca_wait_event_interruptible(wq, condition, ret) \ -do { \ - wait_queue_t __wait; \ - init_waitqueue_entry(&__wait, current); \ - \ - add_wait_queue(&wq, &__wait); \ - for (;;) { \ - set_current_state(TASK_INTERRUPTIBLE); \ - if (ret = condition) \ - break; \ - if (!signal_pending(current)) { \ - schedule(); \ - continue; \ - } \ - ret = -ERESTARTSYS; \ - break; \ - } \ - current->state = TASK_RUNNING; \ - remove_wait_queue(&wq, &__wait); \ -} while (0) - -#define dvb_ca_wait_event_interruptible(wq, condition) \ -({ \ - int __ret = 0; \ - if (!(condition)) \ - __dvb_ca_wait_event_interruptible(wq, condition, __ret); \ - __ret; \ -}) - - /* ******************************************************************************** */ /* Functions for controlling access to slots */ @@ -116,7 +83,7 @@ do { \ * * @return 0 on success, <0 on failure. * */ -int dvb_ca_slot_acquire(struct dvb_ca* ca, int slot) { +static int dvb_ca_slot_acquire(struct dvb_ca* ca, int slot) { int status; if (status = down_interruptible(&ca->slot_info[slot].sem)) return status; @@ -140,7 +107,7 @@ int dvb_ca_slot_acquire(struct dvb_ca* ca, int slot) { * * @return 0 on success, <0 on failure. * */ -int dvb_ca_slot_release(struct dvb_ca* ca, int slot) { +static int dvb_ca_slot_release(struct dvb_ca* ca, int slot) { int status; if (status = down_interruptible(&ca->slot_info[slot].sem)) return status; @@ -161,7 +128,7 @@ int dvb_ca_slot_release(struct dvb_ca* ca, int slot) { * * @return 0 on success, <0 on failure. * */ -int dvb_ca_slot_acquire_exclusive(struct dvb_ca* ca, int slot) { +static int dvb_ca_slot_acquire_exclusive(struct dvb_ca* ca, int slot) { int status; while(1) { @@ -198,7 +165,7 @@ int dvb_ca_slot_acquire_exclusive(struct dvb_ca* ca, int slot) { * * @return 0 on success, <0 on failure. * */ -int dvb_ca_slot_release_exclusive(struct dvb_ca* ca, int slot) { +static int dvb_ca_slot_release_exclusive(struct dvb_ca* ca, int slot) { ca->slot_info[slot].usage_counter--; up(&ca->slot_info[slot].sem); return(0); @@ -768,12 +735,16 @@ exit: */ static int dvb_ca_en50221_slot_init(struct dvb_ca* ca, int slot) { int status; + int old_camchange_count; dprintk ("%s\n", __FUNCTION__); /* acquire the slot exclusively */ if (status = dvb_ca_slot_acquire_exclusive(ca, slot)) return status; + /* get the current number of reported CAM changes */ + old_camchange_count = atomic_get(&ca->slot_info[slot].camchange_count); + /* reset slot */ ca->slot_reset(ca, slot); @@ -789,6 +760,12 @@ static int dvb_ca_en50221_slot_init(struct dvb_ca* ca, int slot) { return status; } + /* check for a CAM change before bothering to initialise it */ + if (atomic_get(&ca->camchange_count) != old_camchange_count) { + dvb_ca_slot_release_exclusive(ca, slot); + return -EIO; + } + /* perform CAM link protocol init */ if (status = dvb_ca_en50221_link_init(ca, slot)) { dvb_ca_slot_release_exclusive(ca, slot); @@ -797,8 +774,8 @@ static int dvb_ca_en50221_slot_init(struct dvb_ca* ca, int slot) { /* OK, slot is now active */ INIT_LIST_HEAD(&ca->slot[info].connections); - init_MUTEX(&ca->slot_info[slot].task_sem); - ca->slot_info[slot].tasks = 0; + atomic_set(&ca->slot_info[slot].write_count, 0); + atomic_set(&ca->slot_info[slot].read_count, 0); ca->slot_info[slot].cam_present = 1; /* success */ @@ -848,10 +825,50 @@ static int dvb_ca_en50221_slot_shutdown(struct dvb_ca* ca, int slot) { return 0; } +/** + * A CAMCHANGE IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_camchange_irq(struct dvb_ca* ca, int slot) { + atomic_inc(&ca->slot_info[slot].camchange_count); +} + +/** + * A READDATA IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_readdata_irq(struct dvb_ca* ca, int slot) { + atomic_inc(&ca->slot_info[slot].read_count); +} + + /* ******************************************************************************** */ /* Monitoring thread */ +/** + * Wake up the DVB CA thread + */ +static void dvb_ca_wakeup(struct dvb_ca* ca) { + ca->wakeup = 1; + wake_up_interruptible(&ca->wait_queue); +} + +/** + * Used by the CA thread to determine if an early wakeup is necessary + */ +static int dvb_ca_should_wakeup(struct dvb_ca* ca) { + if (ca->wakeup) { + ca->wakeup = 0; + return 1; + } + if (ca->exit) return 1; + return 0; +} /** * Kernel thread which monitors CA slots for CAM changes, and performs data transfers. @@ -862,6 +879,7 @@ static int dvb_ca_thread(void* data) { struct dvb_ca_connection* tmp; char name[15]; int slot; + int timeout; unsigned long scan_connections = jiffies + (60*HZ); dprintk ("%s\n", __FUNCTION__); @@ -875,32 +893,49 @@ static int dvb_ca_thread(void* data) { up (&ca->thread_sem); /* is locked when we enter the thread... */ /* sleep for a bit */ - interruptible_sleep_on_timeout(&ca->thread_queue, HZ/10); - if (signal_pending(current)) break; + timeout = wait_event_interruptible_timeout(ca->wait_queue,0 != dvb_ca_should_wakeup(ca), HZ/10); + if ((-ERESTARTSYS == timeout) || (ca->exit)) { + /* got signal or quitting */ + break; + } if (down_interruptible(&ca->thread_sem)) break; - if (ca->exit) break; - /* go through all the slots looking for CAM changes */ + /* go through all the slots processing them */ for(slot=0; slot < ca->slot_count; slot++) { /* poll for CAM changes if there aren't any IRQs for that - * OR: if we've had a CAM_CHANGE task set */ + * OR: if we've have a >1 CAMCHANGE counter in IRQ mode */ if ((!(ca->flags & DVB_CA_FLAG_EN50221_IRQ_CAMCHANGE)) || - (ca->slot_info[slot].tasks & DVB_CA_TASK_CAM_CHANGED)) { - - /* determine the hardware status of the CAM */ - int slot_status = ca->slot_status(ca, slot); - int cam_present = (slot_status & DVB_CA_SLOT_CAM_PRESENT) ? 1: 0; - int cam_changed = (slot_status & DVB_CA_SLOT_CAM_CHANGED) ? 1: 0; - if (!cam_changed) { - cam_changed = (cam_present != ca->slot_info[slot].cam_present); - } + (atomic_get(ca->slot_info[slot].camchange_count))) { + // setup the flag for when we have no CAMCHANGE IRQ, and therefore no camchange_count + int no_irqs_flag = 0; + if (!(ca->flags & DVB_CA_FLAG_EN50221_IRQ_CAMCHANGE)) no_irqs_flag = 1; + + // In IRQ mode, we keep looping until no more CAM changes are reported. + while(atomic_get(ca->slot_info[slot].camchange_count) || no_irqs_flag) { + /* determine the hardware status of the CAM */ + int slot_status = ca->slot_status(ca, slot); + int cam_present_now = (slot_status & DVB_CA_SLOT_CAM_PRESENT) ? 1: 0; + int cam_changed = (slot_status & DVB_CA_SLOT_CAM_CHANGED) ? 1: 0; + if (!cam_changed) { + cam_changed = (cam_present_now != ca->slot_info[slot].cam_present); + } - /* if cam status has changed => update */ - if (cam_changed) { - if (cam_present) dvb_ca_en50221_cam_slot_init(ca, slot); - else dvb_ca_en50221_cam_slot_shutdown(ca, slot); - } + /* if cam status has changed => update */ + if (cam_changed) { + /* clear down an old CI slot */ + if (ca->slot_info[slot].cam_present) dvb_ca_en50221_cam_slot_shutdown(ca, slot); + + /* if a CAM is NOW present, initialise it */ + if (cam_present_now) dvb_ca_en50221_cam_slot_init(ca, slot); + } + + /* if we don't have a CAMCHANGE IRQ, exit the loop after one iteration */ + if (no_irqs_flag) break; + + /* we've handled one change in IRQ mode; decrement the counter */ + atomic_dec(&ca->slot_info[slot].camchange_count); + } } /* do any reading and writing necessary */ @@ -1205,19 +1240,17 @@ int dvb_ca_buf_copy_from_cam(dvb_ca* ca, u8 *data, int len, u8 last_fragment, u8 #define DVB_CA_BUF_COPY_FROM_USER_CONDITION \ ({ \ - int __status; \ - \ - if ((__status = dvb_ca_slot_acquire(ca, slot)) == 0) { \ + if ((err = dvb_ca_slot_acquire(ca, slot)) == 0) { \ cacon = dvb_ca_connection_get(ca, slot, connection_id, 1); \ if (cacon == NULL) { \ - __status = -EIO; \ + err = -EIO; \ dvb_ca_slot_release(ca, slot); \ } else { \ - __status = dvb_ringbuffer_free(&cacon->tx_buffer) >= (count+3) \ - if (!__status) dvb_ca_slot_release(ca, slot); \ + err = dvb_ringbuffer_free(&cacon->tx_buffer) >= (count+3) \ + if (!err) dvb_ca_slot_release(ca, slot); \ } \ } \ - __status; \ + err; \ }) @@ -1238,6 +1271,7 @@ static ssize_t dvb_ca_buf_copy_from_user(dvb_ca* ca, struct file *file, const ch dvb_ca_connection* cacon; int status; int write_pos; + int err; /* Packets in the buffer have a 3 byte header as follows: 0: MSB of total packet size. @@ -1273,7 +1307,7 @@ static ssize_t dvb_ca_buf_copy_from_user(dvb_ca* ca, struct file *file, const ch /* wait for some space */ dvb_ca_slot_release(ca, slot); - if ((status = dvb_ca_wait_event_interruptible(ca->write_queue, DVB_CA_BUF_COPY_FROM_USER_CONDITION)) < 0) { + if ((status = wait_event_interruptible(ca->write_queue, DVB_CA_BUF_COPY_FROM_USER_CONDITION)) < 0) { /* slot is already released if the above failed */ return status; } @@ -1310,22 +1344,20 @@ static ssize_t dvb_ca_buf_copy_from_user(dvb_ca* ca, struct file *file, const ch // at the one after, to give them equal priority. #define DVB_CA_BUF_COPY_TO_USER_CONDITION \ ({ \ - int __status; \ - \ for(slot=0; slot < ca->slot_count; slot++) { \ \ if (dvb_ca_slot_acquire(ca, slot) == 0) { \ \ /* go through each connection id to see if there is data available */ \ list_for_each_entry(cacon, &ca->slot_info[slot].connections, next) { \ - __status = dvb_ringbuffer_avail(&cacon->rx_buffer) >= 3; \ - if (__status) goto completed; \ + err = dvb_ringbuffer_avail(&cacon->rx_buffer) >= 3; \ + if (err) goto completed; \ } \ dvb_ca_slot_release(ca, slot); \ } \ } \ completed: \ - __status; \ + err; \ }) @@ -1348,6 +1380,7 @@ static ssize_t dvb_ca_buf_copy_to_user(dvb_ca* ca, struct file *file, char *buf, int packet_size; int split; int status; + int err; /* Packets in the buffer have a 3 byte header as follows: 0: MSB of total packet size. @@ -1367,7 +1400,7 @@ static ssize_t dvb_ca_buf_copy_to_user(dvb_ca* ca, struct file *file, char *buf, return -EWOULDBLOCK; /* wait for some data */ - if ((status = dvb_ca_wait_event_interruptible(ca->read_queue, DVB_CA_BUF_COPY_TO_USER_CONDITION)) < 0) { + if ((status = wait_event_interruptible(ca->read_queue, DVB_CA_BUF_COPY_TO_USER_CONDITION)) < 0) { /* check for errors. No need to relase slot, as we won't have one */ return status; } diff --git a/linux/drivers/media/dvb/dvb-core/dvb_ca.h b/linux/drivers/media/dvb/dvb-core/dvb_ca.h index 6b1d664d1..b3e3ce2d0 100644 --- a/linux/drivers/media/dvb/dvb-core/dvb_ca.h +++ b/linux/drivers/media/dvb/dvb-core/dvb_ca.h @@ -34,10 +34,6 @@ #define DVB_CA_FLAG_EN50221_IRQ_CAMCHANGE 2 #define DVB_CA_FLAG_EN50221_IRQ_READ 4 -#define DVB_CA_TASK_CAM_CHANGED 1 -#define DVB_CA_TASK_CAM_READ 2 -#define DVB_CA_TASK_CAM_WRITE 4 - /* A connection id to a CAM */ @@ -77,9 +73,9 @@ struct dvb_ca_slot { int usage_counter; /* current usage counter of this slot */ - atomic_t camchange; - atomic_t write; - atomic_t read; + atomic_t camchange_count; + atomic_t write_count; + atomic_t read_count; list_head connections; /* list of dvb_ca_connection, one per connection_id */ }; @@ -145,50 +141,23 @@ struct dvb_ca { /* ******************************************************************************** */ -/* Functions for controlling access to slots */ - -/** - * Safely increment the usage counter for a CA slot. - * - * @param ca CA instance. - * @param slot Slot concerned. - * - * @return 0 on success, <0 on failure. - * */ -extern int dvb_ca_slot_acquire(struct dvb_ca* ca, int slot); - +/* Functions for reporting EN50221 IRQ events */ /** - * Safely decrement the usage counter for a CA slot. + * A CAMCHANGE IRQ has occurred. * * @param ca CA instance. * @param slot Slot concerned. - * - * @return 0 on success, <0 on failure. - * */ -extern int dvb_ca_slot_release(struct dvb_ca* ca, int slot); - -/** - * Acquire a slot exclusively. The slot semaphore will be left locked on successful - * exit of this function. - * - * @param ca CA instance. - * @param slot Slot concerned. - * - * @return 0 on success, <0 on failure. - * */ -extern int dvb_ca_slot_acquire_exclusive(struct dvb_ca* ca, int slot); - + */ +void dvb_ca_en50221_camchange_irq(struct dvb_ca* ca, int slot); /** - * Release an exclusively owned slot. The slot semaphore will be unlocked by this function. + * A READDATA IRQ has occurred. * * @param ca CA instance. * @param slot Slot concerned. - * - * @return 0 on success, <0 on failure. - * */ -extern int dvb_ca_slot_release_exclusive(struct dvb_ca* ca, int slot); + */ +void dvb_ca_en50221_readdata_irq(struct dvb_ca* ca, int slot); -- cgit v1.2.3