xref: /openbmc/linux/drivers/mtd/mtdoops.c (revision 9507b0c8)
14b23aff0SRichard Purdie /*
24b23aff0SRichard Purdie  * MTD Oops/Panic logger
34b23aff0SRichard Purdie  *
44b23aff0SRichard Purdie  * Copyright (C) 2007 Nokia Corporation. All rights reserved.
54b23aff0SRichard Purdie  *
64b23aff0SRichard Purdie  * Author: Richard Purdie <rpurdie@openedhand.com>
74b23aff0SRichard Purdie  *
84b23aff0SRichard Purdie  * This program is free software; you can redistribute it and/or
94b23aff0SRichard Purdie  * modify it under the terms of the GNU General Public License
104b23aff0SRichard Purdie  * version 2 as published by the Free Software Foundation.
114b23aff0SRichard Purdie  *
124b23aff0SRichard Purdie  * This program is distributed in the hope that it will be useful, but
134b23aff0SRichard Purdie  * WITHOUT ANY WARRANTY; without even the implied warranty of
144b23aff0SRichard Purdie  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
154b23aff0SRichard Purdie  * General Public License for more details.
164b23aff0SRichard Purdie  *
174b23aff0SRichard Purdie  * You should have received a copy of the GNU General Public License
184b23aff0SRichard Purdie  * along with this program; if not, write to the Free Software
194b23aff0SRichard Purdie  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
204b23aff0SRichard Purdie  * 02110-1301 USA
214b23aff0SRichard Purdie  *
224b23aff0SRichard Purdie  */
234b23aff0SRichard Purdie 
244b23aff0SRichard Purdie #include <linux/kernel.h>
254b23aff0SRichard Purdie #include <linux/module.h>
264b23aff0SRichard Purdie #include <linux/console.h>
274b23aff0SRichard Purdie #include <linux/vmalloc.h>
284b23aff0SRichard Purdie #include <linux/workqueue.h>
294b23aff0SRichard Purdie #include <linux/sched.h>
304b23aff0SRichard Purdie #include <linux/wait.h>
31621e4f8eSRichard Purdie #include <linux/delay.h>
3247c152b8SRichard Purdie #include <linux/spinlock.h>
33f9f7dd22SDavid Woodhouse #include <linux/interrupt.h>
344b23aff0SRichard Purdie #include <linux/mtd/mtd.h>
354b23aff0SRichard Purdie 
361114e3d0SSimon Kagstrom /* Maximum MTD partition size */
371114e3d0SSimon Kagstrom #define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024)
381114e3d0SSimon Kagstrom 
39f0482ee3SRichard Purdie #define MTDOOPS_KERNMSG_MAGIC 0x5d005d00
409507b0c8SSimon Kagstrom 
419507b0c8SSimon Kagstrom static unsigned long record_size = 4096;
429507b0c8SSimon Kagstrom module_param(record_size, ulong, 0400);
439507b0c8SSimon Kagstrom MODULE_PARM_DESC(record_size,
449507b0c8SSimon Kagstrom 		"record size for MTD OOPS pages in bytes (default 4096)");
454b23aff0SRichard Purdie 
467903cbabSAdrian Bunk static struct mtdoops_context {
474b23aff0SRichard Purdie 	int mtd_index;
486ce0a856SRichard Purdie 	struct work_struct work_erase;
496ce0a856SRichard Purdie 	struct work_struct work_write;
504b23aff0SRichard Purdie 	struct mtd_info *mtd;
514b23aff0SRichard Purdie 	int oops_pages;
524b23aff0SRichard Purdie 	int nextpage;
534b23aff0SRichard Purdie 	int nextcount;
54be95745fSSimon Kagstrom 	unsigned long *oops_page_used;
55e2a0f25bSAdrian Hunter 	char *name;
564b23aff0SRichard Purdie 
574b23aff0SRichard Purdie 	void *oops_buf;
5847c152b8SRichard Purdie 
5947c152b8SRichard Purdie 	/* writecount and disabling ready are spin lock protected */
6047c152b8SRichard Purdie 	spinlock_t writecount_lock;
614b23aff0SRichard Purdie 	int ready;
624b23aff0SRichard Purdie 	int writecount;
634b23aff0SRichard Purdie } oops_cxt;
644b23aff0SRichard Purdie 
65be95745fSSimon Kagstrom static void mark_page_used(struct mtdoops_context *cxt, int page)
66be95745fSSimon Kagstrom {
67be95745fSSimon Kagstrom 	set_bit(page, cxt->oops_page_used);
68be95745fSSimon Kagstrom }
69be95745fSSimon Kagstrom 
70be95745fSSimon Kagstrom static void mark_page_unused(struct mtdoops_context *cxt, int page)
71be95745fSSimon Kagstrom {
72be95745fSSimon Kagstrom 	clear_bit(page, cxt->oops_page_used);
73be95745fSSimon Kagstrom }
74be95745fSSimon Kagstrom 
75be95745fSSimon Kagstrom static int page_is_used(struct mtdoops_context *cxt, int page)
76be95745fSSimon Kagstrom {
77be95745fSSimon Kagstrom 	return test_bit(page, cxt->oops_page_used);
78be95745fSSimon Kagstrom }
79be95745fSSimon Kagstrom 
804b23aff0SRichard Purdie static void mtdoops_erase_callback(struct erase_info *done)
814b23aff0SRichard Purdie {
824b23aff0SRichard Purdie 	wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
834b23aff0SRichard Purdie 	wake_up(wait_q);
844b23aff0SRichard Purdie }
854b23aff0SRichard Purdie 
86be95745fSSimon Kagstrom static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset)
874b23aff0SRichard Purdie {
88be95745fSSimon Kagstrom 	struct mtd_info *mtd = cxt->mtd;
89be95745fSSimon Kagstrom 	u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize;
909507b0c8SSimon Kagstrom 	u32 start_page = start_page_offset / record_size;
919507b0c8SSimon Kagstrom 	u32 erase_pages = mtd->erasesize / record_size;
924b23aff0SRichard Purdie 	struct erase_info erase;
934b23aff0SRichard Purdie 	DECLARE_WAITQUEUE(wait, current);
944b23aff0SRichard Purdie 	wait_queue_head_t wait_q;
954b23aff0SRichard Purdie 	int ret;
96be95745fSSimon Kagstrom 	int page;
974b23aff0SRichard Purdie 
984b23aff0SRichard Purdie 	init_waitqueue_head(&wait_q);
994b23aff0SRichard Purdie 	erase.mtd = mtd;
1004b23aff0SRichard Purdie 	erase.callback = mtdoops_erase_callback;
1014b23aff0SRichard Purdie 	erase.addr = offset;
1024b23aff0SRichard Purdie 	erase.len = mtd->erasesize;
1034b23aff0SRichard Purdie 	erase.priv = (u_long)&wait_q;
1044b23aff0SRichard Purdie 
1054b23aff0SRichard Purdie 	set_current_state(TASK_INTERRUPTIBLE);
1064b23aff0SRichard Purdie 	add_wait_queue(&wait_q, &wait);
1074b23aff0SRichard Purdie 
1084b23aff0SRichard Purdie 	ret = mtd->erase(mtd, &erase);
1094b23aff0SRichard Purdie 	if (ret) {
1104b23aff0SRichard Purdie 		set_current_state(TASK_RUNNING);
1114b23aff0SRichard Purdie 		remove_wait_queue(&wait_q, &wait);
112a15b124fSArtem Bityutskiy 		printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n",
113a15b124fSArtem Bityutskiy 		       (unsigned long long)erase.addr,
114a15b124fSArtem Bityutskiy 		       (unsigned long long)erase.len, mtd->name);
1154b23aff0SRichard Purdie 		return ret;
1164b23aff0SRichard Purdie 	}
1174b23aff0SRichard Purdie 
1184b23aff0SRichard Purdie 	schedule();  /* Wait for erase to finish. */
1194b23aff0SRichard Purdie 	remove_wait_queue(&wait_q, &wait);
1204b23aff0SRichard Purdie 
121be95745fSSimon Kagstrom 	/* Mark pages as unused */
122be95745fSSimon Kagstrom 	for (page = start_page; page < start_page + erase_pages; page++)
123be95745fSSimon Kagstrom 		mark_page_unused(cxt, page);
124be95745fSSimon Kagstrom 
1254b23aff0SRichard Purdie 	return 0;
1264b23aff0SRichard Purdie }
1274b23aff0SRichard Purdie 
1286ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt)
1294b23aff0SRichard Purdie {
1304b23aff0SRichard Purdie 	cxt->nextpage++;
131ecd5b310SRichard Purdie 	if (cxt->nextpage >= cxt->oops_pages)
1324b23aff0SRichard Purdie 		cxt->nextpage = 0;
1334b23aff0SRichard Purdie 	cxt->nextcount++;
1344b23aff0SRichard Purdie 	if (cxt->nextcount == 0xffffffff)
1354b23aff0SRichard Purdie 		cxt->nextcount = 0;
1364b23aff0SRichard Purdie 
137be95745fSSimon Kagstrom 	if (page_is_used(cxt, cxt->nextpage)) {
1386ce0a856SRichard Purdie 		schedule_work(&cxt->work_erase);
1396ce0a856SRichard Purdie 		return;
1406ce0a856SRichard Purdie 	}
1414b23aff0SRichard Purdie 
142a15b124fSArtem Bityutskiy 	printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n",
1434b23aff0SRichard Purdie 	       cxt->nextpage, cxt->nextcount);
1444b23aff0SRichard Purdie 	cxt->ready = 1;
1454b23aff0SRichard Purdie }
1464b23aff0SRichard Purdie 
1476ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */
1486ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work)
1494b23aff0SRichard Purdie {
1506ce0a856SRichard Purdie 	struct mtdoops_context *cxt =
1516ce0a856SRichard Purdie 			container_of(work, struct mtdoops_context, work_erase);
1524b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
1534b23aff0SRichard Purdie 	int i = 0, j, ret, mod;
1544b23aff0SRichard Purdie 
1554b23aff0SRichard Purdie 	/* We were unregistered */
1564b23aff0SRichard Purdie 	if (!mtd)
1574b23aff0SRichard Purdie 		return;
1584b23aff0SRichard Purdie 
1599507b0c8SSimon Kagstrom 	mod = (cxt->nextpage * record_size) % mtd->erasesize;
1604b23aff0SRichard Purdie 	if (mod != 0) {
1619507b0c8SSimon Kagstrom 		cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size);
162ecd5b310SRichard Purdie 		if (cxt->nextpage >= cxt->oops_pages)
1634b23aff0SRichard Purdie 			cxt->nextpage = 0;
1644b23aff0SRichard Purdie 	}
1654b23aff0SRichard Purdie 
1662986bd2aSRichard Purdie 	while (mtd->block_isbad) {
1679507b0c8SSimon Kagstrom 		ret = mtd->block_isbad(mtd, cxt->nextpage * record_size);
1682986bd2aSRichard Purdie 		if (!ret)
1692986bd2aSRichard Purdie 			break;
1702986bd2aSRichard Purdie 		if (ret < 0) {
171a15b124fSArtem Bityutskiy 			printk(KERN_ERR "mtdoops: block_isbad failed, aborting\n");
1722986bd2aSRichard Purdie 			return;
1732986bd2aSRichard Purdie 		}
1744b23aff0SRichard Purdie badblock:
1759507b0c8SSimon Kagstrom 		printk(KERN_WARNING "mtdoops: bad block at %08lx\n",
1769507b0c8SSimon Kagstrom 		       cxt->nextpage * record_size);
1774b23aff0SRichard Purdie 		i++;
1789507b0c8SSimon Kagstrom 		cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size);
179ecd5b310SRichard Purdie 		if (cxt->nextpage >= cxt->oops_pages)
1804b23aff0SRichard Purdie 			cxt->nextpage = 0;
1819507b0c8SSimon Kagstrom 		if (i == cxt->oops_pages / (mtd->erasesize / record_size)) {
182a15b124fSArtem Bityutskiy 			printk(KERN_ERR "mtdoops: all blocks bad!\n");
1834b23aff0SRichard Purdie 			return;
1844b23aff0SRichard Purdie 		}
1854b23aff0SRichard Purdie 	}
1864b23aff0SRichard Purdie 
1874b23aff0SRichard Purdie 	for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
1889507b0c8SSimon Kagstrom 		ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size);
1894b23aff0SRichard Purdie 
1902986bd2aSRichard Purdie 	if (ret >= 0) {
191a15b124fSArtem Bityutskiy 		printk(KERN_DEBUG "mtdoops: ready %d, %d\n",
192a15b124fSArtem Bityutskiy 		       cxt->nextpage, cxt->nextcount);
1932986bd2aSRichard Purdie 		cxt->ready = 1;
1942986bd2aSRichard Purdie 		return;
1954b23aff0SRichard Purdie 	}
1964b23aff0SRichard Purdie 
197a15b124fSArtem Bityutskiy 	if (mtd->block_markbad && ret == -EIO) {
1989507b0c8SSimon Kagstrom 		ret = mtd->block_markbad(mtd, cxt->nextpage * record_size);
1992986bd2aSRichard Purdie 		if (ret < 0) {
200a15b124fSArtem Bityutskiy 			printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n");
2012986bd2aSRichard Purdie 			return;
2022986bd2aSRichard Purdie 		}
2032986bd2aSRichard Purdie 	}
2042986bd2aSRichard Purdie 	goto badblock;
2054b23aff0SRichard Purdie }
2064b23aff0SRichard Purdie 
207621e4f8eSRichard Purdie static void mtdoops_write(struct mtdoops_context *cxt, int panic)
2084b23aff0SRichard Purdie {
2096ce0a856SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
2106ce0a856SRichard Purdie 	size_t retlen;
2116ce0a856SRichard Purdie 	int ret;
2124b23aff0SRichard Purdie 
2139507b0c8SSimon Kagstrom 	if (cxt->writecount < record_size)
2146ce0a856SRichard Purdie 		memset(cxt->oops_buf + cxt->writecount, 0xff,
2159507b0c8SSimon Kagstrom 					record_size - cxt->writecount);
2166ce0a856SRichard Purdie 
217621e4f8eSRichard Purdie 	if (panic)
2189507b0c8SSimon Kagstrom 		ret = mtd->panic_write(mtd, cxt->nextpage * record_size,
2199507b0c8SSimon Kagstrom 					record_size, &retlen, cxt->oops_buf);
220621e4f8eSRichard Purdie 	else
2219507b0c8SSimon Kagstrom 		ret = mtd->write(mtd, cxt->nextpage * record_size,
2229507b0c8SSimon Kagstrom 					record_size, &retlen, cxt->oops_buf);
2236ce0a856SRichard Purdie 
2246ce0a856SRichard Purdie 	cxt->writecount = 0;
2256ce0a856SRichard Purdie 
2269507b0c8SSimon Kagstrom 	if (retlen != record_size || ret < 0)
2279507b0c8SSimon Kagstrom 		printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n",
2289507b0c8SSimon Kagstrom 		       cxt->nextpage * record_size, retlen, record_size, ret);
229be95745fSSimon Kagstrom 	mark_page_used(cxt, cxt->nextpage);
2306ce0a856SRichard Purdie 
2316ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2324b23aff0SRichard Purdie }
2334b23aff0SRichard Purdie 
234621e4f8eSRichard Purdie 
235621e4f8eSRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work)
236621e4f8eSRichard Purdie {
237621e4f8eSRichard Purdie 	struct mtdoops_context *cxt =
238621e4f8eSRichard Purdie 			container_of(work, struct mtdoops_context, work_write);
239621e4f8eSRichard Purdie 
240621e4f8eSRichard Purdie 	mtdoops_write(cxt, 0);
241621e4f8eSRichard Purdie }
242621e4f8eSRichard Purdie 
2436ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt)
2444b23aff0SRichard Purdie {
2454b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
2462986bd2aSRichard Purdie 	int ret, page, maxpos = 0;
247f0482ee3SRichard Purdie 	u32 count[2], maxcount = 0xffffffff;
2484b23aff0SRichard Purdie 	size_t retlen;
2494b23aff0SRichard Purdie 
2504b23aff0SRichard Purdie 	for (page = 0; page < cxt->oops_pages; page++) {
251be95745fSSimon Kagstrom 		/* Assume the page is used */
252be95745fSSimon Kagstrom 		mark_page_used(cxt, page);
2539507b0c8SSimon Kagstrom 		ret = mtd->read(mtd, page * record_size, 8, &retlen, (u_char *) &count[0]);
254a15b124fSArtem Bityutskiy 		if (retlen != 8 || (ret < 0 && ret != -EUCLEAN)) {
2559507b0c8SSimon Kagstrom 			printk(KERN_ERR "mtdoops: read failure at %ld (%td of 8 read), err %d\n",
2569507b0c8SSimon Kagstrom 			       page * record_size, retlen, ret);
2572986bd2aSRichard Purdie 			continue;
2582986bd2aSRichard Purdie 		}
2592986bd2aSRichard Purdie 
260be95745fSSimon Kagstrom 		if (count[0] == 0xffffffff && count[1] == 0xffffffff)
261be95745fSSimon Kagstrom 			mark_page_unused(cxt, page);
262f0482ee3SRichard Purdie 		if (count[1] != MTDOOPS_KERNMSG_MAGIC)
263f0482ee3SRichard Purdie 			continue;
264f0482ee3SRichard Purdie 		if (count[0] == 0xffffffff)
2654b23aff0SRichard Purdie 			continue;
2664b23aff0SRichard Purdie 		if (maxcount == 0xffffffff) {
267f0482ee3SRichard Purdie 			maxcount = count[0];
2684b23aff0SRichard Purdie 			maxpos = page;
269a15b124fSArtem Bityutskiy 		} else if (count[0] < 0x40000000 && maxcount > 0xc0000000) {
270f0482ee3SRichard Purdie 			maxcount = count[0];
2714b23aff0SRichard Purdie 			maxpos = page;
272a15b124fSArtem Bityutskiy 		} else if (count[0] > maxcount && count[0] < 0xc0000000) {
273f0482ee3SRichard Purdie 			maxcount = count[0];
2744b23aff0SRichard Purdie 			maxpos = page;
275a15b124fSArtem Bityutskiy 		} else if (count[0] > maxcount && count[0] > 0xc0000000
276a15b124fSArtem Bityutskiy 					&& maxcount > 0x80000000) {
277f0482ee3SRichard Purdie 			maxcount = count[0];
2784b23aff0SRichard Purdie 			maxpos = page;
2794b23aff0SRichard Purdie 		}
2804b23aff0SRichard Purdie 	}
2814b23aff0SRichard Purdie 	if (maxcount == 0xffffffff) {
2824b23aff0SRichard Purdie 		cxt->nextpage = 0;
2834b23aff0SRichard Purdie 		cxt->nextcount = 1;
28443b5693dSRichard Purdie 		schedule_work(&cxt->work_erase);
2856ce0a856SRichard Purdie 		return;
2864b23aff0SRichard Purdie 	}
2874b23aff0SRichard Purdie 
2884b23aff0SRichard Purdie 	cxt->nextpage = maxpos;
2894b23aff0SRichard Purdie 	cxt->nextcount = maxcount;
2904b23aff0SRichard Purdie 
2916ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2924b23aff0SRichard Purdie }
2934b23aff0SRichard Purdie 
2944b23aff0SRichard Purdie 
2954b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd)
2964b23aff0SRichard Purdie {
2974b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
298be95745fSSimon Kagstrom 	u64 mtdoops_pages = mtd->size;
299be95745fSSimon Kagstrom 
3009507b0c8SSimon Kagstrom 	do_div(mtdoops_pages, record_size);
3014b23aff0SRichard Purdie 
302e2a0f25bSAdrian Hunter 	if (cxt->name && !strcmp(mtd->name, cxt->name))
303e2a0f25bSAdrian Hunter 		cxt->mtd_index = mtd->index;
304e2a0f25bSAdrian Hunter 
305a15b124fSArtem Bityutskiy 	if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0)
3064b23aff0SRichard Purdie 		return;
3074b23aff0SRichard Purdie 
308a15b124fSArtem Bityutskiy 	if (mtd->size < mtd->erasesize * 2) {
309a15b124fSArtem Bityutskiy 		printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n",
3104b23aff0SRichard Purdie 		       mtd->index);
3114b23aff0SRichard Purdie 		return;
3124b23aff0SRichard Purdie 	}
3134b23aff0SRichard Purdie 
3149507b0c8SSimon Kagstrom 	if (mtd->erasesize < record_size) {
315a15b124fSArtem Bityutskiy 		printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n",
31679dcd8e9SRichard Purdie 		       mtd->index);
31779dcd8e9SRichard Purdie 		return;
31879dcd8e9SRichard Purdie 	}
31979dcd8e9SRichard Purdie 
3201114e3d0SSimon Kagstrom 	if (mtd->size > MTDOOPS_MAX_MTD_SIZE) {
3211114e3d0SSimon Kagstrom 		printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n",
3221114e3d0SSimon Kagstrom 		       mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024);
3231114e3d0SSimon Kagstrom 		return;
3241114e3d0SSimon Kagstrom 	}
3251114e3d0SSimon Kagstrom 
326be95745fSSimon Kagstrom 	/* oops_page_used is a bit field */
327be95745fSSimon Kagstrom 	cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages,
328be95745fSSimon Kagstrom 			BITS_PER_LONG));
329be95745fSSimon Kagstrom 	if (!cxt->oops_page_used) {
330be95745fSSimon Kagstrom 		printk(KERN_ERR "Could not allocate page array\n");
331be95745fSSimon Kagstrom 		return;
332be95745fSSimon Kagstrom 	}
3331114e3d0SSimon Kagstrom 
3344b23aff0SRichard Purdie 	cxt->mtd = mtd;
3359507b0c8SSimon Kagstrom 	cxt->oops_pages = (int)mtd->size / record_size;
3366ce0a856SRichard Purdie 	find_next_position(cxt);
33779dcd8e9SRichard Purdie 	printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index);
3384b23aff0SRichard Purdie }
3394b23aff0SRichard Purdie 
3404b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd)
3414b23aff0SRichard Purdie {
3424b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
3434b23aff0SRichard Purdie 
344a15b124fSArtem Bityutskiy 	if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0)
3454b23aff0SRichard Purdie 		return;
3464b23aff0SRichard Purdie 
3474b23aff0SRichard Purdie 	cxt->mtd = NULL;
3484b23aff0SRichard Purdie 	flush_scheduled_work();
3494b23aff0SRichard Purdie }
3504b23aff0SRichard Purdie 
3518691a729SRichard Purdie static void mtdoops_console_sync(void)
3524b23aff0SRichard Purdie {
3538691a729SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
3544b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
35547c152b8SRichard Purdie 	unsigned long flags;
3564b23aff0SRichard Purdie 
3576ce0a856SRichard Purdie 	if (!cxt->ready || !mtd || cxt->writecount == 0)
3584b23aff0SRichard Purdie 		return;
3594b23aff0SRichard Purdie 
36047c152b8SRichard Purdie 	/*
36147c152b8SRichard Purdie 	 *  Once ready is 0 and we've held the lock no further writes to the
36247c152b8SRichard Purdie 	 *  buffer will happen
36347c152b8SRichard Purdie 	 */
36447c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
36547c152b8SRichard Purdie 	if (!cxt->ready) {
36647c152b8SRichard Purdie 		spin_unlock_irqrestore(&cxt->writecount_lock, flags);
36747c152b8SRichard Purdie 		return;
36847c152b8SRichard Purdie 	}
3694b23aff0SRichard Purdie 	cxt->ready = 0;
37047c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
3714b23aff0SRichard Purdie 
372621e4f8eSRichard Purdie 	if (mtd->panic_write && in_interrupt())
373621e4f8eSRichard Purdie 		/* Interrupt context, we're going to panic so try and log */
374621e4f8eSRichard Purdie 		mtdoops_write(cxt, 1);
375621e4f8eSRichard Purdie 	else
3766ce0a856SRichard Purdie 		schedule_work(&cxt->work_write);
3774b23aff0SRichard Purdie }
3784b23aff0SRichard Purdie 
3798691a729SRichard Purdie static void
3808691a729SRichard Purdie mtdoops_console_write(struct console *co, const char *s, unsigned int count)
3818691a729SRichard Purdie {
3828691a729SRichard Purdie 	struct mtdoops_context *cxt = co->data;
3838691a729SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
38447c152b8SRichard Purdie 	unsigned long flags;
3858691a729SRichard Purdie 
3868691a729SRichard Purdie 	if (!oops_in_progress) {
3878691a729SRichard Purdie 		mtdoops_console_sync();
3888691a729SRichard Purdie 		return;
3898691a729SRichard Purdie 	}
3908691a729SRichard Purdie 
3918691a729SRichard Purdie 	if (!cxt->ready || !mtd)
3924b23aff0SRichard Purdie 		return;
3934b23aff0SRichard Purdie 
39447c152b8SRichard Purdie 	/* Locking on writecount ensures sequential writes to the buffer */
39547c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
39647c152b8SRichard Purdie 
39747c152b8SRichard Purdie 	/* Check ready status didn't change whilst waiting for the lock */
39848ec00acSAdrian Hunter 	if (!cxt->ready) {
39948ec00acSAdrian Hunter 		spin_unlock_irqrestore(&cxt->writecount_lock, flags);
40047c152b8SRichard Purdie 		return;
40148ec00acSAdrian Hunter 	}
40247c152b8SRichard Purdie 
4034b23aff0SRichard Purdie 	if (cxt->writecount == 0) {
4044b23aff0SRichard Purdie 		u32 *stamp = cxt->oops_buf;
405f0482ee3SRichard Purdie 		*stamp++ = cxt->nextcount;
406f0482ee3SRichard Purdie 		*stamp = MTDOOPS_KERNMSG_MAGIC;
407f0482ee3SRichard Purdie 		cxt->writecount = 8;
4084b23aff0SRichard Purdie 	}
4094b23aff0SRichard Purdie 
4109507b0c8SSimon Kagstrom 	if (count + cxt->writecount > record_size)
4119507b0c8SSimon Kagstrom 		count = record_size - cxt->writecount;
4124b23aff0SRichard Purdie 
413235d6200SPeter Korsgaard 	memcpy(cxt->oops_buf + cxt->writecount, s, count);
414235d6200SPeter Korsgaard 	cxt->writecount += count;
41547c152b8SRichard Purdie 
41647c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
41747c152b8SRichard Purdie 
4189507b0c8SSimon Kagstrom 	if (cxt->writecount == record_size)
41947c152b8SRichard Purdie 		mtdoops_console_sync();
4204b23aff0SRichard Purdie }
4214b23aff0SRichard Purdie 
4224b23aff0SRichard Purdie static int __init mtdoops_console_setup(struct console *co, char *options)
4234b23aff0SRichard Purdie {
4244b23aff0SRichard Purdie 	struct mtdoops_context *cxt = co->data;
4254b23aff0SRichard Purdie 
426e2a0f25bSAdrian Hunter 	if (cxt->mtd_index != -1 || cxt->name)
4274b23aff0SRichard Purdie 		return -EBUSY;
428e2a0f25bSAdrian Hunter 	if (options) {
429e2a0f25bSAdrian Hunter 		cxt->name = kstrdup(options, GFP_KERNEL);
430e2a0f25bSAdrian Hunter 		return 0;
431e2a0f25bSAdrian Hunter 	}
4324b23aff0SRichard Purdie 	if (co->index == -1)
4334b23aff0SRichard Purdie 		return -EINVAL;
4344b23aff0SRichard Purdie 
4354b23aff0SRichard Purdie 	cxt->mtd_index = co->index;
4364b23aff0SRichard Purdie 	return 0;
4374b23aff0SRichard Purdie }
4384b23aff0SRichard Purdie 
4394b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = {
4404b23aff0SRichard Purdie 	.add	= mtdoops_notify_add,
4414b23aff0SRichard Purdie 	.remove	= mtdoops_notify_remove,
4424b23aff0SRichard Purdie };
4434b23aff0SRichard Purdie 
4444b23aff0SRichard Purdie static struct console mtdoops_console = {
4454b23aff0SRichard Purdie 	.name		= "ttyMTD",
4464b23aff0SRichard Purdie 	.write		= mtdoops_console_write,
4474b23aff0SRichard Purdie 	.setup		= mtdoops_console_setup,
4488691a729SRichard Purdie 	.unblank	= mtdoops_console_sync,
4494b23aff0SRichard Purdie 	.index		= -1,
4504b23aff0SRichard Purdie 	.data		= &oops_cxt,
4514b23aff0SRichard Purdie };
4524b23aff0SRichard Purdie 
4534b23aff0SRichard Purdie static int __init mtdoops_console_init(void)
4544b23aff0SRichard Purdie {
4554b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
4564b23aff0SRichard Purdie 
4579507b0c8SSimon Kagstrom 	if ((record_size & 4095) != 0) {
4589507b0c8SSimon Kagstrom 		printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n");
4599507b0c8SSimon Kagstrom 		return -EINVAL;
4609507b0c8SSimon Kagstrom 	}
4619507b0c8SSimon Kagstrom 	if (record_size < 4096) {
4629507b0c8SSimon Kagstrom 		printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n");
4639507b0c8SSimon Kagstrom 		return -EINVAL;
4649507b0c8SSimon Kagstrom 	}
4654b23aff0SRichard Purdie 	cxt->mtd_index = -1;
4669507b0c8SSimon Kagstrom 	cxt->oops_buf = vmalloc(record_size);
4674b23aff0SRichard Purdie 	if (!cxt->oops_buf) {
468a15b124fSArtem Bityutskiy 		printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n");
4694b23aff0SRichard Purdie 		return -ENOMEM;
4704b23aff0SRichard Purdie 	}
4714b23aff0SRichard Purdie 
472a15b124fSArtem Bityutskiy 	spin_lock_init(&cxt->writecount_lock);
4736ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase);
4746ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_write, mtdoops_workfunc_write);
4754b23aff0SRichard Purdie 
4764b23aff0SRichard Purdie 	register_console(&mtdoops_console);
4774b23aff0SRichard Purdie 	register_mtd_user(&mtdoops_notifier);
4784b23aff0SRichard Purdie 	return 0;
4794b23aff0SRichard Purdie }
4804b23aff0SRichard Purdie 
4814b23aff0SRichard Purdie static void __exit mtdoops_console_exit(void)
4824b23aff0SRichard Purdie {
4834b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
4844b23aff0SRichard Purdie 
4854b23aff0SRichard Purdie 	unregister_mtd_user(&mtdoops_notifier);
4864b23aff0SRichard Purdie 	unregister_console(&mtdoops_console);
487e2a0f25bSAdrian Hunter 	kfree(cxt->name);
4884b23aff0SRichard Purdie 	vfree(cxt->oops_buf);
489be95745fSSimon Kagstrom 	vfree(cxt->oops_page_used);
4904b23aff0SRichard Purdie }
4914b23aff0SRichard Purdie 
4924b23aff0SRichard Purdie 
4934b23aff0SRichard Purdie subsys_initcall(mtdoops_console_init);
4944b23aff0SRichard Purdie module_exit(mtdoops_console_exit);
4954b23aff0SRichard Purdie 
4964b23aff0SRichard Purdie MODULE_LICENSE("GPL");
4974b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
4984b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");
499