Commit aec8e88c authored by Solomon Peachy's avatar Solomon Peachy Committed by John W. Linville

cw1200: Don't perform SPI transfers in interrupt context

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: default avatarSolomon Peachy <pizza@shaftnet.org>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent e7d33bb5
...@@ -41,6 +41,7 @@ struct hwbus_priv { ...@@ -41,6 +41,7 @@ struct hwbus_priv {
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 */
int claimed; int claimed;
int irq_disabled;
}; };
#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2) #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) ...@@ -230,6 +231,8 @@ static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
struct hwbus_priv *self = dev_id; struct hwbus_priv *self = dev_id;
if (self->core) { if (self->core) {
disable_irq_nosync(self->func->irq);
self->irq_disabled = 1;
cw1200_irq_handler(self->core); cw1200_irq_handler(self->core);
return IRQ_HANDLED; return IRQ_HANDLED;
} else { } else {
...@@ -263,13 +266,22 @@ static int cw1200_spi_irq_subscribe(struct hwbus_priv *self) ...@@ -263,13 +266,22 @@ static int cw1200_spi_irq_subscribe(struct hwbus_priv *self)
static int cw1200_spi_irq_unsubscribe(struct hwbus_priv *self) static int cw1200_spi_irq_unsubscribe(struct hwbus_priv *self)
{ {
int ret = 0;
pr_debug("SW IRQ unsubscribe\n"); pr_debug("SW IRQ unsubscribe\n");
disable_irq_wake(self->func->irq); disable_irq_wake(self->func->irq);
free_irq(self->func->irq, self); 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) static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata)
...@@ -349,6 +361,7 @@ static struct hwbus_ops cw1200_spi_hwbus_ops = { ...@@ -349,6 +361,7 @@ static struct hwbus_ops cw1200_spi_hwbus_ops = {
.unlock = cw1200_spi_unlock, .unlock = cw1200_spi_unlock,
.align_size = cw1200_spi_align_size, .align_size = cw1200_spi_align_size,
.power_mgmt = cw1200_spi_pm, .power_mgmt = cw1200_spi_pm,
.irq_enable = cw1200_spi_irq_enable,
}; };
/* Probe Function to be called by SPI stack when device is discovered */ /* Probe Function to be called by SPI stack when device is discovered */
......
...@@ -485,7 +485,7 @@ int cw1200_load_firmware(struct cw1200_common *priv) ...@@ -485,7 +485,7 @@ int cw1200_load_firmware(struct cw1200_common *priv)
/* Enable interrupt signalling */ /* Enable interrupt signalling */
priv->hwbus_ops->lock(priv->hwbus_priv); 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); priv->hwbus_ops->unlock(priv->hwbus_priv);
if (ret < 0) if (ret < 0)
goto unsubscribe; goto unsubscribe;
......
...@@ -28,6 +28,7 @@ struct hwbus_ops { ...@@ -28,6 +28,7 @@ struct hwbus_ops {
void (*unlock)(struct hwbus_priv *self); void (*unlock)(struct hwbus_priv *self);
size_t (*align_size)(struct hwbus_priv *self, size_t size); size_t (*align_size)(struct hwbus_priv *self, size_t size);
int (*power_mgmt)(struct hwbus_priv *self, bool suspend); int (*power_mgmt)(struct hwbus_priv *self, bool suspend);
int (*irq_enable)(struct hwbus_priv *self, int enable);
}; };
#endif /* CW1200_HWBUS_H */ #endif /* CW1200_HWBUS_H */
...@@ -273,6 +273,21 @@ int __cw1200_irq_enable(struct cw1200_common *priv, int enable) ...@@ -273,6 +273,21 @@ int __cw1200_irq_enable(struct cw1200_common *priv, int enable)
u16 val16; u16 val16;
int ret; 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) { if (HIF_8601_SILICON == priv->hw_type) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) { if (ret < 0) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment