diff options
author | Mike Isely <isely@pobox.com> | 2008-04-09 04:14:11 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2008-04-24 13:09:49 -0400 |
commit | 18ecbb4771eb0ecf297e996966b3c42f69cd6c02 (patch) | |
tree | 06978b91985fd4c1b092ec5ac8fef0b134eb18b3 /drivers/media/video/pvrusb2 | |
parent | 13e027a8bf2a507334fa0d30246ce619ff581cbb (diff) |
V4L/DVB (7714): pvrusb2: Fix hang on module removal
The pvrusb2 driver was getting had by this scenario:
1. Task A calls kthread_stop() for task B.
2. Before exiting, then Task B calls kthread_stop() for task C.
The problem is, kthread_stop() wants to allocate an internal resource
to itself (i.e. acquire a lock), which won't be released until
kthread_stop() returns. But kthread_stop() won't return until task B
is dead. But task B won't die until it finishes its call to
kthread_stop() for task C, and that will block waiting on the resource
already allocated inside task A. Deadlock.
With the pvrusb2 driver, task A is the caller to pvr_exit(), task B is
the control thread run inside of pvrusb2-context.c, and task C is any
worker thread run inside of pvrusb2-hdw.c.
This problem got introduced by the previous threading setup change,
which was itself an attempt to fix a module tear-down race (which it
actually did fix). The lesson here is that a task being waited on as
part of a kthread_stop() simply cannot be allow to also issue a
kthread_stop() - or we make sure not to issue the enclosing
kthread_stop() until we know that the inner kthread_stop() has
completed first. The solution for the pvrusb2 driver is some hackish
code which changes the main control thread tear down into a two step
process. This then makes it possible to delay issuing the
kthread_stop() on the control thread until after we know that
everything has been torn down first. (And yes, we really need that
kthread_stop() because it's the only way to safely guarantee that a
module-referencing kernel thread has safely returned back out of the
module before we finally remove the module.)
Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/video/pvrusb2')
-rw-r--r-- | drivers/media/video/pvrusb2/pvrusb2-context.c | 19 |
1 files changed, 18 insertions, 1 deletions
diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.c b/drivers/media/video/pvrusb2/pvrusb2-context.c index e7a2ed58bde..a2ce022c515 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-context.c +++ b/drivers/media/video/pvrusb2/pvrusb2-context.c | |||
@@ -35,6 +35,9 @@ static struct pvr2_context *pvr2_context_notify_first; | |||
35 | static struct pvr2_context *pvr2_context_notify_last; | 35 | static struct pvr2_context *pvr2_context_notify_last; |
36 | static DEFINE_MUTEX(pvr2_context_mutex); | 36 | static DEFINE_MUTEX(pvr2_context_mutex); |
37 | static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data); | 37 | static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data); |
38 | static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_cleanup_data); | ||
39 | static int pvr2_context_cleanup_flag; | ||
40 | static int pvr2_context_cleaned_flag; | ||
38 | static struct task_struct *pvr2_context_thread_ptr; | 41 | static struct task_struct *pvr2_context_thread_ptr; |
39 | 42 | ||
40 | 43 | ||
@@ -153,7 +156,7 @@ static void pvr2_context_check(struct pvr2_context *mp) | |||
153 | 156 | ||
154 | static int pvr2_context_shutok(void) | 157 | static int pvr2_context_shutok(void) |
155 | { | 158 | { |
156 | return kthread_should_stop() && (pvr2_context_exist_first == NULL); | 159 | return pvr2_context_cleanup_flag && (pvr2_context_exist_first == NULL); |
157 | } | 160 | } |
158 | 161 | ||
159 | 162 | ||
@@ -174,6 +177,15 @@ static int pvr2_context_thread_func(void *foo) | |||
174 | pvr2_context_shutok())); | 177 | pvr2_context_shutok())); |
175 | } while (!pvr2_context_shutok()); | 178 | } while (!pvr2_context_shutok()); |
176 | 179 | ||
180 | pvr2_context_cleaned_flag = !0; | ||
181 | wake_up(&pvr2_context_cleanup_data); | ||
182 | |||
183 | pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread cleaned up"); | ||
184 | |||
185 | wait_event_interruptible( | ||
186 | pvr2_context_sync_data, | ||
187 | kthread_should_stop()); | ||
188 | |||
177 | pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end"); | 189 | pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end"); |
178 | 190 | ||
179 | return 0; | 191 | return 0; |
@@ -191,6 +203,11 @@ int pvr2_context_global_init(void) | |||
191 | 203 | ||
192 | void pvr2_context_global_done(void) | 204 | void pvr2_context_global_done(void) |
193 | { | 205 | { |
206 | pvr2_context_cleanup_flag = !0; | ||
207 | wake_up(&pvr2_context_sync_data); | ||
208 | wait_event_interruptible( | ||
209 | pvr2_context_cleanup_data, | ||
210 | pvr2_context_cleaned_flag); | ||
194 | kthread_stop(pvr2_context_thread_ptr); | 211 | kthread_stop(pvr2_context_thread_ptr); |
195 | } | 212 | } |
196 | 213 | ||