diff options
Diffstat (limited to 'linux/drivers/media/video')
-rw-r--r-- | linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c | 71 |
1 files changed, 63 insertions, 8 deletions
diff --git a/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c index f4284f927..78cdd01c8 100644 --- a/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c +++ b/linux/drivers/media/video/pvrusb2/pvrusb2-v4l2.c @@ -22,8 +22,6 @@ #include "compat.h" #include <linux/kernel.h> -#include <linux/videodev.h> - #include "pvrusb2-context.h" #include "pvrusb2-hdw.h" #include "pvrusb2.h" @@ -37,10 +35,21 @@ struct pvr2_v4l2_dev; struct pvr2_v4l2_fh; struct pvr2_v4l2; +/* V4L no longer provide the ability to set / get a private context pointer + (i.e. video_get_drvdata / video_set_drvdata), which means we have to + concoct our own context locating mechanism. Supposedly this is intended + to simplify driver implementation. It's not clear to me how that can + possibly be true. Our solution here is to maintain a lookup table of + our context instances, indexed by the minor device number of the V4L + device. See pvr2_v4l2_open() for some implications of this approach. */ +static struct pvr2_v4l2_dev *devices[256]; +static DEFINE_MUTEX(device_lock); + struct pvr2_v4l2_dev { struct pvr2_v4l2 *v4lp; struct video_device *vdev; struct pvr2_context_stream *stream; + int ctxt_idx; enum pvr2_config config; }; @@ -641,6 +650,12 @@ static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip) pvr2_trace(PVR2_TRACE_INIT, "unregistering device video%d [%s]", dip->vdev->minor,pvr2_config_get_name(dip->config)); + if (dip->ctxt_idx >= 0) { + mutex_lock(&device_lock); + devices[dip->ctxt_idx] = NULL; + dip->ctxt_idx = -1; + mutex_unlock(&device_lock); + } video_unregister_device(dip->vdev); } @@ -727,12 +742,43 @@ int pvr2_v4l2_release(struct inode *inode, struct file *file) int pvr2_v4l2_open(struct inode *inode, struct file *file) { - struct video_device *vdev = video_devdata(file); - struct pvr2_v4l2_dev *dip = - (struct pvr2_v4l2_dev *)video_get_drvdata(vdev); + struct pvr2_v4l2_dev *dip = 0; /* Our own context pointer */ struct pvr2_v4l2_fh *fhp; - struct pvr2_v4l2 *vp = dip->v4lp; - struct pvr2_hdw *hdw = vp->channel.hdw; + struct pvr2_v4l2 *vp; + struct pvr2_hdw *hdw; + + mutex_lock(&device_lock); + /* MCI 7-Jun-2006 Even though we're just doing what amounts to an + atomic read of the device mapping array here, we still need the + mutex. The problem is that there is a tiny race possible when + we register the device. We can't update the device mapping + array until after the device has been registered, owing to the + fact that we can't know the minor device number until after the + registration succeeds. And if another thread tries to open the + device in the window of time after registration but before the + map is updated, then it will get back an erroneous null pointer + and the open will result in a spurious failure. The only way to + prevent that is to (a) be inside the mutex here before we access + the array, and (b) cover the entire registration process later + on with this same mutex. Thus if we get inside the mutex here, + then we can be assured that the registration process actually + completed correctly. This is an unhappy complication from the + use of global data in a driver that lives in a preemptible + environment. It sure would be nice if the video device itself + had a means for storing and retrieving a local context pointer. + Oh wait. It did. But now it's gone. Silly me. */ + { + unsigned int midx = iminor(file->f_dentry->d_inode); + if (midx < sizeof(devices)/sizeof(devices[0])) { + dip = devices[midx]; + } + } + mutex_unlock(&device_lock); + + if (!dip) return -ENODEV; /* Should be impossible but I'm paranoid */ + + vp = dip->v4lp; + hdw = vp->channel.hdw; pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open"); @@ -980,7 +1026,7 @@ static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip, dip->vdev->dev = &usbdev->dev; #endif dip->vdev->release = video_device_release; - video_set_drvdata(dip->vdev,dip); + mutex_lock(&device_lock); mindevnum = -1; unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw); @@ -995,6 +1041,14 @@ static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip, "registered device video%d [%s]", dip->vdev->minor,pvr2_config_get_name(dip->config)); } + + if ((dip->vdev->minor < sizeof(devices)/sizeof(devices[0])) && + (devices[dip->vdev->minor] == NULL)) { + dip->ctxt_idx = dip->vdev->minor; + devices[dip->ctxt_idx] = dip; + } + mutex_unlock(&device_lock); + pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw, dip->vdev->minor); } @@ -1007,6 +1061,7 @@ struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp) vp = kmalloc(sizeof(*vp),GFP_KERNEL); if (!vp) return vp; memset(vp,0,sizeof(*vp)); + vp->video_dev.ctxt_idx = -1; pvr2_channel_init(&vp->channel,mnp); pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp); |