From patchwork Tue Nov 30 08:42:13 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jaehoon Chung X-Patchwork-Id: 366461 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id oAU8kLAI021751 for ; Tue, 30 Nov 2010 08:46:21 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754804Ab0K3IpV (ORCPT ); Tue, 30 Nov 2010 03:45:21 -0500 Received: from mailout3.samsung.com ([203.254.224.33]:44432 "EHLO mailout3.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754796Ab0K3IpT (ORCPT ); Tue, 30 Nov 2010 03:45:19 -0500 Received: from epmmp2 (mailout3.samsung.com [203.254.224.33]) by mailout3.samsung.com (Oracle Communications Messaging Exchange Server 7u4-19.01 64bit (built Sep 7 2010)) with ESMTP id <0LCO00EJJW6DTT00@mailout3.samsung.com> for linux-mmc@vger.kernel.org; Tue, 30 Nov 2010 17:42:13 +0900 (KST) Received: from TNRNDGASPAPP1.tn.corp.samsungelectronics.net ([165.213.149.150]) by mmp2.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0LCO00169W6D7N@mmp2.samsung.com> for linux-mmc@vger.kernel.org; Tue, 30 Nov 2010 17:42:13 +0900 (KST) Received: from [10.89.53.154] ([10.89.53.154]) by TNRNDGASPAPP1.tn.corp.samsungelectronics.net with Microsoft SMTPSVC(6.0.3790.4675); Tue, 30 Nov 2010 17:42:13 +0900 Date: Tue, 30 Nov 2010 17:42:13 +0900 From: Jaehoon Chung Subject: [RFC] mmcoops with panic/oops To: "linux-mmc@vger.kernel.org" Cc: Chris Ball , Philip Rakity , Kyungmin Park , Andrew Morton , Matt Fleming Message-id: <4CF4B8E5.9070604@samsung.com> MIME-version: 1.0 Content-type: text/plain; charset=ISO-8859-1 Content-transfer-encoding: 7BIT User-Agent: Thunderbird 2.0.0.24 (X11/20100317) X-OriginalArrivalTime: 30 Nov 2010 08:42:13.0281 (UTC) FILETIME=[7C140110:01CB906A] Sender: linux-mmc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-mmc@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Tue, 30 Nov 2010 08:46:21 +0000 (UTC) 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +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 + +struct mmcoops_platform_data { + struct platform_device *pdev; + unsigned long start; + unsigned long size; +}; + +#endif