diff options
author | Oleg Nesterov <oleg@tv-sign.ru> | 2007-07-02 11:26:20 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2007-07-03 14:11:19 -0400 |
commit | 1e4597e8f0049dccedb0e011934007309fa2aeab (patch) | |
tree | 74e8413d6bd1c5202fb3767ce18ae22fae235a25 | |
parent | f057131fb6eb2c45f6023e3da41ccd6e4e71aee9 (diff) |
V4L/DVB (5818): CinergyT2: fix flush_workqueue() vs work->func() deadlock
Spotted and tested by Thomas Sattler <tsattler@gmx.de>.
cinergyT2.c does cancel_delayed_work() + flush_scheduled_work() while
holding cinergyt2->sem. This leads to deadlock because work->func()
needs the same mutex to complete. Another bug is that this code in fact
can't reliably stop the re-arming delayed_work.
Convert this code to use cancel_rearming_delayed_work() and move it
out of ->sem. Another mutex, ->wq_sem, was added to protect against the
concurrent open/resume.
This patch is a horrible hack to fix the lockup which happens in practice.
As Dmitry Torokhov pointed out this driver has other problems and needs
further changes.
Signed-off-by: Oleg Nesterov <oleg@tv-sign.ru>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
-rw-r--r-- | drivers/media/dvb/cinergyT2/cinergyT2.c | 66 |
1 files changed, 40 insertions, 26 deletions
diff --git a/drivers/media/dvb/cinergyT2/cinergyT2.c b/drivers/media/dvb/cinergyT2/cinergyT2.c index 6aba5b39ed14..b40af48a2edb 100644 --- a/drivers/media/dvb/cinergyT2/cinergyT2.c +++ b/drivers/media/dvb/cinergyT2/cinergyT2.c | |||
@@ -118,6 +118,7 @@ struct cinergyt2 { | |||
118 | struct dvb_demux demux; | 118 | struct dvb_demux demux; |
119 | struct usb_device *udev; | 119 | struct usb_device *udev; |
120 | struct mutex sem; | 120 | struct mutex sem; |
121 | struct mutex wq_sem; | ||
121 | struct dvb_adapter adapter; | 122 | struct dvb_adapter adapter; |
122 | struct dvb_device *fedev; | 123 | struct dvb_device *fedev; |
123 | struct dmxdev dmxdev; | 124 | struct dmxdev dmxdev; |
@@ -482,14 +483,14 @@ static int cinergyt2_open (struct inode *inode, struct file *file) | |||
482 | struct cinergyt2 *cinergyt2 = dvbdev->priv; | 483 | struct cinergyt2 *cinergyt2 = dvbdev->priv; |
483 | int err = -ERESTARTSYS; | 484 | int err = -ERESTARTSYS; |
484 | 485 | ||
485 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->sem)) | 486 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->wq_sem)) |
486 | return -ERESTARTSYS; | 487 | goto out; |
487 | 488 | ||
488 | if ((err = dvb_generic_open(inode, file))) { | 489 | if (mutex_lock_interruptible(&cinergyt2->sem)) |
489 | mutex_unlock(&cinergyt2->sem); | 490 | goto out_unlock1; |
490 | return err; | ||
491 | } | ||
492 | 491 | ||
492 | if ((err = dvb_generic_open(inode, file))) | ||
493 | goto out_unlock2; | ||
493 | 494 | ||
494 | if ((file->f_flags & O_ACCMODE) != O_RDONLY) { | 495 | if ((file->f_flags & O_ACCMODE) != O_RDONLY) { |
495 | cinergyt2_sleep(cinergyt2, 0); | 496 | cinergyt2_sleep(cinergyt2, 0); |
@@ -498,8 +499,12 @@ static int cinergyt2_open (struct inode *inode, struct file *file) | |||
498 | 499 | ||
499 | atomic_inc(&cinergyt2->inuse); | 500 | atomic_inc(&cinergyt2->inuse); |
500 | 501 | ||
502 | out_unlock2: | ||
501 | mutex_unlock(&cinergyt2->sem); | 503 | mutex_unlock(&cinergyt2->sem); |
502 | return 0; | 504 | out_unlock1: |
505 | mutex_unlock(&cinergyt2->wq_sem); | ||
506 | out: | ||
507 | return err; | ||
503 | } | 508 | } |
504 | 509 | ||
505 | static void cinergyt2_unregister(struct cinergyt2 *cinergyt2) | 510 | static void cinergyt2_unregister(struct cinergyt2 *cinergyt2) |
@@ -519,15 +524,17 @@ static int cinergyt2_release (struct inode *inode, struct file *file) | |||
519 | struct dvb_device *dvbdev = file->private_data; | 524 | struct dvb_device *dvbdev = file->private_data; |
520 | struct cinergyt2 *cinergyt2 = dvbdev->priv; | 525 | struct cinergyt2 *cinergyt2 = dvbdev->priv; |
521 | 526 | ||
522 | mutex_lock(&cinergyt2->sem); | 527 | mutex_lock(&cinergyt2->wq_sem); |
523 | 528 | ||
524 | if (!cinergyt2->disconnect_pending && (file->f_flags & O_ACCMODE) != O_RDONLY) { | 529 | if (!cinergyt2->disconnect_pending && (file->f_flags & O_ACCMODE) != O_RDONLY) { |
525 | cancel_delayed_work(&cinergyt2->query_work); | 530 | cancel_rearming_delayed_work(&cinergyt2->query_work); |
526 | flush_scheduled_work(); | 531 | |
532 | mutex_lock(&cinergyt2->sem); | ||
527 | cinergyt2_sleep(cinergyt2, 1); | 533 | cinergyt2_sleep(cinergyt2, 1); |
534 | mutex_unlock(&cinergyt2->sem); | ||
528 | } | 535 | } |
529 | 536 | ||
530 | mutex_unlock(&cinergyt2->sem); | 537 | mutex_unlock(&cinergyt2->wq_sem); |
531 | 538 | ||
532 | if (atomic_dec_and_test(&cinergyt2->inuse) && cinergyt2->disconnect_pending) { | 539 | if (atomic_dec_and_test(&cinergyt2->inuse) && cinergyt2->disconnect_pending) { |
533 | warn("delayed unregister in release"); | 540 | warn("delayed unregister in release"); |
@@ -838,13 +845,13 @@ static int cinergyt2_register_rc(struct cinergyt2 *cinergyt2) | |||
838 | 845 | ||
839 | static void cinergyt2_unregister_rc(struct cinergyt2 *cinergyt2) | 846 | static void cinergyt2_unregister_rc(struct cinergyt2 *cinergyt2) |
840 | { | 847 | { |
841 | cancel_delayed_work(&cinergyt2->rc_query_work); | 848 | cancel_rearming_delayed_work(&cinergyt2->rc_query_work); |
842 | input_unregister_device(cinergyt2->rc_input_dev); | 849 | input_unregister_device(cinergyt2->rc_input_dev); |
843 | } | 850 | } |
844 | 851 | ||
845 | static inline void cinergyt2_suspend_rc(struct cinergyt2 *cinergyt2) | 852 | static inline void cinergyt2_suspend_rc(struct cinergyt2 *cinergyt2) |
846 | { | 853 | { |
847 | cancel_delayed_work(&cinergyt2->rc_query_work); | 854 | cancel_rearming_delayed_work(&cinergyt2->rc_query_work); |
848 | } | 855 | } |
849 | 856 | ||
850 | static inline void cinergyt2_resume_rc(struct cinergyt2 *cinergyt2) | 857 | static inline void cinergyt2_resume_rc(struct cinergyt2 *cinergyt2) |
@@ -907,6 +914,7 @@ static int cinergyt2_probe (struct usb_interface *intf, | |||
907 | usb_set_intfdata (intf, (void *) cinergyt2); | 914 | usb_set_intfdata (intf, (void *) cinergyt2); |
908 | 915 | ||
909 | mutex_init(&cinergyt2->sem); | 916 | mutex_init(&cinergyt2->sem); |
917 | mutex_init(&cinergyt2->wq_sem); | ||
910 | init_waitqueue_head (&cinergyt2->poll_wq); | 918 | init_waitqueue_head (&cinergyt2->poll_wq); |
911 | INIT_DELAYED_WORK(&cinergyt2->query_work, cinergyt2_query); | 919 | INIT_DELAYED_WORK(&cinergyt2->query_work, cinergyt2_query); |
912 | 920 | ||
@@ -974,11 +982,8 @@ static void cinergyt2_disconnect (struct usb_interface *intf) | |||
974 | { | 982 | { |
975 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); | 983 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); |
976 | 984 | ||
977 | flush_scheduled_work(); | ||
978 | |||
979 | cinergyt2_unregister_rc(cinergyt2); | 985 | cinergyt2_unregister_rc(cinergyt2); |
980 | 986 | cancel_rearming_delayed_work(&cinergyt2->query_work); | |
981 | cancel_delayed_work(&cinergyt2->query_work); | ||
982 | wake_up_interruptible(&cinergyt2->poll_wq); | 987 | wake_up_interruptible(&cinergyt2->poll_wq); |
983 | 988 | ||
984 | cinergyt2->demux.dmx.close(&cinergyt2->demux.dmx); | 989 | cinergyt2->demux.dmx.close(&cinergyt2->demux.dmx); |
@@ -992,21 +997,21 @@ static int cinergyt2_suspend (struct usb_interface *intf, pm_message_t state) | |||
992 | { | 997 | { |
993 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); | 998 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); |
994 | 999 | ||
995 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->sem)) | 1000 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->wq_sem)) |
996 | return -ERESTARTSYS; | 1001 | return -ERESTARTSYS; |
997 | 1002 | ||
998 | if (1) { | 1003 | if (1) { |
999 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); | ||
1000 | |||
1001 | cinergyt2_suspend_rc(cinergyt2); | 1004 | cinergyt2_suspend_rc(cinergyt2); |
1002 | cancel_delayed_work(&cinergyt2->query_work); | 1005 | cancel_rearming_delayed_work(&cinergyt2->query_work); |
1006 | |||
1007 | mutex_lock(&cinergyt2->sem); | ||
1003 | if (cinergyt2->streaming) | 1008 | if (cinergyt2->streaming) |
1004 | cinergyt2_stop_stream_xfer(cinergyt2); | 1009 | cinergyt2_stop_stream_xfer(cinergyt2); |
1005 | flush_scheduled_work(); | ||
1006 | cinergyt2_sleep(cinergyt2, 1); | 1010 | cinergyt2_sleep(cinergyt2, 1); |
1011 | mutex_unlock(&cinergyt2->sem); | ||
1007 | } | 1012 | } |
1008 | 1013 | ||
1009 | mutex_unlock(&cinergyt2->sem); | 1014 | mutex_unlock(&cinergyt2->wq_sem); |
1010 | return 0; | 1015 | return 0; |
1011 | } | 1016 | } |
1012 | 1017 | ||
@@ -1014,9 +1019,15 @@ static int cinergyt2_resume (struct usb_interface *intf) | |||
1014 | { | 1019 | { |
1015 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); | 1020 | struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); |
1016 | struct dvbt_set_parameters_msg *param = &cinergyt2->param; | 1021 | struct dvbt_set_parameters_msg *param = &cinergyt2->param; |
1022 | int err = -ERESTARTSYS; | ||
1017 | 1023 | ||
1018 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->sem)) | 1024 | if (cinergyt2->disconnect_pending || mutex_lock_interruptible(&cinergyt2->wq_sem)) |
1019 | return -ERESTARTSYS; | 1025 | goto out; |
1026 | |||
1027 | if (mutex_lock_interruptible(&cinergyt2->sem)) | ||
1028 | goto out_unlock1; | ||
1029 | |||
1030 | err = 0; | ||
1020 | 1031 | ||
1021 | if (!cinergyt2->sleeping) { | 1032 | if (!cinergyt2->sleeping) { |
1022 | cinergyt2_sleep(cinergyt2, 0); | 1033 | cinergyt2_sleep(cinergyt2, 0); |
@@ -1029,7 +1040,10 @@ static int cinergyt2_resume (struct usb_interface *intf) | |||
1029 | cinergyt2_resume_rc(cinergyt2); | 1040 | cinergyt2_resume_rc(cinergyt2); |
1030 | 1041 | ||
1031 | mutex_unlock(&cinergyt2->sem); | 1042 | mutex_unlock(&cinergyt2->sem); |
1032 | return 0; | 1043 | out_unlock1: |
1044 | mutex_unlock(&cinergyt2->wq_sem); | ||
1045 | out: | ||
1046 | return err; | ||
1033 | } | 1047 | } |
1034 | 1048 | ||
1035 | static const struct usb_device_id cinergyt2_table [] __devinitdata = { | 1049 | static const struct usb_device_id cinergyt2_table [] __devinitdata = { |