From: Adrian Hunter <adrian.hun...@intel.com>

Signed-off-by: Adrian Hunter <adrian.hun...@intel.com>
Signed-off-by: Irina Tirdea <irina.tir...@intel.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to