Commit 8047310e authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Chris Ball

mmc: sh_mmcif: fix a race, causing an Oops on SMP

Oopses have been observed on SMP in the sh-mmcif IRQ thread, when the two
IRQ threads run simultaneously on two CPUs. Also take care to guard the
timeout work and the DMA completion callback from possible NULL-pointer
dereferences and races.
Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent 99eb9d8d
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
#include <linux/mmc/sh_mmcif.h> #include <linux/mmc/sh_mmcif.h>
#include <linux/mmc/slot-gpio.h> #include <linux/mmc/slot-gpio.h>
#include <linux/mod_devicetable.h> #include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_qos.h> #include <linux/pm_qos.h>
...@@ -196,6 +197,7 @@ enum mmcif_state { ...@@ -196,6 +197,7 @@ enum mmcif_state {
STATE_IDLE, STATE_IDLE,
STATE_REQUEST, STATE_REQUEST,
STATE_IOS, STATE_IOS,
STATE_TIMEOUT,
}; };
enum mmcif_wait_for { enum mmcif_wait_for {
...@@ -232,6 +234,7 @@ struct sh_mmcif_host { ...@@ -232,6 +234,7 @@ struct sh_mmcif_host {
int sg_blkidx; int sg_blkidx;
bool power; bool power;
bool card_present; bool card_present;
struct mutex thread_lock;
/* DMA support */ /* DMA support */
struct dma_chan *chan_rx; struct dma_chan *chan_rx;
...@@ -255,11 +258,11 @@ static inline void sh_mmcif_bitclr(struct sh_mmcif_host *host, ...@@ -255,11 +258,11 @@ static inline void sh_mmcif_bitclr(struct sh_mmcif_host *host,
static void mmcif_dma_complete(void *arg) static void mmcif_dma_complete(void *arg)
{ {
struct sh_mmcif_host *host = arg; struct sh_mmcif_host *host = arg;
struct mmc_data *data = host->mrq->data; struct mmc_request *mrq = host->mrq;
dev_dbg(&host->pd->dev, "Command completed\n"); dev_dbg(&host->pd->dev, "Command completed\n");
if (WARN(!data, "%s: NULL data in DMA completion!\n", if (WARN(!mrq || !mrq->data, "%s: NULL data in DMA completion!\n",
dev_name(&host->pd->dev))) dev_name(&host->pd->dev)))
return; return;
...@@ -1113,11 +1116,21 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) ...@@ -1113,11 +1116,21 @@ static bool sh_mmcif_end_cmd(struct sh_mmcif_host *host)
static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id)
{ {
struct sh_mmcif_host *host = dev_id; struct sh_mmcif_host *host = dev_id;
struct mmc_request *mrq = host->mrq; struct mmc_request *mrq;
bool wait = false; bool wait = false;
cancel_delayed_work_sync(&host->timeout_work); cancel_delayed_work_sync(&host->timeout_work);
mutex_lock(&host->thread_lock);
mrq = host->mrq;
if (!mrq) {
dev_dbg(&host->pd->dev, "IRQ thread state %u, wait %u: NULL mrq!\n",
host->state, host->wait_for);
mutex_unlock(&host->thread_lock);
return IRQ_HANDLED;
}
/* /*
* All handlers return true, if processing continues, and false, if the * All handlers return true, if processing continues, and false, if the
* request has to be completed - successfully or not * request has to be completed - successfully or not
...@@ -1125,6 +1138,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) ...@@ -1125,6 +1138,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id)
switch (host->wait_for) { switch (host->wait_for) {
case MMCIF_WAIT_FOR_REQUEST: case MMCIF_WAIT_FOR_REQUEST:
/* We're too late, the timeout has already kicked in */ /* We're too late, the timeout has already kicked in */
mutex_unlock(&host->thread_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
case MMCIF_WAIT_FOR_CMD: case MMCIF_WAIT_FOR_CMD:
/* Wait for data? */ /* Wait for data? */
...@@ -1166,6 +1180,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) ...@@ -1166,6 +1180,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id)
if (wait) { if (wait) {
schedule_delayed_work(&host->timeout_work, host->timeout); schedule_delayed_work(&host->timeout_work, host->timeout);
/* Wait for more data */ /* Wait for more data */
mutex_unlock(&host->thread_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1179,6 +1194,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) ...@@ -1179,6 +1194,7 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id)
sh_mmcif_stop_cmd(host, mrq); sh_mmcif_stop_cmd(host, mrq);
if (!mrq->stop->error) { if (!mrq->stop->error) {
schedule_delayed_work(&host->timeout_work, host->timeout); schedule_delayed_work(&host->timeout_work, host->timeout);
mutex_unlock(&host->thread_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
} }
...@@ -1189,6 +1205,8 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) ...@@ -1189,6 +1205,8 @@ static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id)
host->mrq = NULL; host->mrq = NULL;
mmc_request_done(host->mmc, mrq); mmc_request_done(host->mmc, mrq);
mutex_unlock(&host->thread_lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1262,11 +1280,24 @@ static void mmcif_timeout_work(struct work_struct *work) ...@@ -1262,11 +1280,24 @@ static void mmcif_timeout_work(struct work_struct *work)
struct delayed_work *d = container_of(work, struct delayed_work, work); struct delayed_work *d = container_of(work, struct delayed_work, work);
struct sh_mmcif_host *host = container_of(d, struct sh_mmcif_host, timeout_work); struct sh_mmcif_host *host = container_of(d, struct sh_mmcif_host, timeout_work);
struct mmc_request *mrq = host->mrq; struct mmc_request *mrq = host->mrq;
unsigned long flags;
if (host->dying) if (host->dying)
/* Don't run after mmc_remove_host() */ /* Don't run after mmc_remove_host() */
return; return;
dev_dbg(&host->pd->dev, "Timeout waiting for %u, opcode %u\n",
host->wait_for, mrq->cmd->opcode);
spin_lock_irqsave(&host->lock, flags);
if (host->state == STATE_IDLE) {
spin_unlock_irqrestore(&host->lock, flags);
return;
}
host->state = STATE_TIMEOUT;
spin_unlock_irqrestore(&host->lock, flags);
/* /*
* Handle races with cancel_delayed_work(), unless * Handle races with cancel_delayed_work(), unless
* cancel_delayed_work_sync() is used * cancel_delayed_work_sync() is used
...@@ -1410,6 +1441,8 @@ static int sh_mmcif_probe(struct platform_device *pdev) ...@@ -1410,6 +1441,8 @@ static int sh_mmcif_probe(struct platform_device *pdev)
goto erqcd; goto erqcd;
} }
mutex_init(&host->thread_lock);
clk_disable(host->hclk); clk_disable(host->hclk);
ret = mmc_add_host(mmc); ret = mmc_add_host(mmc);
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