Skip to content

Commit

Permalink
V4L/DVB (9054): implement proper locking in the dvb ca en50221 driver
Browse files Browse the repository at this point in the history
Concurrent access to a single DVB CA 50221 interface slot is generally
discouraged. The underlying drivers (budget-av, budget-ci) do not implement
proper locking and thus two transactions could (and do) interfere with on
another.

This fixes the following problems seen by others and myself:

 - sudden i/o errors when writing to the ci device which usually would
   result in an undefined state of the hw and require a software restart

 - errors about the CAM trying to send a buffer larger than the agreed size
   usually also resulting in an undefined state of the hw

Due the to design of the DVB CA 50221 driver, implementing the locks in the
underlying drivers would not be enough and still leave some race conditions,
even though they were harder to trigger.

Signed-off-by: Matthias Dahl <devel@mortal-soul.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
  • Loading branch information
Matthias Dahl authored and Mauro Carvalho Chehab committed Jan 29, 2009
1 parent 0c37dd7 commit d7e4384
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 5 deletions.
24 changes: 21 additions & 3 deletions drivers/media/dvb/dvb-core/dvb_ca_en50221.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ struct dvb_ca_slot {
/* current state of the CAM */
int slot_state;

/* mutex used for serializing access to one CI slot */
struct mutex slot_lock;

/* Number of CAMCHANGES that have occurred since last processing */
atomic_t camchange_count;

Expand Down Expand Up @@ -711,14 +714,20 @@ static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * b
dprintk("%s\n", __func__);


// sanity check
/* sanity check */
if (bytes_write > ca->slot_info[slot].link_buf_size)
return -EINVAL;

/* check if interface is actually waiting for us to read from it, or if a read is in progress */
/* it is possible we are dealing with a single buffer implementation,
thus if there is data available for read or if there is even a read
already in progress, we do nothing but awake the kernel thread to
process the data if necessary. */
if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0)
goto exitnowrite;
if (status & (STATUSREG_DA | STATUSREG_RE)) {
if (status & STATUSREG_DA)
dvb_ca_en50221_thread_wakeup(ca);

status = -EAGAIN;
goto exitnowrite;
}
Expand Down Expand Up @@ -987,6 +996,8 @@ static int dvb_ca_en50221_thread(void *data)
/* go through all the slots processing them */
for (slot = 0; slot < ca->slot_count; slot++) {

mutex_lock(&ca->slot_info[slot].slot_lock);

// check the cam status + deal with CAMCHANGEs
while (dvb_ca_en50221_check_camstatus(ca, slot)) {
/* clear down an old CI slot if necessary */
Expand Down Expand Up @@ -1122,7 +1133,7 @@ static int dvb_ca_en50221_thread(void *data)

case DVB_CA_SLOTSTATE_RUNNING:
if (!ca->open)
continue;
break;

// poll slots for data
pktcount = 0;
Expand All @@ -1146,6 +1157,8 @@ static int dvb_ca_en50221_thread(void *data)
}
break;
}

mutex_unlock(&ca->slot_info[slot].slot_lock);
}
}

Expand Down Expand Up @@ -1181,13 +1194,15 @@ static int dvb_ca_en50221_io_do_ioctl(struct inode *inode, struct file *file,
switch (cmd) {
case CA_RESET:
for (slot = 0; slot < ca->slot_count; slot++) {
mutex_lock(&ca->slot_info[slot].slot_lock);
if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE) {
dvb_ca_en50221_slot_shutdown(ca, slot);
if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)
dvb_ca_en50221_camchange_irq(ca->pub,
slot,
DVB_CA_EN50221_CAMCHANGE_INSERTED);
}
mutex_unlock(&ca->slot_info[slot].slot_lock);
}
ca->next_read_slot = 0;
dvb_ca_en50221_thread_wakeup(ca);
Expand Down Expand Up @@ -1308,7 +1323,9 @@ static ssize_t dvb_ca_en50221_io_write(struct file *file,
goto exit;
}

mutex_lock(&ca->slot_info[slot].slot_lock);
status = dvb_ca_en50221_write_data(ca, slot, fragbuf, fraglen + 2);
mutex_unlock(&ca->slot_info[slot].slot_lock);
if (status == (fraglen + 2)) {
written = 1;
break;
Expand Down Expand Up @@ -1664,6 +1681,7 @@ int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter,
ca->slot_info[i].slot_state = DVB_CA_SLOTSTATE_NONE;
atomic_set(&ca->slot_info[i].camchange_count, 0);
ca->slot_info[i].camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED;
mutex_init(&ca->slot_info[i].slot_lock);
}

if (signal_pending(current)) {
Expand Down
6 changes: 4 additions & 2 deletions drivers/media/dvb/dvb-core/dvb_ca_en50221.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ struct dvb_ca_en50221 {
/* the module owning this structure */
struct module* owner;

/* NOTE: the read_*, write_* and poll_slot_status functions must use locks as
* they may be called from several threads at once */
/* NOTE: the read_*, write_* and poll_slot_status functions will be
* called for different slots concurrently and need to use locks where
* and if appropriate. There will be no concurrent access to one slot.
*/

/* functions for accessing attribute memory on the CAM */
int (*read_attribute_mem)(struct dvb_ca_en50221* ca, int slot, int address);
Expand Down

0 comments on commit d7e4384

Please sign in to comment.