[PATCH 06/26] mmc: block: add panic write support

From: Irina Tirdea
Date: Tue Oct 23 2012 - 09:48:19 EST


From: Adrian Hunter <adrian.hunter@xxxxxxxxx>

Signed-off-by: Adrian Hunter <adrian.hunter@xxxxxxxxx>
Signed-off-by: Irina Tirdea <irina.tirdea@xxxxxxxxx>
---
drivers/mmc/card/Kconfig | 11 ++
drivers/mmc/card/block.c | 257 +++++++++++++++++++++++++++++++++++++++++++++-
include/linux/mmc/host.h | 92 +++++++++++++++++
3 files changed, 359 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index 3b1f783..efc49e1 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -50,6 +50,17 @@ config MMC_BLOCK_BOUNCE

If unsure, say Y here.

+config MMC_BLOCK_PANIC_WRITE
+ bool "Panic write support"
+ depends on MMC_BLOCK
+ select BLK_DEV_PANIC_WRITE
+ default n
+ help
+ Say Y here to support panic write. Panic write enables upper
+ layers to write to MMC/SD cards during an oops or panic.
+ However a host controller driver that supports panic writes
+ is also needed.
+
config SDIO_UART
tristate "SDIO UART/GPS class support"
help
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index 172a768..e18ce4a 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -108,6 +108,11 @@ struct mmc_blk_data {
struct device_attribute force_ro;
struct device_attribute power_ro_lock;
int area_type;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ int panic;
+ struct scatterlist *panic_sg;
+ struct mmc_blk_request *panic_brq;
+#endif
};

static DEFINE_MUTEX(open_lock);
@@ -517,7 +522,7 @@ static const struct block_device_operations mmc_bdops = {
#endif
};

-static inline int mmc_blk_part_switch(struct mmc_card *card,
+static int mmc_blk_part_switch(struct mmc_card *card,
struct mmc_blk_data *md)
{
int ret;
@@ -1432,6 +1437,253 @@ out:
return ret;
}

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+static int mmc_blk_panic_init_cleanup(struct block_device *bdev, int init)
+{
+ struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
+ struct mmc_card *card;
+ int err = 0;
+
+ if (!md)
+ return -ENODEV;
+
+ card = md->queue.card;
+ if (!card) {
+ err = -ENODEV;
+ goto out_put;
+ }
+
+ mmc_claim_host(card->host);
+
+ if (!init) {
+ mmc_panic_cleanup_host(card->host);
+ goto out_free_brq;
+ }
+
+ md->panic_sg = kmalloc(sizeof(struct scatterlist), GFP_KERNEL);
+ if (!md->panic_sg) {
+ err = -ENOMEM;
+ goto out_release;
+ }
+ sg_init_table(md->panic_sg, 1);
+
+ md->panic_brq = kmalloc(sizeof(struct mmc_blk_request), GFP_KERNEL);
+ if (!md->panic_brq) {
+ err = -ENOMEM;
+ goto out_free_sg;
+ }
+
+ err = mmc_panic_init_host(card->host);
+ if (err)
+ goto out_free_brq;
+
+ goto out_release;
+
+out_free_brq:
+ kfree(md->panic_brq);
+out_free_sg:
+ kfree(md->panic_sg);
+out_release:
+ mmc_release_host(card->host);
+out_put:
+ mmc_blk_put(md);
+ return err;
+}
+
+static int mmc_blk_panic_init(struct block_device *bdev)
+{
+ return mmc_blk_panic_init_cleanup(bdev, 1);
+}
+
+static void mmc_blk_panic_cleanup(struct block_device *bdev)
+{
+ mmc_blk_panic_init_cleanup(bdev, 0);
+}
+
+static int get_card_status(struct mmc_card *card, u32 *status, int retries);
+
+static int mmc_blk_panic_do_write(struct mmc_blk_data *md,
+ struct mmc_card *card, sector_t sect,
+ void *addr, unsigned long len)
+{
+ struct mmc_blk_request *brq = md->panic_brq;
+ unsigned int blocks = len >> 9;
+ unsigned int blksz = 512;
+
+ int err = 0;
+
+ sg_init_one(md->panic_sg, addr, len);
+
+ memset(brq, 0, sizeof(struct mmc_blk_request));
+
+ brq->mrq.cmd = &brq->cmd;
+ brq->mrq.data = &brq->data;
+ brq->mrq.stop = &brq->stop;
+
+ if (blocks > 1)
+ brq->mrq.cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
+ else
+ brq->mrq.cmd->opcode = MMC_WRITE_BLOCK;
+
+ brq->mrq.cmd->arg = sect;
+ if (!mmc_card_blockaddr(card))
+ brq->mrq.cmd->arg <<= 9;
+
+ brq->mrq.cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ if (blocks == 1)
+ brq->mrq.stop = NULL;
+ else {
+ brq->mrq.stop->opcode = MMC_STOP_TRANSMISSION;
+ brq->mrq.stop->arg = 0;
+ brq->mrq.stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
+ }
+
+ brq->mrq.data->blksz = blksz;
+ brq->mrq.data->blocks = blocks;
+ brq->mrq.data->flags = MMC_DATA_WRITE;
+ brq->mrq.data->sg = md->panic_sg;
+ brq->mrq.data->sg_len = 1;
+
+ mmc_set_data_timeout(brq->mrq.data, card);
+
+ mmc_wait_for_req(card->host, &brq->mrq);
+
+ if (brq->cmd.error)
+ err = brq->cmd.error;
+ else if (brq->stop.error)
+ err = brq->stop.error;
+ else if (brq->data.error)
+ err = brq->data.error;
+
+ if (!mmc_host_is_spi(card->host)) {
+ u32 status;
+
+ do {
+ err = get_card_status(card, &status, 5);
+ if (err)
+ break;
+ } while (!(status & R1_READY_FOR_DATA) ||
+ (R1_CURRENT_STATE(status) == R1_STATE_PRG));
+ }
+
+ return err;
+}
+
+static int mmc_blk_panic_reset(struct mmc_blk_data *md, struct mmc_host *host)
+{
+ struct mmc_blk_data *main_md = mmc_get_drvdata(host->card);
+ int err;
+
+ err = mmc_blk_reset(md, host, 0);
+ if (err != -EOPNOTSUPP)
+ goto out;
+
+ /* No hardware reset so try a software reset */
+ mmc_power_save_host(host);
+ mmc_power_restore_host(host);
+out:
+ /* Partition may have changed, force a switch */
+ main_md->part_curr = -1;
+
+ return err;
+}
+
+/*
+ * Tuning while panicing is not supported, so disable speeds that require it
+ * and reset.
+ */
+static int mmc_blk_panic_no_tuning(struct mmc_blk_data *md,
+ struct mmc_host *host)
+{
+ if (host->ocr & SD_OCR_S18R) {
+ host->caps &= ~(MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 |
+ MMC_CAP_UHS_DDR50);
+ host->ocr &= ~SD_OCR_S18R;
+ return mmc_blk_panic_reset(md, host);
+ }
+ return 0;
+}
+
+static int mmc_blk_panic_write(struct block_device *bdev, sector_t sect,
+ void *addr, unsigned long len)
+{
+ struct mmc_blk_data *md = bdev->bd_disk->private_data;
+ struct mmc_card *card;
+ size_t n;
+ int err = 0, err2;
+
+ if (!md)
+ return -ENODEV;
+ card = md->queue.card;
+ if (!card)
+ return -ENODEV;
+
+ card->host->panic_task = current;
+
+ if (!md->panic) {
+ u32 status;
+
+ md->panic = 1;
+ mmc_panic_begin_host(card->host);
+ mmc_blk_panic_no_tuning(md, card->host);
+ err2 = get_card_status(card, &status, 0);
+ if (err2 || !(status & R1_READY_FOR_DATA) ||
+ R1_CURRENT_STATE(status) != R1_STATE_TRAN)
+ mmc_blk_panic_reset(md, card->host);
+ mmc_set_blocklen(card, 512);
+ }
+
+ err = mmc_blk_part_switch(card, md);
+ if (err)
+ return err;
+
+ while (len) {
+ n = min_t(size_t, card->host->panic_max_size, len);
+ err2 = mmc_blk_panic_do_write(md, card, sect, addr, n);
+ if (err2 && !err)
+ err = err2;
+ len -= n;
+ addr += n;
+ sect += n >> 9;
+ }
+
+ return err;
+}
+
+static int mmc_blk_panic_flush(struct block_device *bdev)
+{
+ struct mmc_blk_data *md = bdev->bd_disk->private_data;
+ struct mmc_card *card;
+
+ if (!md)
+ return -ENODEV;
+ card = md->queue.card;
+ if (!card)
+ return -ENODEV;
+
+ if (md->panic) {
+ card->host->panic_task = current;
+ mmc_panic_end_host(card->host);
+ md->panic = 0;
+ }
+
+ card->host->panic_task = NULL;
+
+ return 0;
+}
+
+static const struct panic_write_operations mmc_pwops = {
+ .init = mmc_blk_panic_init,
+ .cleanup = mmc_blk_panic_cleanup,
+ .write = mmc_blk_panic_write,
+ .flush = mmc_blk_panic_flush,
+};
+
+#endif
+
static inline int mmc_blk_readonly(struct mmc_card *card)
{
return mmc_card_readonly(card) ||
@@ -1500,6 +1752,9 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
md->disk->major = MMC_BLOCK_MAJOR;
md->disk->first_minor = devidx * perdev_minors;
md->disk->fops = &mmc_bdops;
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ md->disk->pwops = &mmc_pwops;
+#endif
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
md->disk->driverfs_dev = parent;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 7abb0e1..4205d2d 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -13,6 +13,7 @@
#include <linux/leds.h>
#include <linux/mutex.h>
#include <linux/sched.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fault-inject.h>

@@ -138,6 +139,17 @@ struct mmc_host_ops {
void (*hw_reset)(struct mmc_host *host);
};

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+struct mmc_panic_ops {
+ int (*init)(struct mmc_host *host);
+ void (*cleanup)(struct mmc_host *host);
+ void (*begin)(struct mmc_host *host);
+ void (*end)(struct mmc_host *host);
+};
+
+#endif
+
struct mmc_card;
struct device;

@@ -257,6 +269,7 @@ struct mmc_host {
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
+#define MMC_CAP_PANIC_WRITE (1 << 12) /* Panic write support */

mmc_pm_flag_t pm_caps; /* supported pm features */

@@ -337,6 +350,12 @@ struct mmc_host {

unsigned int actual_clock; /* Actual HC clock rate */

+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+ struct task_struct *panic_task; /* task that is panic writing */
+ const struct mmc_panic_ops *pops; /* panic operations */
+ size_t panic_max_size; /* max data transfer size */
+#endif
+
unsigned long private[0] ____cacheline_aligned;
};

@@ -452,4 +471,77 @@ static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
return host->ios.clock;
}
#endif
+
+#ifdef CONFIG_MMC_BLOCK_PANIC_WRITE
+
+static inline int mmc_panic_task_active(struct mmc_host *host)
+{
+ return host->panic_task != NULL;
+}
+
+static inline int mmc_am_panic_task(struct mmc_host *host)
+{
+ return host->panic_task == current;
+}
+
+static inline int mmc_am_nonpanic_task(struct mmc_host *host)
+{
+ return host->panic_task && host->panic_task != current;
+}
+
+static inline void mmc_trap_nonpanic_tasks(struct mmc_host *host)
+{
+ while (host->panic_task && host->panic_task != current)
+ msleep(100);
+}
+
+static inline int mmc_panic_init_host(struct mmc_host *host)
+{
+ if (!(host->caps2 & MMC_CAP_PANIC_WRITE))
+ return -EOPNOTSUPP;
+ if (host->pops->init)
+ return host->pops->init(host);
+ return 0;
+}
+
+static inline void mmc_panic_cleanup_host(struct mmc_host *host)
+{
+ if (host->pops->cleanup)
+ host->pops->cleanup(host);
+}
+
+static inline void mmc_panic_begin_host(struct mmc_host *host)
+{
+if (host->pops->begin)
+ host->pops->begin(host);
+}
+
+static inline void mmc_panic_end_host(struct mmc_host *host)
+{
+ if (host->pops->end)
+ host->pops->end(host);
+}
+
+#else
+
+static inline int mmc_panic_task_active(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline int mmc_am_panic_task(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline int mmc_am_nonpanic_task(struct mmc_host *host)
+{
+ return 0;
+}
+
+static inline void mmc_trap_nonpanic_tasks(struct mmc_host *host)
+{
+}
+
+#endif
#endif /* LINUX_MMC_HOST_H */
--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/