From: Adrian Hunter <adrian.hunter@xxxxxxxxx> Define panic_write_operations for mmc block devices. Also define host mmc panic ops for controling panic dumping. 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..06c09a3 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. Panic writes are defined by mmc_panic_ops. + 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-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html