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>
This commit is contained in:
committed by
John W. Linville
parent
aec8e88c94
commit
85ba8f529c
@@ -40,6 +40,7 @@ struct hwbus_priv {
|
|||||||
struct cw1200_common *core;
|
struct cw1200_common *core;
|
||||||
const struct cw1200_platform_data_spi *pdata;
|
const struct cw1200_platform_data_spi *pdata;
|
||||||
spinlock_t lock; /* Serialize all bus operations */
|
spinlock_t lock; /* Serialize all bus operations */
|
||||||
|
wait_queue_head_t wq;
|
||||||
int claimed;
|
int claimed;
|
||||||
int irq_disabled;
|
int irq_disabled;
|
||||||
};
|
};
|
||||||
@@ -198,8 +199,11 @@ static void cw1200_spi_lock(struct hwbus_priv *self)
|
|||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
|
DECLARE_WAITQUEUE(wait, current);
|
||||||
|
|
||||||
might_sleep();
|
might_sleep();
|
||||||
|
|
||||||
|
add_wait_queue(&self->wq, &wait);
|
||||||
spin_lock_irqsave(&self->lock, flags);
|
spin_lock_irqsave(&self->lock, flags);
|
||||||
while (1) {
|
while (1) {
|
||||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||||
@@ -212,6 +216,7 @@ static void cw1200_spi_lock(struct hwbus_priv *self)
|
|||||||
set_current_state(TASK_RUNNING);
|
set_current_state(TASK_RUNNING);
|
||||||
self->claimed = 1;
|
self->claimed = 1;
|
||||||
spin_unlock_irqrestore(&self->lock, flags);
|
spin_unlock_irqrestore(&self->lock, flags);
|
||||||
|
remove_wait_queue(&self->wq, &wait);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -223,6 +228,8 @@ static void cw1200_spi_unlock(struct hwbus_priv *self)
|
|||||||
spin_lock_irqsave(&self->lock, flags);
|
spin_lock_irqsave(&self->lock, flags);
|
||||||
self->claimed = 0;
|
self->claimed = 0;
|
||||||
spin_unlock_irqrestore(&self->lock, flags);
|
spin_unlock_irqrestore(&self->lock, flags);
|
||||||
|
wake_up(&self->wq);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +420,8 @@ static int cw1200_spi_probe(struct spi_device *func)
|
|||||||
|
|
||||||
spi_set_drvdata(func, self);
|
spi_set_drvdata(func, self);
|
||||||
|
|
||||||
|
init_waitqueue_head(&self->wq);
|
||||||
|
|
||||||
status = cw1200_spi_irq_subscribe(self);
|
status = cw1200_spi_irq_subscribe(self);
|
||||||
|
|
||||||
status = cw1200_core_probe(&cw1200_spi_hwbus_ops,
|
status = cw1200_core_probe(&cw1200_spi_hwbus_ops,
|
||||||
|
Reference in New Issue
Block a user