new file mode 100644
@@ -0,0 +1,23 @@
+* Mmcoops oops/panic logger
+
+Mmcoops is an oops/panic logger that write its logs to MMC before the system crashes.
+Introduction and concept are similar to other oops logger.
+(Refer to Documention/ramoops.txt)
+- Disadvantage: if MMC is occurred an oops/panic, this logger can't work.
+
+After reboot, you can get the last log with below command.
+#dd if=/dev/mmcblk0 of=/tmp/dump.log skip=64 count=16
+#vi dump.log
+
+Required Properties:
+
+* compatible: should be "mmcoops"
+* start-offset: block-offset for start.
+* size: the number of block to write oopses and panics.
+
+Example:
+ mmcoops {
+ compatible = "mmcoops";
+ start-offest = <64>; /* 64 * 512B = 32KiB offset */
+ size = <16>; /* 16 * 512B = 8KiB */
+ };
@@ -68,3 +68,11 @@ config MMC_TEST
This driver is only of interest to those developing or
testing a host driver. Most people should say N here.
+
+config MMC_OOPS
+ tristate "Log panic/oops to a MMC buffer"
+ depends on OF
+ help
+ This enables panic and oops messages to be logged to a circular
+ buffer in a MMC sectors where it can be read back at some
+ later point.
@@ -5,6 +5,7 @@
obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
mmc_block-objs := block.o queue.o
obj-$(CONFIG_MMC_TEST) += mmc_test.o
+obj-$(CONFIG_MMC_OOPS) += mmc_oops.o
obj-$(CONFIG_SDIO_UART) += sdio_uart.o
@@ -2447,6 +2447,10 @@ static int mmc_blk_probe(struct device *dev)
dev_set_drvdata(dev, md);
+#ifdef CONFIG_MMC_OOPS
+ if (mmc_card_mmc(card))
+ mmc_oops_card_set(card);
+#endif
if (mmc_add_disk(md))
goto out;
new file mode 100644
@@ -0,0 +1,304 @@
+/*
+ * MMC Oops/Panic logger
+ *
+ * Copyright (C) 2010-2015 Samsung Electronics
+ * Jaehoon Chung <jh80.chung@samsung.com>
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kmsg_dump.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 <linux/platform_device.h>
+#include <linux/of.h>
+
+/* TODO Unify the oops header, mmtoops, ramoops, mmcoops */
+#define MMCOOPS_KERNMSG_HDR "===="
+#define MMCOOPS_HEADER_SIZE (5 + sizeof(struct timeval))
+
+#define RECORD_SIZE 4096
+
+static int dump_oops = 1;
+module_param(dump_oops, int, 0600);
+MODULE_PARM_DESC(dump_oops,
+ "set to 1 to dump oopses, 0 to only dump panics (default 1)");
+
+#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
+
+static struct mmcoops_context {
+ struct kmsg_dumper dump;
+ struct mmc_card *card;
+ unsigned long start;
+ unsigned long size;
+ struct device *dev;
+ struct platform_device *pdev;
+ int count;
+ int max_count;
+ void *virt_addr;
+} oops_cxt;
+
+static void mmc_panic_write(struct mmcoops_context *cxt,
+ char *buf, unsigned long start, unsigned int size)
+{
+ struct mmc_card *card = cxt->card;
+ struct mmc_host *host = card->host;
+ struct mmc_request mrq;
+ struct mmc_command cmd;
+ struct mmc_command stop;
+ struct mmc_data data;
+ struct scatterlist sg;
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ memset(&stop, 0, sizeof(struct mmc_command));
+ memset(&data, 0, sizeof(struct mmc_data));
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+ mrq.stop = &stop;
+
+ sg_init_one(&sg, buf, (size << 9));
+
+ if (size > 1)
+ cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
+ else
+ cmd.opcode = MMC_WRITE_BLOCK;
+ cmd.arg = start;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ if (size == 1)
+ mrq.stop = NULL;
+ else {
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+ }
+
+ data.blksz = 512;
+ data.blocks = size;
+ data.flags = MMC_DATA_WRITE;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ mmc_wait_for_oops_req(host, &mrq);
+
+ if (cmd.error)
+ pr_info("%s: cmd error %d\n", __func__, cmd.error);
+ if (data.error)
+ pr_info("%s: data error %d\n", __func__, data.error);
+ /* wait busy */
+
+ cxt->count = (cxt->count + 1) % cxt->max_count;
+}
+
+static void mmcoops_do_dump(struct kmsg_dumper *dumper,
+ enum kmsg_dump_reason reason)
+{
+ struct mmcoops_context *cxt = container_of(dumper,
+ struct mmcoops_context, dump);
+ struct mmc_card *card = cxt->card;
+ unsigned int count = 0;
+ char *buf;
+
+ if (!card)
+ return;
+
+ mmc_claim_host(card->host);
+
+ /* Only dump oopses if dump_oops is set */
+ if (reason == KMSG_DUMP_OOPS && !dump_oops)
+ return;
+
+ buf = (char *)(cxt->virt_addr + (cxt->count * RECORD_SIZE));
+ memset(buf, '\0', RECORD_SIZE);
+ count = sprintf(buf + count, "%s", MMCOOPS_KERNMSG_HDR);
+
+ kmsg_dump_get_buffer(dumper, true, buf + MMCOOPS_HEADER_SIZE,
+ RECORD_SIZE - MMCOOPS_HEADER_SIZE, NULL);
+
+ mmc_panic_write(cxt, buf, cxt->start + (cxt->count * 8), cxt->size);
+}
+
+int mmc_oops_card_set(struct mmc_card *card)
+{
+ struct mmcoops_context *cxt = &oops_cxt;
+
+ if (!mmc_card_mmc(card) && !mmc_card_sd(card))
+ return -ENODEV;
+
+ cxt->card = card;
+ pr_info("%s: %s\n", mmc_hostname(card->host), __func__);
+
+ return 0;
+}
+EXPORT_SYMBOL(mmc_oops_card_set);
+
+static int mmc_oops_probe(struct device *dev)
+{
+ int ret = 0;
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ ret = mmc_oops_card_set(card);
+ if (ret)
+ return ret;
+
+ mmc_claim_host(card->host);
+
+ return 0;
+}
+
+static int mmc_oops_remove(struct device *dev)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ mmc_release_host(card->host);
+
+ return 0;
+}
+
+/*
+ * You can always switch between mmc_test and mmc_block by
+ * unbinding / binding e.g.
+ *
+ *
+ * # ls -al /sys/bus/mmc/drivers/mmcblk
+ * drwxr-xr-x 2 root 0 0 Jan 1 00:00 .
+ * drwxr-xr-x 4 root 0 0 Jan 1 00:00 ..
+ * --w------- 1 root 0 4096 Jan 1 00:01 bind
+ * lrwxrwxrwx 1 root 0 0 Jan 1 00:01
+ * mmc0:0001 -> ../../../../class/mmc_host/mmc0/mmc0:0001
+ * --w------- 1 root 0 4096 Jan 1 00:01 uevent
+ * --w------- 1 root 0 4096 Jan 1 00:01 unbind
+ *
+ * # echo mmc0:0001 > /sys/bus/mmc/drivers/mmcblk/unbind
+ *
+ * # echo mmc0:0001 > /sys/bus/mmc/drivers/mmc_oops/bind
+ * [ 48.490814] mmc0: mmc_oops_card_set
+ */
+static struct device_driver mmc_driver = {
+ .name = "mmc_oops",
+ .probe = mmc_oops_probe,
+ .remove = mmc_oops_remove,
+};
+
+/* Parsing dt node */
+static int mmcoops_parse_dt(struct mmcoops_context *cxt)
+{
+ struct device_node *np = cxt->dev->of_node;
+ u32 start_offset = 0;
+ u32 size = 0;
+ int ret = 0;
+
+ ret = of_property_read_u32(np, "start-offset", &start_offset);
+ if (ret) {
+ pr_err("%s: Start offset can't set..\n", __func__);
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "size", &size);
+ if (ret) {
+ pr_err("%s: Size can't set..\n", __func__);
+ return ret;
+ }
+
+ cxt->start = start_offset;
+ cxt->size = size;
+
+ return 0;
+}
+
+static int __init mmcoops_probe(struct platform_device *pdev)
+{
+ struct mmcoops_context *cxt = &oops_cxt;
+ int err = -EINVAL;
+
+ err = mmc_register_driver(&mmc_driver);
+ if (err)
+ return err;
+
+ cxt->card = NULL;
+ cxt->count = 0;
+ cxt->dev = &pdev->dev;
+
+ err = mmcoops_parse_dt(cxt);
+ if (err) {
+ pr_err("mmcoops: parsing mmcoops property failed");
+ return err;
+ }
+
+
+ cxt->max_count = (cxt->size << 9) / RECORD_SIZE;
+
+ cxt->virt_addr = kmalloc((cxt->size << 9), GFP_KERNEL);
+ if (!cxt->virt_addr)
+ goto kmalloc_failed;
+
+ cxt->dump.dump = mmcoops_do_dump;
+
+ err = kmsg_dump_register(&cxt->dump);
+ if (err) {
+ pr_err("mmcoops: registering kmsg dumper failed");
+ goto kmsg_dump_register_failed;
+ }
+
+ pr_info("mmcoops is probed\n");
+ return err;
+
+kmsg_dump_register_failed:
+ kfree(cxt->virt_addr);
+kmalloc_failed:
+ mmc_unregister_driver(&mmc_driver);
+
+ return err;
+}
+
+static int mmcoops_remove(struct platform_device *pdev)
+{
+ struct mmcoops_context *cxt = &oops_cxt;
+
+ if (kmsg_dump_unregister(&cxt->dump) < 0)
+ pr_warn("mmcoops: colud not unregister kmsg dumper");
+ kfree(cxt->virt_addr);
+ mmc_unregister_driver(&mmc_driver);
+
+ return 0;
+}
+
+static const struct of_device_id mmcoops_match[] = {
+ { .compatible = "mmcoops", },
+};
+
+static struct platform_driver mmcoops_driver = {
+ .remove = mmcoops_remove,
+ .driver = {
+ .name = "mmcoops",
+ .of_match_table = mmcoops_match,
+ },
+};
+
+static int __init mmcoops_init(void)
+{
+ return platform_driver_probe(&mmcoops_driver, mmcoops_probe);
+}
+
+static void __exit mmcoops_exit(void)
+{
+ platform_driver_unregister(&mmcoops_driver);
+}
+
+module_init(mmcoops_init);
+module_exit(mmcoops_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jaehoon Chung <jh80.chung@samsung.com>");
+MODULE_DESCRIPTION("MMC Oops/Panic Looger");
@@ -610,6 +610,33 @@ void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
}
EXPORT_SYMBOL(mmc_wait_for_req);
+#ifdef CONFIG_MMC_OOPS
+/**
+ * mmc_wait_for_oops_req - start a oops request and wait for completion
+ * @host: MMC host to start command
+ * @mrq: MMC request to start
+ *
+ * Start a new MMC custom command request for a host, and wait
+ * for the command to complete. Does not attempt to parse the
+ * response.
+ */
+void mmc_wait_for_oops_req(struct mmc_host *host, struct mmc_request *mrq)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ mmc_start_request(host, mrq);
+
+ spin_lock_irq(&host->wq.lock);
+ init_waitqueue_head(&host->wq);
+
+ add_wait_queue_exclusive(&host->wq, &wait);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+
+ mdelay(10);
+ spin_unlock_irq(&host->wq.lock);
+}
+#endif
+
/**
* mmc_interrupt_hpi - Issue for High priority Interrupt
* @card: the MMC card associated with the HPI transfer
@@ -517,5 +517,8 @@ extern void mmc_unregister_driver(struct device_driver *);
extern void mmc_fixup_device(struct mmc_card *card,
const struct mmc_fixup *table);
+#ifdef CONFIG_MMC_OOPS
+int mmc_oops_card_set(struct mmc_card *card);
+#endif /* CONFIG_MMC_OOPS */
#endif /* LINUX_MMC_CARD_H */
@@ -146,6 +146,9 @@ extern struct mmc_async_req *mmc_start_req(struct mmc_host *,
struct mmc_async_req *, int *);
extern int mmc_interrupt_hpi(struct mmc_card *);
extern void mmc_wait_for_req(struct mmc_host *, struct mmc_request *);
+#ifdef CONFIG_MMC_OOPS
+extern void mmc_wait_for_oops_req(struct mmc_host *, struct mmc_request *);
+#endif /* CONFIG_MMC_OOPS */
extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int);
extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *);
extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *,