Skip to content

Commit

Permalink
mmc: Support kmsg dumper based on pstore/blk
Browse files Browse the repository at this point in the history
This patch introduces to mmcpstore. The functioning of mmcpstore
is similar to mtdpstore. mmcpstore works on FTL based flash devices
whereas mtdpstore works on raw flash devices. When the system crashes,
mmcpstore stores the kmsg panic and oops logs to a user specified
MMC device.

It collects the details about the host MMC device through pstore/blk
"blkdev" parameter. The user can specify the MMC device in many ways
by checking in Documentation/admin-guide/pstore-blk.rst.

The individual mmc host drivers have to define suitable polling and
cleanup subroutines to write kmsg panic/oops logs through mmcpstore.
These new host operations are needed as pstore panic write runs with
interrupts disabled.

Signed-off-by: Bhaskara Budiredla <bbudiredla@marvell.com>
  • Loading branch information
Bhaskara Budiredla authored and intel-lab-lkp committed Jan 20, 2021
1 parent 45dfb8a commit 69795d6
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 1 deletion.
14 changes: 13 additions & 1 deletion drivers/mmc/core/Kconfig
Expand Up @@ -34,9 +34,22 @@ config PWRSEQ_SIMPLE
This driver can also be built as a module. If so, the module
will be called pwrseq_simple.

config MMC_PSTORE_BACKEND
bool "Log panic/oops to a MMC buffer"
depends on MMC_BLOCK
help
This option will let you create platform backend to store kmsg
crash dumps to a user specified MMC device. This is primarily
based on pstore/blk.

config MMC_PSTORE
tristate
select PSTORE_BLK

config MMC_BLOCK
tristate "MMC block device driver"
depends on BLOCK
select MMC_PSTORE if MMC_PSTORE_BACKEND=y
default y
help
Say Y here to enable the MMC block device driver support.
Expand Down Expand Up @@ -80,4 +93,3 @@ config MMC_TEST

This driver is only of interest to those developing or
testing a host driver. Most people should say N here.

1 change: 1 addition & 0 deletions drivers/mmc/core/Makefile
Expand Up @@ -16,5 +16,6 @@ obj-$(CONFIG_PWRSEQ_EMMC) += pwrseq_emmc.o
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
mmc_block-objs := block.o queue.o
mmc_block-$(CONFIG_MMC_PSTORE) += mmcpstore.o
obj-$(CONFIG_MMC_TEST) += mmc_test.o
obj-$(CONFIG_SDIO_UART) += sdio_uart.o
19 changes: 19 additions & 0 deletions drivers/mmc/core/block.c
Expand Up @@ -2870,6 +2870,21 @@ static void mmc_blk_remove_debugfs(struct mmc_card *card,

#endif /* CONFIG_DEBUG_FS */

#if IS_ENABLED(CONFIG_MMC_PSTORE)
sector_t mmc_blk_get_part(struct mmc_card *card, int part_num, sector_t *size)
{
struct mmc_blk_data *md = dev_get_drvdata(&card->dev);
struct gendisk *disk = md->disk;
struct disk_part_tbl *part_tbl = disk->part_tbl;

if (part_num < 0 || part_num >= part_tbl->len)
return 0;

*size = part_tbl->part[part_num]->nr_sects << SECTOR_SHIFT;
return part_tbl->part[part_num]->start_sect;
}
#endif

static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
Expand Down Expand Up @@ -2913,6 +2928,9 @@ static int mmc_blk_probe(struct mmc_card *card)
goto out;
}

if (mmc_card_mmc(card) || mmc_card_sd(card))
mmcpstore_card_set(card, md->disk->disk_name);

/* Add two debugfs entries */
mmc_blk_add_debugfs(card, md);

Expand Down Expand Up @@ -3060,6 +3078,7 @@ static void __exit mmc_blk_exit(void)
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES);
bus_unregister(&mmc_rpmb_bus_type);
unregister_mmcpstore();
}

module_init(mmc_blk_init);
Expand Down
9 changes: 9 additions & 0 deletions drivers/mmc/core/block.h
Expand Up @@ -16,5 +16,14 @@ void mmc_blk_mq_recovery(struct mmc_queue *mq);
struct work_struct;

void mmc_blk_mq_complete_work(struct work_struct *work);
#if IS_ENABLED(CONFIG_MMC_PSTORE)
sector_t mmc_blk_get_part(struct mmc_card *card, int part_num, sector_t *size);
void mmcpstore_card_set(struct mmc_card *card, const char *disk_name);
void unregister_mmcpstore(void);
#else
static inline void mmcpstore_card_set(struct mmc_card *card,
const char *disk_name) {}
static inline void unregister_mmcpstore(void) {}
#endif

#endif
44 changes: 44 additions & 0 deletions drivers/mmc/core/core.c
Expand Up @@ -569,6 +569,30 @@ int mmc_cqe_recovery(struct mmc_host *host)
}
EXPORT_SYMBOL(mmc_cqe_recovery);

#if IS_ENABLED(CONFIG_MMC_PSTORE)
/**
* mmc_wait_for_pstore_req - initiate a blocking mmc request
* @host: MMC host to start command
* @mrq: MMC request to start
*
* Start a blocking MMC request for a host and wait for the request
* to complete that is based on polling and timeout.
*/
void mmc_wait_for_pstore_req(struct mmc_host *host, struct mmc_request *mrq)
{
unsigned int timeout;

host->ops->req_cleanup_pending(host);
mmc_start_request(host, mrq);

if (mrq->data) {
timeout = mrq->data->timeout_ns / NSEC_PER_MSEC;
host->ops->req_completion_poll(host, timeout);
}
}
EXPORT_SYMBOL(mmc_wait_for_pstore_req);
#endif

/**
* mmc_is_req_done - Determine if a 'cap_cmd_during_tfr' request is done
* @host: MMC host
Expand Down Expand Up @@ -817,6 +841,26 @@ int __mmc_claim_host(struct mmc_host *host, struct mmc_ctx *ctx,
}
EXPORT_SYMBOL(__mmc_claim_host);

#if IS_ENABLED(CONFIG_MMC_PSTORE)
/**
* mmc_claim_host_async - claim host in atomic context
* @host: mmc host to claim
*
* This routine may be called in panic/oops scenarios.
* Return zero with host claim success, else busy status.
*/
int mmc_claim_host_async(struct mmc_host *host)
{
if (!host->claimed && pm_runtime_active(mmc_dev(host))) {
host->claimed = 1;
return 0;
}

return -EBUSY;
}
EXPORT_SYMBOL(mmc_claim_host_async);
#endif

/**
* mmc_release_host - release a host
* @host: mmc host to release
Expand Down
227 changes: 227 additions & 0 deletions drivers/mmc/core/mmcpstore.c
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MMC pstore support based on pstore/blk
*
* Copyright (c) 2020 Marvell.
* Author: Bhaskara Budiredla <bbudiredla@marvell.com>
*/

#define pr_fmt(fmt) "mmcpstore: " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pstore_blk.h>
#include <linux/blkdev.h>
#include <linux/mount.h>
#include <linux/slab.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/scatterlist.h>
#include "block.h"
#include "card.h"
#include "core.h"

static struct mmcpstore_context {
char dev_name[BDEVNAME_SIZE];
int partno;
sector_t start_sect;
sector_t size;
struct pstore_blk_config conf;
struct pstore_blk_info info;

struct mmc_card *card;
struct mmc_request *mrq;
} oops_cxt;

static void mmc_prep_req(struct mmc_request *mrq,
unsigned int sect_offset, unsigned int nsects,
struct scatterlist *sg, u32 opcode, unsigned int flags)
{
mrq->cmd->opcode = opcode;
mrq->cmd->arg = sect_offset;
mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;

if (nsects == 1) {
mrq->stop = NULL;
} else {
mrq->stop->opcode = MMC_STOP_TRANSMISSION;
mrq->stop->arg = 0;
mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
}

mrq->data->blksz = SECTOR_SIZE;
mrq->data->blocks = nsects;
mrq->data->flags = flags;
mrq->data->sg = sg;
mrq->data->sg_len = 1;
}

static int mmcpstore_panic_write_req(const char *buf,
unsigned int nsects, unsigned int sect_offset)
{
struct mmcpstore_context *cxt = &oops_cxt;
struct mmc_request *mrq = cxt->mrq;
struct mmc_card *card = cxt->card;
struct mmc_host *host = card->host;
struct scatterlist sg;
u32 opcode;
int ret;

opcode = (nsects > 1) ? MMC_WRITE_MULTIPLE_BLOCK : MMC_WRITE_BLOCK;
mmc_prep_req(mrq, sect_offset, nsects, &sg, opcode, MMC_DATA_WRITE);
sg_init_one(&sg, buf, (nsects << SECTOR_SHIFT));
mmc_set_data_timeout(mrq->data, cxt->card);

ret = mmc_claim_host_async(host);
if (ret)
return ret;

mmc_wait_for_pstore_req(host, mrq);
return 0;
}

static int mmcpstore_panic_write(const char *buf, sector_t off, sector_t sects)
{
struct mmcpstore_context *cxt = &oops_cxt;
int ret;

ret = mmcpstore_panic_write_req(buf, sects, cxt->start_sect + off);
if (ret)
return ret;

return 0;
}

static struct block_device *mmcpstore_open_backend(const char *device)
{
struct block_device *bdev;
dev_t devt;

bdev = blkdev_get_by_path(device, FMODE_READ, NULL);
if (IS_ERR(bdev)) {
devt = name_to_dev_t(device);
if (devt == 0)
return ERR_PTR(-ENODEV);

bdev = blkdev_get_by_dev(devt, FMODE_READ, NULL);
if (IS_ERR(bdev))
return bdev;
}

return bdev;
}

static void mmcpstore_close_backend(struct block_device *bdev)
{
if (!bdev)
return;
blkdev_put(bdev, FMODE_READ);
}

void mmcpstore_card_set(struct mmc_card *card, const char *disk_name)
{
struct mmcpstore_context *cxt = &oops_cxt;
struct pstore_blk_config *conf = &cxt->conf;
struct pstore_blk_info *info = &cxt->info;
struct block_device *bdev;
struct mmc_command *stop;
struct mmc_command *cmd;
struct mmc_request *mrq;
struct mmc_data *data;
int ret;

ret = pstore_blk_get_config(conf);
if (!conf->device[0]) {
pr_debug("psblk backend is empty\n");
return;
}

/* Multiple backend devices not allowed */
if (cxt->dev_name[0])
return;

bdev = mmcpstore_open_backend(conf->device);
if (IS_ERR(bdev)) {
pr_err("%s failed to open with %ld\n",
conf->device, PTR_ERR(bdev));
return;
}

bdevname(bdev, cxt->dev_name);
cxt->partno = bdev->bd_part->partno;
mmcpstore_close_backend(bdev);

if (strncmp(cxt->dev_name, disk_name, strlen(disk_name)))
return;

cxt->start_sect = mmc_blk_get_part(card, cxt->partno, &cxt->size);
if (!cxt->start_sect) {
pr_err("Non-existent partition %d selected\n", cxt->partno);
return;
}

/* Check for host mmc panic write polling function definitions */
if (!card->host->ops->req_cleanup_pending ||
!card->host->ops->req_completion_poll)
return;

cxt->card = card;

mrq = kzalloc(sizeof(struct mmc_request), GFP_KERNEL);
if (!mrq)
goto out;

cmd = kzalloc(sizeof(struct mmc_command), GFP_KERNEL);
if (!cmd)
goto free_mrq;

stop = kzalloc(sizeof(struct mmc_command), GFP_KERNEL);
if (!stop)
goto free_cmd;

data = kzalloc(sizeof(struct mmc_data), GFP_KERNEL);
if (!data)
goto free_stop;

mrq->cmd = cmd;
mrq->data = data;
mrq->stop = stop;
cxt->mrq = mrq;

info->major = MMC_BLOCK_MAJOR;
info->flags = PSTORE_FLAGS_DMESG;
info->panic_write = mmcpstore_panic_write;
ret = register_pstore_blk(info);
if (ret) {
pr_err("%s registering with psblk failed (%d)\n",
cxt->dev_name, ret);
goto free_data;
}

pr_info("%s registered as psblk backend\n", cxt->dev_name);
return;

free_data:
kfree(data);
free_stop:
kfree(stop);
free_cmd:
kfree(cmd);
free_mrq:
kfree(mrq);
out:
return;
}

void unregister_mmcpstore(void)
{
struct mmcpstore_context *cxt = &oops_cxt;

unregister_pstore_blk(MMC_BLOCK_MAJOR);
kfree(cxt->mrq->data);
kfree(cxt->mrq->stop);
kfree(cxt->mrq->cmd);
kfree(cxt->mrq);
cxt->card = NULL;
}
5 changes: 5 additions & 0 deletions include/linux/mmc/core.h
Expand Up @@ -166,6 +166,11 @@ struct mmc_request {

struct mmc_card;

#if IS_ENABLED(CONFIG_MMC_PSTORE)
void mmc_wait_for_pstore_req(struct mmc_host *host, struct mmc_request *mrq);
int mmc_claim_host_async(struct mmc_host *host);
#endif

void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq);
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd,
int retries);
Expand Down

0 comments on commit 69795d6

Please sign in to comment.