xref: /openbmc/linux/drivers/mtd/mtdoops.c (revision 7903cbab)
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 
364b23aff0SRichard Purdie #define OOPS_PAGE_SIZE 4096
374b23aff0SRichard Purdie 
387903cbabSAdrian Bunk static struct mtdoops_context {
394b23aff0SRichard Purdie 	int mtd_index;
406ce0a856SRichard Purdie 	struct work_struct work_erase;
416ce0a856SRichard Purdie 	struct work_struct work_write;
424b23aff0SRichard Purdie 	struct mtd_info *mtd;
434b23aff0SRichard Purdie 	int oops_pages;
444b23aff0SRichard Purdie 	int nextpage;
454b23aff0SRichard Purdie 	int nextcount;
464b23aff0SRichard Purdie 
474b23aff0SRichard Purdie 	void *oops_buf;
4847c152b8SRichard Purdie 
4947c152b8SRichard Purdie 	/* writecount and disabling ready are spin lock protected */
5047c152b8SRichard Purdie 	spinlock_t writecount_lock;
514b23aff0SRichard Purdie 	int ready;
524b23aff0SRichard Purdie 	int writecount;
534b23aff0SRichard Purdie } oops_cxt;
544b23aff0SRichard Purdie 
554b23aff0SRichard Purdie static void mtdoops_erase_callback(struct erase_info *done)
564b23aff0SRichard Purdie {
574b23aff0SRichard Purdie 	wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
584b23aff0SRichard Purdie 	wake_up(wait_q);
594b23aff0SRichard Purdie }
604b23aff0SRichard Purdie 
614b23aff0SRichard Purdie static int mtdoops_erase_block(struct mtd_info *mtd, int offset)
624b23aff0SRichard Purdie {
634b23aff0SRichard Purdie 	struct erase_info erase;
644b23aff0SRichard Purdie 	DECLARE_WAITQUEUE(wait, current);
654b23aff0SRichard Purdie 	wait_queue_head_t wait_q;
664b23aff0SRichard Purdie 	int ret;
674b23aff0SRichard Purdie 
684b23aff0SRichard Purdie 	init_waitqueue_head(&wait_q);
694b23aff0SRichard Purdie 	erase.mtd = mtd;
704b23aff0SRichard Purdie 	erase.callback = mtdoops_erase_callback;
714b23aff0SRichard Purdie 	erase.addr = offset;
724b23aff0SRichard Purdie 	erase.len = mtd->erasesize;
734b23aff0SRichard Purdie 	erase.priv = (u_long)&wait_q;
744b23aff0SRichard Purdie 
754b23aff0SRichard Purdie 	set_current_state(TASK_INTERRUPTIBLE);
764b23aff0SRichard Purdie 	add_wait_queue(&wait_q, &wait);
774b23aff0SRichard Purdie 
784b23aff0SRichard Purdie 	ret = mtd->erase(mtd, &erase);
794b23aff0SRichard Purdie 	if (ret) {
804b23aff0SRichard Purdie 		set_current_state(TASK_RUNNING);
814b23aff0SRichard Purdie 		remove_wait_queue(&wait_q, &wait);
824b23aff0SRichard Purdie 		printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
834b23aff0SRichard Purdie 				     "on \"%s\" failed\n",
844b23aff0SRichard Purdie 			erase.addr, erase.len, mtd->name);
854b23aff0SRichard Purdie 		return ret;
864b23aff0SRichard Purdie 	}
874b23aff0SRichard Purdie 
884b23aff0SRichard Purdie 	schedule();  /* Wait for erase to finish. */
894b23aff0SRichard Purdie 	remove_wait_queue(&wait_q, &wait);
904b23aff0SRichard Purdie 
914b23aff0SRichard Purdie 	return 0;
924b23aff0SRichard Purdie }
934b23aff0SRichard Purdie 
946ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt)
954b23aff0SRichard Purdie {
964b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
974b23aff0SRichard Purdie 	size_t retlen;
984b23aff0SRichard Purdie 	u32 count;
994b23aff0SRichard Purdie 	int ret;
1004b23aff0SRichard Purdie 
1014b23aff0SRichard Purdie 	cxt->nextpage++;
1024b23aff0SRichard Purdie 	if (cxt->nextpage > cxt->oops_pages)
1034b23aff0SRichard Purdie 		cxt->nextpage = 0;
1044b23aff0SRichard Purdie 	cxt->nextcount++;
1054b23aff0SRichard Purdie 	if (cxt->nextcount == 0xffffffff)
1064b23aff0SRichard Purdie 		cxt->nextcount = 0;
1074b23aff0SRichard Purdie 
1084b23aff0SRichard Purdie 	ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4,
1094b23aff0SRichard Purdie 			&retlen, (u_char *) &count);
1102986bd2aSRichard Purdie 	if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) {
11168d09b1bSAndrew Morton 		printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
1124b23aff0SRichard Purdie 				", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE,
1134b23aff0SRichard Purdie 				retlen, ret);
1146ce0a856SRichard Purdie 		schedule_work(&cxt->work_erase);
1156ce0a856SRichard Purdie 		return;
1164b23aff0SRichard Purdie 	}
1174b23aff0SRichard Purdie 
1184b23aff0SRichard Purdie 	/* See if we need to erase the next block */
1196ce0a856SRichard Purdie 	if (count != 0xffffffff) {
1206ce0a856SRichard Purdie 		schedule_work(&cxt->work_erase);
1216ce0a856SRichard Purdie 		return;
1226ce0a856SRichard Purdie 	}
1234b23aff0SRichard Purdie 
1244b23aff0SRichard Purdie 	printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n",
1254b23aff0SRichard Purdie 			cxt->nextpage, cxt->nextcount);
1264b23aff0SRichard Purdie 	cxt->ready = 1;
1274b23aff0SRichard Purdie }
1284b23aff0SRichard Purdie 
1296ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */
1306ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work)
1314b23aff0SRichard Purdie {
1326ce0a856SRichard Purdie 	struct mtdoops_context *cxt =
1336ce0a856SRichard Purdie 			container_of(work, struct mtdoops_context, work_erase);
1344b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
1354b23aff0SRichard Purdie 	int i = 0, j, ret, mod;
1364b23aff0SRichard Purdie 
1374b23aff0SRichard Purdie 	/* We were unregistered */
1384b23aff0SRichard Purdie 	if (!mtd)
1394b23aff0SRichard Purdie 		return;
1404b23aff0SRichard Purdie 
1414b23aff0SRichard Purdie 	mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize;
1424b23aff0SRichard Purdie 	if (mod != 0) {
1434b23aff0SRichard Purdie 		cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE);
1444b23aff0SRichard Purdie 		if (cxt->nextpage > cxt->oops_pages)
1454b23aff0SRichard Purdie 			cxt->nextpage = 0;
1464b23aff0SRichard Purdie 	}
1474b23aff0SRichard Purdie 
1482986bd2aSRichard Purdie 	while (mtd->block_isbad) {
1492986bd2aSRichard Purdie 		ret = mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1502986bd2aSRichard Purdie 		if (!ret)
1512986bd2aSRichard Purdie 			break;
1522986bd2aSRichard Purdie 		if (ret < 0) {
1532986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: block_isbad failed, aborting.\n");
1542986bd2aSRichard Purdie 			return;
1552986bd2aSRichard Purdie 		}
1564b23aff0SRichard Purdie badblock:
1574b23aff0SRichard Purdie 		printk(KERN_WARNING "mtdoops: Bad block at %08x\n",
1584b23aff0SRichard Purdie 				cxt->nextpage * OOPS_PAGE_SIZE);
1594b23aff0SRichard Purdie 		i++;
1604b23aff0SRichard Purdie 		cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE);
1614b23aff0SRichard Purdie 		if (cxt->nextpage > cxt->oops_pages)
1624b23aff0SRichard Purdie 			cxt->nextpage = 0;
1634b23aff0SRichard Purdie 		if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) {
1644b23aff0SRichard Purdie 			printk(KERN_ERR "mtdoops: All blocks bad!\n");
1654b23aff0SRichard Purdie 			return;
1664b23aff0SRichard Purdie 		}
1674b23aff0SRichard Purdie 	}
1684b23aff0SRichard Purdie 
1694b23aff0SRichard Purdie 	for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
1704b23aff0SRichard Purdie 		ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1714b23aff0SRichard Purdie 
1722986bd2aSRichard Purdie 	if (ret >= 0) {
1732986bd2aSRichard Purdie 		printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount);
1742986bd2aSRichard Purdie 		cxt->ready = 1;
1752986bd2aSRichard Purdie 		return;
1764b23aff0SRichard Purdie 	}
1774b23aff0SRichard Purdie 
1782986bd2aSRichard Purdie 	if (mtd->block_markbad && (ret == -EIO)) {
1792986bd2aSRichard Purdie 		ret = mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1802986bd2aSRichard Purdie 		if (ret < 0) {
1812986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: block_markbad failed, aborting.\n");
1822986bd2aSRichard Purdie 			return;
1832986bd2aSRichard Purdie 		}
1842986bd2aSRichard Purdie 	}
1852986bd2aSRichard Purdie 	goto badblock;
1864b23aff0SRichard Purdie }
1874b23aff0SRichard Purdie 
188621e4f8eSRichard Purdie static void mtdoops_write(struct mtdoops_context *cxt, int panic)
1894b23aff0SRichard Purdie {
1906ce0a856SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
1916ce0a856SRichard Purdie 	size_t retlen;
1926ce0a856SRichard Purdie 	int ret;
1934b23aff0SRichard Purdie 
1946ce0a856SRichard Purdie 	if (cxt->writecount < OOPS_PAGE_SIZE)
1956ce0a856SRichard Purdie 		memset(cxt->oops_buf + cxt->writecount, 0xff,
1966ce0a856SRichard Purdie 					OOPS_PAGE_SIZE - cxt->writecount);
1976ce0a856SRichard Purdie 
198621e4f8eSRichard Purdie 	if (panic)
199621e4f8eSRichard Purdie 		ret = mtd->panic_write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
200621e4f8eSRichard Purdie 					OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
201621e4f8eSRichard Purdie 	else
2026ce0a856SRichard Purdie 		ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
2036ce0a856SRichard Purdie 					OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
2046ce0a856SRichard Purdie 
2056ce0a856SRichard Purdie 	cxt->writecount = 0;
2066ce0a856SRichard Purdie 
2076ce0a856SRichard Purdie 	if ((retlen != OOPS_PAGE_SIZE) || (ret < 0))
2086ce0a856SRichard Purdie 		printk(KERN_ERR "mtdoops: Write failure at %d (%td of %d written), err %d.\n",
2096ce0a856SRichard Purdie 			cxt->nextpage * OOPS_PAGE_SIZE, retlen,	OOPS_PAGE_SIZE, ret);
2106ce0a856SRichard Purdie 
2116ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2124b23aff0SRichard Purdie }
2134b23aff0SRichard Purdie 
214621e4f8eSRichard Purdie 
215621e4f8eSRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work)
216621e4f8eSRichard Purdie {
217621e4f8eSRichard Purdie 	struct mtdoops_context *cxt =
218621e4f8eSRichard Purdie 			container_of(work, struct mtdoops_context, work_write);
219621e4f8eSRichard Purdie 
220621e4f8eSRichard Purdie 	mtdoops_write(cxt, 0);
221621e4f8eSRichard Purdie }
222621e4f8eSRichard Purdie 
2236ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt)
2244b23aff0SRichard Purdie {
2254b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
2262986bd2aSRichard Purdie 	int ret, page, maxpos = 0;
2274b23aff0SRichard Purdie 	u32 count, maxcount = 0xffffffff;
2284b23aff0SRichard Purdie 	size_t retlen;
2294b23aff0SRichard Purdie 
2304b23aff0SRichard Purdie 	for (page = 0; page < cxt->oops_pages; page++) {
2312986bd2aSRichard Purdie 		ret = mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count);
2322986bd2aSRichard Purdie 		if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) {
2332986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
2342986bd2aSRichard Purdie 				", err %d.\n", page * OOPS_PAGE_SIZE, retlen, ret);
2352986bd2aSRichard Purdie 			continue;
2362986bd2aSRichard Purdie 		}
2372986bd2aSRichard Purdie 
2384b23aff0SRichard Purdie 		if (count == 0xffffffff)
2394b23aff0SRichard Purdie 			continue;
2404b23aff0SRichard Purdie 		if (maxcount == 0xffffffff) {
2414b23aff0SRichard Purdie 			maxcount = count;
2424b23aff0SRichard Purdie 			maxpos = page;
2434b23aff0SRichard Purdie 		} else if ((count < 0x40000000) && (maxcount > 0xc0000000)) {
2444b23aff0SRichard Purdie 			maxcount = count;
2454b23aff0SRichard Purdie 			maxpos = page;
2464b23aff0SRichard Purdie 		} else if ((count > maxcount) && (count < 0xc0000000)) {
2474b23aff0SRichard Purdie 			maxcount = count;
2484b23aff0SRichard Purdie 			maxpos = page;
2494b23aff0SRichard Purdie 		} else if ((count > maxcount) && (count > 0xc0000000)
2504b23aff0SRichard Purdie 					&& (maxcount > 0x80000000)) {
2514b23aff0SRichard Purdie 			maxcount = count;
2524b23aff0SRichard Purdie 			maxpos = page;
2534b23aff0SRichard Purdie 		}
2544b23aff0SRichard Purdie 	}
2554b23aff0SRichard Purdie 	if (maxcount == 0xffffffff) {
2564b23aff0SRichard Purdie 		cxt->nextpage = 0;
2574b23aff0SRichard Purdie 		cxt->nextcount = 1;
2584b23aff0SRichard Purdie 		cxt->ready = 1;
2594b23aff0SRichard Purdie 		printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n",
2604b23aff0SRichard Purdie 				cxt->nextpage, cxt->nextcount);
2616ce0a856SRichard Purdie 		return;
2624b23aff0SRichard Purdie 	}
2634b23aff0SRichard Purdie 
2644b23aff0SRichard Purdie 	cxt->nextpage = maxpos;
2654b23aff0SRichard Purdie 	cxt->nextcount = maxcount;
2664b23aff0SRichard Purdie 
2676ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2684b23aff0SRichard Purdie }
2694b23aff0SRichard Purdie 
2704b23aff0SRichard Purdie 
2714b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd)
2724b23aff0SRichard Purdie {
2734b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
2744b23aff0SRichard Purdie 
2754b23aff0SRichard Purdie 	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
2764b23aff0SRichard Purdie 		return;
2774b23aff0SRichard Purdie 
2784b23aff0SRichard Purdie 	if (mtd->size < (mtd->erasesize * 2)) {
2794b23aff0SRichard Purdie 		printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n",
2804b23aff0SRichard Purdie 				mtd->index);
2814b23aff0SRichard Purdie 		return;
2824b23aff0SRichard Purdie 	}
2834b23aff0SRichard Purdie 
28479dcd8e9SRichard Purdie 	if (mtd->erasesize < OOPS_PAGE_SIZE) {
28579dcd8e9SRichard Purdie 		printk(KERN_ERR "Eraseblock size of MTD partition %d too small\n",
28679dcd8e9SRichard Purdie 				mtd->index);
28779dcd8e9SRichard Purdie 		return;
28879dcd8e9SRichard Purdie 	}
28979dcd8e9SRichard Purdie 
2904b23aff0SRichard Purdie 	cxt->mtd = mtd;
2914b23aff0SRichard Purdie 	cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE;
2924b23aff0SRichard Purdie 
2936ce0a856SRichard Purdie 	find_next_position(cxt);
2944b23aff0SRichard Purdie 
29579dcd8e9SRichard Purdie 	printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index);
2964b23aff0SRichard Purdie }
2974b23aff0SRichard Purdie 
2984b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd)
2994b23aff0SRichard Purdie {
3004b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
3014b23aff0SRichard Purdie 
3024b23aff0SRichard Purdie 	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
3034b23aff0SRichard Purdie 		return;
3044b23aff0SRichard Purdie 
3054b23aff0SRichard Purdie 	cxt->mtd = NULL;
3064b23aff0SRichard Purdie 	flush_scheduled_work();
3074b23aff0SRichard Purdie }
3084b23aff0SRichard Purdie 
3098691a729SRichard Purdie static void mtdoops_console_sync(void)
3104b23aff0SRichard Purdie {
3118691a729SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
3124b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
31347c152b8SRichard Purdie 	unsigned long flags;
3144b23aff0SRichard Purdie 
3156ce0a856SRichard Purdie 	if (!cxt->ready || !mtd || cxt->writecount == 0)
3164b23aff0SRichard Purdie 		return;
3174b23aff0SRichard Purdie 
31847c152b8SRichard Purdie 	/*
31947c152b8SRichard Purdie 	 *  Once ready is 0 and we've held the lock no further writes to the
32047c152b8SRichard Purdie 	 *  buffer will happen
32147c152b8SRichard Purdie 	 */
32247c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
32347c152b8SRichard Purdie 	if (!cxt->ready) {
32447c152b8SRichard Purdie 		spin_unlock_irqrestore(&cxt->writecount_lock, flags);
32547c152b8SRichard Purdie 		return;
32647c152b8SRichard Purdie 	}
3274b23aff0SRichard Purdie 	cxt->ready = 0;
32847c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
3294b23aff0SRichard Purdie 
330621e4f8eSRichard Purdie 	if (mtd->panic_write && in_interrupt())
331621e4f8eSRichard Purdie 		/* Interrupt context, we're going to panic so try and log */
332621e4f8eSRichard Purdie 		mtdoops_write(cxt, 1);
333621e4f8eSRichard Purdie 	else
3346ce0a856SRichard Purdie 		schedule_work(&cxt->work_write);
3354b23aff0SRichard Purdie }
3364b23aff0SRichard Purdie 
3378691a729SRichard Purdie static void
3388691a729SRichard Purdie mtdoops_console_write(struct console *co, const char *s, unsigned int count)
3398691a729SRichard Purdie {
3408691a729SRichard Purdie 	struct mtdoops_context *cxt = co->data;
3418691a729SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
34247c152b8SRichard Purdie 	unsigned long flags;
3438691a729SRichard Purdie 
3448691a729SRichard Purdie 	if (!oops_in_progress) {
3458691a729SRichard Purdie 		mtdoops_console_sync();
3468691a729SRichard Purdie 		return;
3478691a729SRichard Purdie 	}
3488691a729SRichard Purdie 
3498691a729SRichard Purdie 	if (!cxt->ready || !mtd)
3504b23aff0SRichard Purdie 		return;
3514b23aff0SRichard Purdie 
35247c152b8SRichard Purdie 	/* Locking on writecount ensures sequential writes to the buffer */
35347c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
35447c152b8SRichard Purdie 
35547c152b8SRichard Purdie 	/* Check ready status didn't change whilst waiting for the lock */
35647c152b8SRichard Purdie 	if (!cxt->ready)
35747c152b8SRichard Purdie 		return;
35847c152b8SRichard Purdie 
3594b23aff0SRichard Purdie 	if (cxt->writecount == 0) {
3604b23aff0SRichard Purdie 		u32 *stamp = cxt->oops_buf;
3614b23aff0SRichard Purdie 		*stamp = cxt->nextcount;
3624b23aff0SRichard Purdie 		cxt->writecount = 4;
3634b23aff0SRichard Purdie 	}
3644b23aff0SRichard Purdie 
3654b23aff0SRichard Purdie 	if ((count + cxt->writecount) > OOPS_PAGE_SIZE)
3664b23aff0SRichard Purdie 		count = OOPS_PAGE_SIZE - cxt->writecount;
3674b23aff0SRichard Purdie 
368235d6200SPeter Korsgaard 	memcpy(cxt->oops_buf + cxt->writecount, s, count);
369235d6200SPeter Korsgaard 	cxt->writecount += count;
37047c152b8SRichard Purdie 
37147c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
37247c152b8SRichard Purdie 
37347c152b8SRichard Purdie 	if (cxt->writecount == OOPS_PAGE_SIZE)
37447c152b8SRichard Purdie 		mtdoops_console_sync();
3754b23aff0SRichard Purdie }
3764b23aff0SRichard Purdie 
3774b23aff0SRichard Purdie static int __init mtdoops_console_setup(struct console *co, char *options)
3784b23aff0SRichard Purdie {
3794b23aff0SRichard Purdie 	struct mtdoops_context *cxt = co->data;
3804b23aff0SRichard Purdie 
3814b23aff0SRichard Purdie 	if (cxt->mtd_index != -1)
3824b23aff0SRichard Purdie 		return -EBUSY;
3834b23aff0SRichard Purdie 	if (co->index == -1)
3844b23aff0SRichard Purdie 		return -EINVAL;
3854b23aff0SRichard Purdie 
3864b23aff0SRichard Purdie 	cxt->mtd_index = co->index;
3874b23aff0SRichard Purdie 	return 0;
3884b23aff0SRichard Purdie }
3894b23aff0SRichard Purdie 
3904b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = {
3914b23aff0SRichard Purdie 	.add	= mtdoops_notify_add,
3924b23aff0SRichard Purdie 	.remove	= mtdoops_notify_remove,
3934b23aff0SRichard Purdie };
3944b23aff0SRichard Purdie 
3954b23aff0SRichard Purdie static struct console mtdoops_console = {
3964b23aff0SRichard Purdie 	.name		= "ttyMTD",
3974b23aff0SRichard Purdie 	.write		= mtdoops_console_write,
3984b23aff0SRichard Purdie 	.setup		= mtdoops_console_setup,
3998691a729SRichard Purdie 	.unblank	= mtdoops_console_sync,
4004b23aff0SRichard Purdie 	.index		= -1,
4014b23aff0SRichard Purdie 	.data		= &oops_cxt,
4024b23aff0SRichard Purdie };
4034b23aff0SRichard Purdie 
4044b23aff0SRichard Purdie static int __init mtdoops_console_init(void)
4054b23aff0SRichard Purdie {
4064b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
4074b23aff0SRichard Purdie 
4084b23aff0SRichard Purdie 	cxt->mtd_index = -1;
4094b23aff0SRichard Purdie 	cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE);
4104b23aff0SRichard Purdie 
4114b23aff0SRichard Purdie 	if (!cxt->oops_buf) {
41279dcd8e9SRichard Purdie 		printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n");
4134b23aff0SRichard Purdie 		return -ENOMEM;
4144b23aff0SRichard Purdie 	}
4154b23aff0SRichard Purdie 
4166ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase);
4176ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_write, mtdoops_workfunc_write);
4184b23aff0SRichard Purdie 
4194b23aff0SRichard Purdie 	register_console(&mtdoops_console);
4204b23aff0SRichard Purdie 	register_mtd_user(&mtdoops_notifier);
4214b23aff0SRichard Purdie 	return 0;
4224b23aff0SRichard Purdie }
4234b23aff0SRichard Purdie 
4244b23aff0SRichard Purdie static void __exit mtdoops_console_exit(void)
4254b23aff0SRichard Purdie {
4264b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
4274b23aff0SRichard Purdie 
4284b23aff0SRichard Purdie 	unregister_mtd_user(&mtdoops_notifier);
4294b23aff0SRichard Purdie 	unregister_console(&mtdoops_console);
4304b23aff0SRichard Purdie 	vfree(cxt->oops_buf);
4314b23aff0SRichard Purdie }
4324b23aff0SRichard Purdie 
4334b23aff0SRichard Purdie 
4344b23aff0SRichard Purdie subsys_initcall(mtdoops_console_init);
4354b23aff0SRichard Purdie module_exit(mtdoops_console_exit);
4364b23aff0SRichard Purdie 
4374b23aff0SRichard Purdie MODULE_LICENSE("GPL");
4384b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
4394b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");
440