diff mbox

[RFC] mmcoops with panic/oops

Message ID 4CF4B8E5.9070604@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jaehoon Chung Nov. 30, 2010, 8:42 a.m. UTC
None
diff mbox

Patch

diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index 57e4416..02c9256 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -54,6 +54,12 @@  config SDIO_UART
 	help
 	  SDIO function driver for SDIO cards that implements the UART
 	  class, as well as the GPS class which appears like a UART.
+config MMC_OOPS
+	tristate "Log panic/oops to a MMC buffer"
+	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.
 
 config MMC_TEST
 	tristate "MMC host test driver"
diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
index c73b406..09ecd82 100644
--- a/drivers/mmc/card/Makefile
+++ b/drivers/mmc/card/Makefile
@@ -4,6 +4,7 @@ 
 
 obj-$(CONFIG_MMC_BLOCK)		+= mmc_block.o
 mmc_block-objs			:= block.o queue.o
+obj-$(CONFIG_MMC_OOPS)		+= mmc_oops.o
 obj-$(CONFIG_MMC_TEST)		+= mmc_test.o
 
 obj-$(CONFIG_SDIO_UART)		+= sdio_uart.o
diff --git a/drivers/mmc/card/mmc_oops.c b/drivers/mmc/card/mmc_oops.c
new file mode 100644
index 0000000..79b98f2
--- /dev/null
+++ b/drivers/mmc/card/mmc_oops.c
@@ -0,0 +1,270 @@ 
+/*
+ *  MMC Oops/Panic logger
+ *
+ *  Copyright (C) 2010 Samsung Electronics
+ *  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
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#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/mmcoops.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;
+	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_req(host, &mrq, 1);
+
+	if (cmd.error)
+		printk("%s[%d] cmd error %d\n", __func__, __LINE__, cmd.error);
+	if (data.error)
+		printk("%s[%d] data error %d\n", __func__, __LINE__, 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, const char *s1, unsigned long l1,
+		const char *s2, unsigned long l2)
+{
+	struct mmcoops_context *cxt = container_of(dumper,
+			struct mmcoops_context, dump);
+	struct mmc_card *card = cxt->card;
+	unsigned long s1_start, s2_start;
+	unsigned long l1_cpy, l2_cpy;
+	unsigned int count = 0;
+	char *buf;
+
+	if (!card)
+		return;
+
+	/* 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);
+
+	l2_cpy = min(l2, (unsigned long)(RECORD_SIZE - MMCOOPS_HEADER_SIZE));
+	l1_cpy = min(l1, (unsigned long)(RECORD_SIZE - MMCOOPS_HEADER_SIZE) - l2_cpy);
+
+	s2_start = l2 - l2_cpy;
+	s1_start = l1 - l1_cpy;
+
+	memcpy(buf + count, s1 + s1_start, l1_cpy);
+	memcpy(buf + count + l1_cpy, s2 + s2_start, l2_cpy);
+
+	mmc_panic_write(cxt, buf, cxt->start + (cxt->count * 8), cxt->size);
+}
+
+static int mmc_oops_probe(struct mmc_card *card)
+{
+	struct mmcoops_context *cxt = &oops_cxt;
+
+	if (!mmc_card_mmc(card) && !mmc_card_sd(card))
+		return -ENODEV;
+
+	cxt->card = card;
+	mmc_claim_host(card->host);
+
+	printk("%s[%d] %s\n", __func__, __LINE__, mmc_hostname(card->host));
+
+	return 0;
+}
+
+static void mmc_oops_remove(struct mmc_card *card)
+{
+	mmc_release_host(card->host);
+}
+
+/*
+ * 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] mmc_oops_probe[97] mmc0
+ */
+static struct mmc_driver mmc_driver = {
+	.drv		= {
+		.name	= "mmc_oops",
+	},
+	.probe		= mmc_oops_probe,
+	.remove		= mmc_oops_remove,
+};
+
+static int __init mmcoops_probe(struct platform_device *pdev)
+{
+	struct mmcoops_platform_data *pdata = pdev->dev.platform_data;
+	struct mmcoops_context *cxt = &oops_cxt;
+	int err = -EINVAL;
+
+	if (!pdata) {
+		dev_warn(&pdev->dev, "No platform data\n");
+		return -EINVAL;
+	}
+
+	cxt->start = pdata->start;
+	cxt->size = pdata->size;
+
+	/* FIXME how to get the given mmc deivce instead of bind/unbind */
+	err = mmc_register_driver(&mmc_driver);
+	if (err)
+		return err;
+
+	cxt->card = NULL;
+	cxt->count = 0;
+	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) {
+		printk(KERN_ERR "mmcoops: registering kmsg dumper failed");
+		goto kmsg_dump_register_failed;
+	}
+
+	return err;
+
+kmsg_dump_register_failed:
+	kfree(cxt->virt_addr);
+kmalloc_failed:
+	mmc_unregister_driver(&mmc_driver);
+
+	return err;
+}
+
+static int __exit mmcoops_remove(struct platform_device *pdev)
+{
+	struct mmcoops_context *cxt = &oops_cxt;
+
+	if (kmsg_dump_unregister(&cxt->dump) < 0)
+		printk(KERN_WARNING "mmcoops: colud not unregister kmsg dumper");
+	kfree(cxt->virt_addr);
+	mmc_unregister_driver(&mmc_driver);
+
+	return 0;
+}
+
+static struct platform_driver mmcoops_driver = {
+	.remove			= __exit_p(mmcoops_remove),
+	.driver			= {
+		.name		= "mmcoops",
+		.owner		= THIS_MODULE,
+	},
+};
+
+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("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("MMC Oops/Panic Looger");
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 8f86d70..aa32e4f 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -199,24 +199,57 @@  static void mmc_wait_done(struct mmc_request *mrq)
 }
 
 /**
- *	mmc_wait_for_req - start a request and wait for completion
+ *	__mmc_wait_for_req - start a request and wait for completion
  *	@host: MMC host to start command
  *	@mrq: MMC request to start
+ *	@panic: kernel panic/oops or not
  *
  *	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_req(struct mmc_host *host, struct mmc_request *mrq)
+void __mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq,
+		int panic)
 {
-	DECLARE_COMPLETION_ONSTACK(complete);
+	if (panic) {
+		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);
 
-	mrq->done_data = &complete;
-	mrq->done = mmc_wait_done;
+		mdelay(10);
+		spin_unlock_irq(&host->wq.lock);
+	} else {
+		DECLARE_COMPLETION_ONSTACK(complete);
+
+		mrq->done_data = &complete;
+		mrq->done = mmc_wait_done;
+
+		mmc_start_request(host, mrq);
+
+		wait_for_completion(&complete);
+	}
+}
 
-	mmc_start_request(host, mrq);
+EXPORT_SYMBOL(__mmc_wait_for_req);
 
-	wait_for_completion(&complete);
+/**
+ *	mmc_wait_for_req - start a 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_req(struct mmc_host *host, struct mmc_request *mrq)
+{
+	__mmc_wait_for_req(host, mrq, 0);
 }
 
 EXPORT_SYMBOL(mmc_wait_for_req);
diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
index 64e013f..7107344 100644
--- a/include/linux/mmc/core.h
+++ b/include/linux/mmc/core.h
@@ -131,6 +131,7 @@  struct mmc_request {
 struct mmc_host;
 struct mmc_card;
 
+extern void __mmc_wait_for_req(struct mmc_host *, struct mmc_request *, int);
 extern void mmc_wait_for_req(struct mmc_host *, struct mmc_request *);
 extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int);
 extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *,
diff --git a/include/linux/mmcoops.h b/include/linux/mmcoops.h
new file mode 100644
index 0000000..fa356eb
--- /dev/null
+++ b/include/linux/mmcoops.h
@@ -0,0 +1,12 @@ 
+#ifndef __MMCOOPS_H
+#define __MMCOOPS_H
+
+#include <linux/platform_device.h>
+
+struct mmcoops_platform_data {
+	struct platform_device	*pdev;
+	unsigned long		start;
+	unsigned long		size;
+};
+
+#endif