diff options
author | Mike Isely <isely@pobox.com> | 2006-09-23 21:30:50 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2006-10-03 14:13:38 -0400 |
commit | 759100531d76f36714dde0f05a09245e9e921e4c (patch) | |
tree | 453877480d05adcab9b2f49c3a5511878aef13fe | |
parent | 32ffa9ae03c529df4208b63b4b17c6d84141faa3 (diff) |
V4L/DVB (4663): Pvrusb2: Get rid of private global context array brain damage
A previous attempt to deal with the upcoming loss of
video_set_drvdata() and video_get_drvdata() resulted in logic which
causes a circular locking dependency - also known as a deadlock. This
changeset attacks the problem in a different manner, using a technique
that no longer requires the problematic mutex (or that private global
array either).
Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
-rw-r--r-- | drivers/media/video/pvrusb2/pvrusb2-v4l2.c | 111 |
1 files changed, 36 insertions, 75 deletions
diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c index 156e375c93e9..f4c54ef4beaa 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c +++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include "pvrusb2-debug.h" | 28 | #include "pvrusb2-debug.h" |
29 | #include "pvrusb2-v4l2.h" | 29 | #include "pvrusb2-v4l2.h" |
30 | #include "pvrusb2-ioread.h" | 30 | #include "pvrusb2-ioread.h" |
31 | #include <linux/videodev.h> | ||
31 | #include <linux/videodev2.h> | 32 | #include <linux/videodev2.h> |
32 | #include <media/v4l2-common.h> | 33 | #include <media/v4l2-common.h> |
33 | 34 | ||
@@ -35,21 +36,10 @@ struct pvr2_v4l2_dev; | |||
35 | struct pvr2_v4l2_fh; | 36 | struct pvr2_v4l2_fh; |
36 | struct pvr2_v4l2; | 37 | struct pvr2_v4l2; |
37 | 38 | ||
38 | /* V4L no longer provide the ability to set / get a private context pointer | ||
39 | (i.e. video_get_drvdata / video_set_drvdata), which means we have to | ||
40 | concoct our own context locating mechanism. Supposedly this is intended | ||
41 | to simplify driver implementation. It's not clear to me how that can | ||
42 | possibly be true. Our solution here is to maintain a lookup table of | ||
43 | our context instances, indexed by the minor device number of the V4L | ||
44 | device. See pvr2_v4l2_open() for some implications of this approach. */ | ||
45 | static struct pvr2_v4l2_dev *devices[256]; | ||
46 | static DEFINE_MUTEX(device_lock); | ||
47 | |||
48 | struct pvr2_v4l2_dev { | 39 | struct pvr2_v4l2_dev { |
40 | struct video_device devbase; /* MUST be first! */ | ||
49 | struct pvr2_v4l2 *v4lp; | 41 | struct pvr2_v4l2 *v4lp; |
50 | struct video_device *vdev; | ||
51 | struct pvr2_context_stream *stream; | 42 | struct pvr2_context_stream *stream; |
52 | int ctxt_idx; | ||
53 | enum pvr2_config config; | 43 | enum pvr2_config config; |
54 | }; | 44 | }; |
55 | 45 | ||
@@ -74,7 +64,7 @@ struct pvr2_v4l2 { | |||
74 | struct v4l2_prio_state prio; | 64 | struct v4l2_prio_state prio; |
75 | 65 | ||
76 | /* streams */ | 66 | /* streams */ |
77 | struct pvr2_v4l2_dev video_dev; | 67 | struct pvr2_v4l2_dev *vdev; |
78 | }; | 68 | }; |
79 | 69 | ||
80 | static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; | 70 | static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; |
@@ -718,21 +708,22 @@ static int pvr2_v4l2_do_ioctl(struct inode *inode, struct file *file, | |||
718 | static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip) | 708 | static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip) |
719 | { | 709 | { |
720 | printk(KERN_INFO "pvrusb2: unregistering device video%d [%s]\n", | 710 | printk(KERN_INFO "pvrusb2: unregistering device video%d [%s]\n", |
721 | dip->vdev->minor,pvr2_config_get_name(dip->config)); | 711 | dip->devbase.minor,pvr2_config_get_name(dip->config)); |
722 | if (dip->ctxt_idx >= 0) { | 712 | |
723 | mutex_lock(&device_lock); | 713 | /* Paranoia */ |
724 | devices[dip->ctxt_idx] = NULL; | 714 | dip->v4lp = 0; |
725 | dip->ctxt_idx = -1; | 715 | dip->stream = 0; |
726 | mutex_unlock(&device_lock); | 716 | |
727 | } | 717 | /* Actual deallocation happens later when all internal references |
728 | video_unregister_device(dip->vdev); | 718 | are gone. */ |
719 | video_unregister_device(&dip->devbase); | ||
729 | } | 720 | } |
730 | 721 | ||
731 | 722 | ||
732 | static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp) | 723 | static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp) |
733 | { | 724 | { |
734 | pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,-1); | 725 | pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,-1); |
735 | pvr2_v4l2_dev_destroy(&vp->video_dev); | 726 | pvr2_v4l2_dev_destroy(vp->vdev); |
736 | 727 | ||
737 | pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp); | 728 | pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp); |
738 | pvr2_channel_done(&vp->channel); | 729 | pvr2_channel_done(&vp->channel); |
@@ -740,6 +731,14 @@ static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp) | |||
740 | } | 731 | } |
741 | 732 | ||
742 | 733 | ||
734 | static void pvr2_video_device_release(struct video_device *vdev) | ||
735 | { | ||
736 | struct pvr2_v4l2_dev *dev; | ||
737 | dev = container_of(vdev,struct pvr2_v4l2_dev,devbase); | ||
738 | kfree(dev); | ||
739 | } | ||
740 | |||
741 | |||
743 | static void pvr2_v4l2_internal_check(struct pvr2_channel *chp) | 742 | static void pvr2_v4l2_internal_check(struct pvr2_channel *chp) |
744 | { | 743 | { |
745 | struct pvr2_v4l2 *vp; | 744 | struct pvr2_v4l2 *vp; |
@@ -811,40 +810,12 @@ static int pvr2_v4l2_release(struct inode *inode, struct file *file) | |||
811 | 810 | ||
812 | static int pvr2_v4l2_open(struct inode *inode, struct file *file) | 811 | static int pvr2_v4l2_open(struct inode *inode, struct file *file) |
813 | { | 812 | { |
814 | struct pvr2_v4l2_dev *dip = NULL; /* Our own context pointer */ | 813 | struct pvr2_v4l2_dev *dip; /* Our own context pointer */ |
815 | struct pvr2_v4l2_fh *fhp; | 814 | struct pvr2_v4l2_fh *fhp; |
816 | struct pvr2_v4l2 *vp; | 815 | struct pvr2_v4l2 *vp; |
817 | struct pvr2_hdw *hdw; | 816 | struct pvr2_hdw *hdw; |
818 | 817 | ||
819 | mutex_lock(&device_lock); | 818 | dip = container_of(video_devdata(file),struct pvr2_v4l2_dev,devbase); |
820 | /* MCI 7-Jun-2006 Even though we're just doing what amounts to an | ||
821 | atomic read of the device mapping array here, we still need the | ||
822 | mutex. The problem is that there is a tiny race possible when | ||
823 | we register the device. We can't update the device mapping | ||
824 | array until after the device has been registered, owing to the | ||
825 | fact that we can't know the minor device number until after the | ||
826 | registration succeeds. And if another thread tries to open the | ||
827 | device in the window of time after registration but before the | ||
828 | map is updated, then it will get back an erroneous null pointer | ||
829 | and the open will result in a spurious failure. The only way to | ||
830 | prevent that is to (a) be inside the mutex here before we access | ||
831 | the array, and (b) cover the entire registration process later | ||
832 | on with this same mutex. Thus if we get inside the mutex here, | ||
833 | then we can be assured that the registration process actually | ||
834 | completed correctly. This is an unhappy complication from the | ||
835 | use of global data in a driver that lives in a preemptible | ||
836 | environment. It sure would be nice if the video device itself | ||
837 | had a means for storing and retrieving a local context pointer. | ||
838 | Oh wait. It did. But now it's gone. Silly me. */ | ||
839 | { | ||
840 | unsigned int midx = iminor(file->f_dentry->d_inode); | ||
841 | if (midx < sizeof(devices)/sizeof(devices[0])) { | ||
842 | dip = devices[midx]; | ||
843 | } | ||
844 | } | ||
845 | mutex_unlock(&device_lock); | ||
846 | |||
847 | if (!dip) return -ENODEV; /* Should be impossible but I'm paranoid */ | ||
848 | 819 | ||
849 | vp = dip->v4lp; | 820 | vp = dip->v4lp; |
850 | hdw = vp->channel.hdw; | 821 | hdw = vp->channel.hdw; |
@@ -1074,38 +1045,24 @@ static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip, | |||
1074 | return; | 1045 | return; |
1075 | } | 1046 | } |
1076 | 1047 | ||
1077 | dip->vdev = video_device_alloc(); | 1048 | memcpy(&dip->devbase,&vdev_template,sizeof(vdev_template)); |
1078 | if (!dip->vdev) { | 1049 | dip->devbase.release = pvr2_video_device_release; |
1079 | err("Alloc of pvrusb2 v4l video device failed"); | ||
1080 | return; | ||
1081 | } | ||
1082 | |||
1083 | memcpy(dip->vdev,&vdev_template,sizeof(vdev_template)); | ||
1084 | dip->vdev->release = video_device_release; | ||
1085 | mutex_lock(&device_lock); | ||
1086 | 1050 | ||
1087 | mindevnum = -1; | 1051 | mindevnum = -1; |
1088 | unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw); | 1052 | unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw); |
1089 | if ((unit_number >= 0) && (unit_number < PVR_NUM)) { | 1053 | if ((unit_number >= 0) && (unit_number < PVR_NUM)) { |
1090 | mindevnum = video_nr[unit_number]; | 1054 | mindevnum = video_nr[unit_number]; |
1091 | } | 1055 | } |
1092 | if ((video_register_device(dip->vdev, v4l_type, mindevnum) < 0) && | 1056 | if ((video_register_device(&dip->devbase, v4l_type, mindevnum) < 0) && |
1093 | (video_register_device(dip->vdev, v4l_type, -1) < 0)) { | 1057 | (video_register_device(&dip->devbase, v4l_type, -1) < 0)) { |
1094 | err("Failed to register pvrusb2 v4l video device"); | 1058 | err("Failed to register pvrusb2 v4l video device"); |
1095 | } else { | 1059 | } else { |
1096 | printk(KERN_INFO "pvrusb2: registered device video%d [%s]\n", | 1060 | printk(KERN_INFO "pvrusb2: registered device video%d [%s]\n", |
1097 | dip->vdev->minor,pvr2_config_get_name(dip->config)); | 1061 | dip->devbase.minor,pvr2_config_get_name(dip->config)); |
1098 | } | 1062 | } |
1099 | 1063 | ||
1100 | if ((dip->vdev->minor < sizeof(devices)/sizeof(devices[0])) && | ||
1101 | (devices[dip->vdev->minor] == NULL)) { | ||
1102 | dip->ctxt_idx = dip->vdev->minor; | ||
1103 | devices[dip->ctxt_idx] = dip; | ||
1104 | } | ||
1105 | mutex_unlock(&device_lock); | ||
1106 | |||
1107 | pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw, | 1064 | pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw, |
1108 | dip->vdev->minor); | 1065 | dip->devbase.minor); |
1109 | } | 1066 | } |
1110 | 1067 | ||
1111 | 1068 | ||
@@ -1116,15 +1073,19 @@ struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp) | |||
1116 | vp = kmalloc(sizeof(*vp),GFP_KERNEL); | 1073 | vp = kmalloc(sizeof(*vp),GFP_KERNEL); |
1117 | if (!vp) return vp; | 1074 | if (!vp) return vp; |
1118 | memset(vp,0,sizeof(*vp)); | 1075 | memset(vp,0,sizeof(*vp)); |
1119 | vp->video_dev.ctxt_idx = -1; | 1076 | vp->vdev = kmalloc(sizeof(*vp->vdev),GFP_KERNEL); |
1077 | if (!vp->vdev) { | ||
1078 | kfree(vp); | ||
1079 | return 0; | ||
1080 | } | ||
1081 | memset(vp->vdev,0,sizeof(*vp->vdev)); | ||
1120 | pvr2_channel_init(&vp->channel,mnp); | 1082 | pvr2_channel_init(&vp->channel,mnp); |
1121 | pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp); | 1083 | pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp); |
1122 | 1084 | ||
1123 | vp->channel.check_func = pvr2_v4l2_internal_check; | 1085 | vp->channel.check_func = pvr2_v4l2_internal_check; |
1124 | 1086 | ||
1125 | /* register streams */ | 1087 | /* register streams */ |
1126 | pvr2_v4l2_dev_init(&vp->video_dev,vp,pvr2_config_mpeg); | 1088 | pvr2_v4l2_dev_init(vp->vdev,vp,pvr2_config_mpeg); |
1127 | |||
1128 | 1089 | ||
1129 | return vp; | 1090 | return vp; |
1130 | } | 1091 | } |