Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b0beafc authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Chris Ball
Browse files

mmc: sh_mmcif: protect against a theoretical race



The MMC subsystem does not guarantee that host driver .request() and
.set_ios() callbacks are serialised. Such concurrent calls, however,
do not have to be meaningfully supported, drivers just have to make
sure to avoid any severe problems.

Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Cc: Simon Horman <horms@verge.net.au>
Cc: Magnus Damm <damm@opensource.se>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent 06e8935f
Loading
Loading
Loading
Loading
+42 −8
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include <linux/mmc/sh_mmcif.h>
#include <linux/pagemap.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>

#define DRIVER_NAME	"sh_mmcif"
#define DRIVER_VERSION	"2010-04-28"
@@ -153,6 +154,12 @@
#define CLKDEV_MMC_DATA		20000000 /* 20MHz */
#define CLKDEV_INIT		400000   /* 400 KHz */

enum mmcif_state {
	STATE_IDLE,
	STATE_REQUEST,
	STATE_IOS,
};

struct sh_mmcif_host {
	struct mmc_host *mmc;
	struct mmc_data *data;
@@ -164,6 +171,8 @@ struct sh_mmcif_host {
	long timeout;
	void __iomem *addr;
	struct completion intr_wait;
	enum mmcif_state state;
	spinlock_t lock;

	/* DMA support */
	struct dma_chan		*chan_rx;
@@ -798,17 +807,31 @@ static void sh_mmcif_stop_cmd(struct sh_mmcif_host *host,
static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
	struct sh_mmcif_host *host = mmc_priv(mmc);
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);
	if (host->state != STATE_IDLE) {
		spin_unlock_irqrestore(&host->lock, flags);
		mrq->cmd->error = -EAGAIN;
		mmc_request_done(mmc, mrq);
		return;
	}

	host->state = STATE_REQUEST;
	spin_unlock_irqrestore(&host->lock, flags);

	switch (mrq->cmd->opcode) {
	/* MMCIF does not support SD/SDIO command */
	case SD_IO_SEND_OP_COND:
	case MMC_APP_CMD:
		host->state = STATE_IDLE;
		mrq->cmd->error = -ETIMEDOUT;
		mmc_request_done(mmc, mrq);
		return;
	case MMC_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */
		if (!mrq->data) {
			/* send_if_cond cmd (not support) */
			host->state = STATE_IDLE;
			mrq->cmd->error = -ETIMEDOUT;
			mmc_request_done(mmc, mrq);
			return;
@@ -830,12 +853,9 @@ static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
	sh_mmcif_start_cmd(host, mrq, mrq->cmd);
	host->data = NULL;

	if (mrq->cmd->error != 0) {
		mmc_request_done(mmc, mrq);
		return;
	}
	if (mrq->stop)
	if (!mrq->cmd->error && mrq->stop)
		sh_mmcif_stop_cmd(host, mrq, mrq->stop);
	host->state = STATE_IDLE;
	mmc_request_done(mmc, mrq);
}

@@ -843,6 +863,16 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
	struct sh_mmcif_host *host = mmc_priv(mmc);
	struct sh_mmcif_plat_data *p = host->pd->dev.platform_data;
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);
	if (host->state != STATE_IDLE) {
		spin_unlock_irqrestore(&host->lock, flags);
		return;
	}

	host->state = STATE_IOS;
	spin_unlock_irqrestore(&host->lock, flags);

	if (ios->power_mode == MMC_POWER_UP) {
		if (p->set_pwr)
@@ -852,6 +882,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
		sh_mmcif_clock_control(host, 0);
		if (ios->power_mode == MMC_POWER_OFF && p->down_pwr)
			p->down_pwr(host->pd);
		host->state = STATE_IDLE;
		return;
	}

@@ -859,6 +890,7 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
		sh_mmcif_clock_control(host, ios->clock);

	host->bus_width = ios->bus_width;
	host->state = STATE_IDLE;
}

static int sh_mmcif_get_cd(struct mmc_host *mmc)
@@ -996,6 +1028,7 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
	host->pd = pdev;

	init_completion(&host->intr_wait);
	spin_lock_init(&host->lock);

	mmc->ops = &sh_mmcif_ops;
	mmc->f_max = host->clk;
@@ -1025,6 +1058,8 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)

	mmc_add_host(mmc);

	sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);

	ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
	if (ret) {
		dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n");
@@ -1037,7 +1072,6 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
		goto clean_up2;
	}

	sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
	sh_mmcif_detect(host->mmc);

	dev_info(&pdev->dev, "driver version %s\n", DRIVER_VERSION);
@@ -1063,11 +1097,11 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
	mmc_remove_host(host->mmc);
	sh_mmcif_release_dma(host);

	sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);

	if (host->addr)
		iounmap(host->addr);

	sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);

	irq[0] = platform_get_irq(pdev, 0);
	irq[1] = platform_get_irq(pdev, 1);