Browse Source

V4L/DVB (9655): radio-mr800: fix unplug

This patch fixes problems(kernel oopses) with unplug of device while
it's working.
Patch adds disconnect_lock mutex, changes usb_amradio_close and
usb_amradio_disconnect functions and adds a lot of safety checks.

Signed-off-by: Alexey Klimov <klimov.linux@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Alexey Klimov 16 years ago
parent
commit
3480130a50
1 changed files with 54 additions and 8 deletions
  1. 54 8
      drivers/media/radio/radio-mr800.c

+ 54 - 8
drivers/media/radio/radio-mr800.c

@@ -141,6 +141,7 @@ struct amradio_device {
 
 	unsigned char *buffer;
 	struct mutex lock;	/* buffer locking */
+	struct mutex disconnect_lock;
 	int curfreq;
 	int stereo;
 	int users;
@@ -207,6 +208,10 @@ static int amradio_stop(struct amradio_device *radio)
 	int retval;
 	int size;
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	mutex_lock(&radio->lock);
 
 	radio->buffer[0] = 0x00;
@@ -240,6 +245,10 @@ static int amradio_setfreq(struct amradio_device *radio, int freq)
 	int size;
 	unsigned short freq_send = 0x13 + (freq >> 3) / 25;
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	mutex_lock(&radio->lock);
 
 	radio->buffer[0] = 0x00;
@@ -293,18 +302,16 @@ static void usb_amradio_disconnect(struct usb_interface *intf)
 {
 	struct amradio_device *radio = usb_get_intfdata(intf);
 
+	mutex_lock(&radio->disconnect_lock);
+	radio->removed = 1;
 	usb_set_intfdata(intf, NULL);
 
-	if (radio) {
+	if (radio->users == 0) {
 		video_unregister_device(radio->videodev);
-		radio->videodev = NULL;
-		if (radio->users) {
-			kfree(radio->buffer);
-			kfree(radio);
-		} else {
-			radio->removed = 1;
-		}
+		kfree(radio->buffer);
+		kfree(radio);
 	}
+	mutex_unlock(&radio->disconnect_lock);
 }
 
 /* vidioc_querycap - query device capabilities */
@@ -325,6 +332,10 @@ static int vidioc_g_tuner(struct file *file, void *priv,
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	if (v->index > 0)
 		return -EINVAL;
 
@@ -351,6 +362,12 @@ static int vidioc_g_tuner(struct file *file, void *priv,
 static int vidioc_s_tuner(struct file *file, void *priv,
 				struct v4l2_tuner *v)
 {
+	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	if (v->index > 0)
 		return -EINVAL;
 	return 0;
@@ -362,6 +379,10 @@ static int vidioc_s_frequency(struct file *file, void *priv,
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	radio->curfreq = f->frequency;
 	if (amradio_setfreq(radio, radio->curfreq) < 0)
 		amradio_dev_warn(&radio->videodev->dev,
@@ -375,6 +396,10 @@ static int vidioc_g_frequency(struct file *file, void *priv,
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	f->type = V4L2_TUNER_RADIO;
 	f->frequency = radio->curfreq;
 	return 0;
@@ -401,6 +426,10 @@ static int vidioc_g_ctrl(struct file *file, void *priv,
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	switch (ctrl->id) {
 	case V4L2_CID_AUDIO_MUTE:
 		ctrl->value = radio->muted;
@@ -415,6 +444,10 @@ static int vidioc_s_ctrl(struct file *file, void *priv,
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
 
+	/* safety check */
+	if (radio->removed)
+		return -EIO;
+
 	switch (ctrl->id) {
 	case V4L2_CID_AUDIO_MUTE:
 		if (ctrl->value) {
@@ -500,14 +533,26 @@ static int usb_amradio_open(struct inode *inode, struct file *file)
 static int usb_amradio_close(struct inode *inode, struct file *file)
 {
 	struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+	int retval;
 
 	if (!radio)
 		return -ENODEV;
+
+	mutex_lock(&radio->disconnect_lock);
 	radio->users = 0;
 	if (radio->removed) {
+		video_unregister_device(radio->videodev);
 		kfree(radio->buffer);
 		kfree(radio);
+
+	} else {
+		retval = amradio_stop(radio);
+		if (retval < 0)
+			amradio_dev_warn(&radio->videodev->dev,
+				"amradio_stop failed\n");
 	}
+
+	mutex_unlock(&radio->disconnect_lock);
 	return 0;
 }
 
@@ -607,6 +652,7 @@ static int usb_amradio_probe(struct usb_interface *intf,
 	radio->usbdev = interface_to_usbdev(intf);
 	radio->curfreq = 95.16 * FREQ_MUL;
 
+	mutex_init(&radio->disconnect_lock);
 	mutex_init(&radio->lock);
 
 	video_set_drvdata(radio->videodev, radio);