When we get an interrupt from the hardware, the first thing the driver does
is tell the device to mask off the interrupt line. Unfortunately this
involves a SPI transaction in interrupt context. Some (most?) SPI
controllers perform the transfer asynchronously and try to sleep.
This is bad, and triggers a BUG().
So, work around this by using adding a hwbus hook for the cw1200 driver
core to call. The cw1200_spi driver translates this into
irq_disable()/irq_enable() calls instead, which can safely be called in
interrupt context.
Apparently the platforms I used to develop the cw1200_spi driver used
synchronous spi_sync() implementations, which is why this didn't surface
until now.
Many thanks to Dave Sizeburns for the inital bug report and his services
as a tester.
Signed-off-by: Solomon Peachy <[email protected]>
---
Please consider this for 3.11-rc; without this patch many SPI users
will immediately trigger a BUG().
drivers/net/wireless/cw1200/cw1200_spi.c | 19 ++++++++++++++++---
drivers/net/wireless/cw1200/fwio.c | 2 +-
drivers/net/wireless/cw1200/hwbus.h | 1 +
drivers/net/wireless/cw1200/hwio.c | 15 +++++++++++++++
4 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c
index d063760..c31580b 100644
--- a/drivers/net/wireless/cw1200/cw1200_spi.c
+++ b/drivers/net/wireless/cw1200/cw1200_spi.c
@@ -41,6 +41,7 @@ struct hwbus_priv {
const struct cw1200_platform_data_spi *pdata;
spinlock_t lock; /* Serialize all bus operations */
int claimed;
+ int irq_disabled;
};
#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2)
@@ -230,6 +231,8 @@ static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
struct hwbus_priv *self = dev_id;
if (self->core) {
+ disable_irq_nosync(self->func->irq);
+ self->irq_disabled = 1;
cw1200_irq_handler(self->core);
return IRQ_HANDLED;
} else {
@@ -263,13 +266,22 @@ exit:
static int cw1200_spi_irq_unsubscribe(struct hwbus_priv *self)
{
- int ret = 0;
-
pr_debug("SW IRQ unsubscribe\n");
disable_irq_wake(self->func->irq);
free_irq(self->func->irq, self);
- return ret;
+ return 0;
+}
+
+static int cw1200_spi_irq_enable(struct hwbus_priv *self, int enable)
+{
+ /* Disables are handled by the interrupt handler */
+ if (enable && self->irq_disabled) {
+ enable_irq(self->func->irq);
+ self->irq_disabled = 0;
+ }
+
+ return 0;
}
static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata)
@@ -349,6 +361,7 @@ static struct hwbus_ops cw1200_spi_hwbus_ops = {
.unlock = cw1200_spi_unlock,
.align_size = cw1200_spi_align_size,
.power_mgmt = cw1200_spi_pm,
+ .irq_enable = cw1200_spi_irq_enable,
};
/* Probe Function to be called by SPI stack when device is discovered */
diff --git a/drivers/net/wireless/cw1200/fwio.c b/drivers/net/wireless/cw1200/fwio.c
index acdff0f..0b2061b 100644
--- a/drivers/net/wireless/cw1200/fwio.c
+++ b/drivers/net/wireless/cw1200/fwio.c
@@ -485,7 +485,7 @@ int cw1200_load_firmware(struct cw1200_common *priv)
/* Enable interrupt signalling */
priv->hwbus_ops->lock(priv->hwbus_priv);
- ret = __cw1200_irq_enable(priv, 1);
+ ret = __cw1200_irq_enable(priv, 2);
priv->hwbus_ops->unlock(priv->hwbus_priv);
if (ret < 0)
goto unsubscribe;
diff --git a/drivers/net/wireless/cw1200/hwbus.h b/drivers/net/wireless/cw1200/hwbus.h
index 8b2fc83..51dfb3a 100644
--- a/drivers/net/wireless/cw1200/hwbus.h
+++ b/drivers/net/wireless/cw1200/hwbus.h
@@ -28,6 +28,7 @@ struct hwbus_ops {
void (*unlock)(struct hwbus_priv *self);
size_t (*align_size)(struct hwbus_priv *self, size_t size);
int (*power_mgmt)(struct hwbus_priv *self, bool suspend);
+ int (*irq_enable)(struct hwbus_priv *self, int enable);
};
#endif /* CW1200_HWBUS_H */
diff --git a/drivers/net/wireless/cw1200/hwio.c b/drivers/net/wireless/cw1200/hwio.c
index ff230b7..41bd761 100644
--- a/drivers/net/wireless/cw1200/hwio.c
+++ b/drivers/net/wireless/cw1200/hwio.c
@@ -273,6 +273,21 @@ int __cw1200_irq_enable(struct cw1200_common *priv, int enable)
u16 val16;
int ret;
+ /* We need to do this hack because the SPI layer can sleep on I/O
+ and the general path involves I/O to the device in interrupt
+ context.
+
+ However, the initial enable call needs to go to the hardware.
+
+ We don't worry about shutdown because we do a full reset which
+ clears the interrupt enabled bits.
+ */
+ if (priv->hwbus_ops->irq_enable) {
+ ret = priv->hwbus_ops->irq_enable(priv->hwbus_priv, enable);
+ if (ret || enable < 2)
+ return ret;
+ }
+
if (HIF_8601_SILICON == priv->hw_type) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
--
1.8.3.1
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 <[email protected]>
---
Please consider this for 3.11-rc; without this patch SPI users are all
but guaranteed to deadlock while running this driver.
drivers/net/wireless/cw1200/cw1200_spi.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/wireless/cw1200/cw1200_spi.c b/drivers/net/wireless/cw1200/cw1200_spi.c
index c31580b..f5e6b48 100644
--- a/drivers/net/wireless/cw1200/cw1200_spi.c
+++ b/drivers/net/wireless/cw1200/cw1200_spi.c
@@ -40,6 +40,7 @@ struct hwbus_priv {
struct cw1200_common *core;
const struct cw1200_platform_data_spi *pdata;
spinlock_t lock; /* Serialize all bus operations */
+ wait_queue_head_t wq;
int claimed;
int irq_disabled;
};
@@ -198,8 +199,11 @@ static void cw1200_spi_lock(struct hwbus_priv *self)
{
unsigned long flags;
+ DECLARE_WAITQUEUE(wait, current);
+
might_sleep();
+ add_wait_queue(&self->wq, &wait);
spin_lock_irqsave(&self->lock, flags);
while (1) {
set_current_state(TASK_UNINTERRUPTIBLE);
@@ -212,6 +216,7 @@ static void cw1200_spi_lock(struct hwbus_priv *self)
set_current_state(TASK_RUNNING);
self->claimed = 1;
spin_unlock_irqrestore(&self->lock, flags);
+ remove_wait_queue(&self->wq, &wait);
return;
}
@@ -223,6 +228,8 @@ static void cw1200_spi_unlock(struct hwbus_priv *self)
spin_lock_irqsave(&self->lock, flags);
self->claimed = 0;
spin_unlock_irqrestore(&self->lock, flags);
+ wake_up(&self->wq);
+
return;
}
@@ -413,6 +420,8 @@ static int cw1200_spi_probe(struct spi_device *func)
spi_set_drvdata(func, self);
+ init_waitqueue_head(&self->wq);
+
status = cw1200_spi_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_spi_hwbus_ops,
--
1.8.3.1
On Tue, Aug 27, 2013 at 08:29:46PM -0400, Solomon Peachy wrote:
> When we get an interrupt from the hardware, the first thing the driver does
> is tell the device to mask off the interrupt line. Unfortunately this
> involves a SPI transaction in interrupt context. Some (most?) SPI
> controllers perform the transfer asynchronously and try to sleep.
> This is bad, and triggers a BUG().
Did this patch series get dropped? I saw that the followup series
of minor cleanups were merged into wireless-next, but not these.
Since they didn't make it into 3.11, I intend to submit them to -stable,
but I need to make sure they at least make it into -next.
Thanks,
- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Delray Beach, FL ^^ (email/xmpp) ^^
Quidquid latine dictum sit, altum viditur.
On Tue, Sep 03, 2013 at 07:58:57AM -0400, Solomon Peachy wrote:
> On Tue, Aug 27, 2013 at 08:29:46PM -0400, Solomon Peachy wrote:
> > When we get an interrupt from the hardware, the first thing the driver does
> > is tell the device to mask off the interrupt line. Unfortunately this
> > involves a SPI transaction in interrupt context. Some (most?) SPI
> > controllers perform the transfer asynchronously and try to sleep.
> > This is bad, and triggers a BUG().
>
> Did this patch series get dropped? I saw that the followup series
> of minor cleanups were merged into wireless-next, but not these.
>
> Since they didn't make it into 3.11, I intend to submit them to -stable,
> but I need to make sure they at least make it into -next.
I'll be sending them for 3.12 soon...
--
John W. Linville Someday the world will need a hero, and you
[email protected] might be all we have. Be ready.
On Mon, Sep 09, 2013 at 02:33:50PM -0400, John W. Linville wrote:
> > Since they didn't make it into 3.11, I intend to submit them to -stable,
> > but I need to make sure they at least make it into -next.
>
> I'll be sending them for 3.12 soon...
Can you please revert this commit? (aec8e88c947b7017e2b4bbcb68a4bfc4a1f8ad35)
It ends up creating horrible interrupt losses.
I'll have a much simpler replacement posted shortly.
- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Delray Beach, FL ^^ (email/xmpp) ^^
Quidquid latine dictum sit, altum viditur.