diff options
author | Solomon Peachy <pizza@shaftnet.org> | 2013-08-27 20:29:47 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2013-09-09 14:40:54 -0400 |
commit | 85ba8f529c57ac6e2fca9be0d9e17920a1afb2e8 (patch) | |
tree | bb22dd1b9f6e5b442d3c6c2bad87476baa4b9e0f | |
parent | aec8e88c947b7017e2b4bbcb68a4bfc4a1f8ad35 (diff) |
cw1200: Prevent a lock-related hang in the cw1200_spi driver
The cw1200_spi driver tries to mirror the cw1200_sdio driver's lock
API, which relies on sdio_claim_host/sdio_release_host to serialize
hardware operations across multiple threads.
Unfortunately the implementation was flawed, as it lacked a way to wake
up the lock requestor when there was contention, often resulting in a
hang.
This problem was uncovered while trying to fix the
spi-transfers-in-interrupt-context BUG() corrected in the previous
patch. Many thanks to Dave Sizeburns for his assistance in fixing this.
Signed-off-by: Solomon Peachy <pizza@shaftnet.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r-- | drivers/net/wireless/cw1200/cw1200_spi.c | 9 |
1 files changed, 9 insertions, 0 deletions
diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c index c31580ba883b..f5e6b489ed32 100644 --- a/drivers/net/wireless/cw1200/cw1200_spi.c +++ b/drivers/net/wireless/cw1200/cw1200_spi.c | |||
@@ -40,6 +40,7 @@ struct hwbus_priv { | |||
40 | struct cw1200_common *core; | 40 | struct cw1200_common *core; |
41 | const struct cw1200_platform_data_spi *pdata; | 41 | const struct cw1200_platform_data_spi *pdata; |
42 | spinlock_t lock; /* Serialize all bus operations */ | 42 | spinlock_t lock; /* Serialize all bus operations */ |
43 | wait_queue_head_t wq; | ||
43 | int claimed; | 44 | int claimed; |
44 | int irq_disabled; | 45 | int irq_disabled; |
45 | }; | 46 | }; |
@@ -198,8 +199,11 @@ static void cw1200_spi_lock(struct hwbus_priv *self) | |||
198 | { | 199 | { |
199 | unsigned long flags; | 200 | unsigned long flags; |
200 | 201 | ||
202 | DECLARE_WAITQUEUE(wait, current); | ||
203 | |||
201 | might_sleep(); | 204 | might_sleep(); |
202 | 205 | ||
206 | add_wait_queue(&self->wq, &wait); | ||
203 | spin_lock_irqsave(&self->lock, flags); | 207 | spin_lock_irqsave(&self->lock, flags); |
204 | while (1) { | 208 | while (1) { |
205 | set_current_state(TASK_UNINTERRUPTIBLE); | 209 | set_current_state(TASK_UNINTERRUPTIBLE); |
@@ -212,6 +216,7 @@ static void cw1200_spi_lock(struct hwbus_priv *self) | |||
212 | set_current_state(TASK_RUNNING); | 216 | set_current_state(TASK_RUNNING); |
213 | self->claimed = 1; | 217 | self->claimed = 1; |
214 | spin_unlock_irqrestore(&self->lock, flags); | 218 | spin_unlock_irqrestore(&self->lock, flags); |
219 | remove_wait_queue(&self->wq, &wait); | ||
215 | 220 | ||
216 | return; | 221 | return; |
217 | } | 222 | } |
@@ -223,6 +228,8 @@ static void cw1200_spi_unlock(struct hwbus_priv *self) | |||
223 | spin_lock_irqsave(&self->lock, flags); | 228 | spin_lock_irqsave(&self->lock, flags); |
224 | self->claimed = 0; | 229 | self->claimed = 0; |
225 | spin_unlock_irqrestore(&self->lock, flags); | 230 | spin_unlock_irqrestore(&self->lock, flags); |
231 | wake_up(&self->wq); | ||
232 | |||
226 | return; | 233 | return; |
227 | } | 234 | } |
228 | 235 | ||
@@ -413,6 +420,8 @@ static int cw1200_spi_probe(struct spi_device *func) | |||
413 | 420 | ||
414 | spi_set_drvdata(func, self); | 421 | spi_set_drvdata(func, self); |
415 | 422 | ||
423 | init_waitqueue_head(&self->wq); | ||
424 | |||
416 | status = cw1200_spi_irq_subscribe(self); | 425 | status = cw1200_spi_irq_subscribe(self); |
417 | 426 | ||
418 | status = cw1200_core_probe(&cw1200_spi_hwbus_ops, | 427 | status = cw1200_core_probe(&cw1200_spi_hwbus_ops, |