xref: /linux/drivers/mtd/mtdoops.c (revision 79dcd8e9e1f2864ade80f45e144e5e80fef71613)
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>
3147c152b8SRichard Purdie #include <linux/spinlock.h>
324b23aff0SRichard Purdie #include <linux/mtd/mtd.h>
334b23aff0SRichard Purdie 
344b23aff0SRichard Purdie #define OOPS_PAGE_SIZE 4096
354b23aff0SRichard Purdie 
366ce0a856SRichard Purdie struct mtdoops_context {
374b23aff0SRichard Purdie 	int mtd_index;
386ce0a856SRichard Purdie 	struct work_struct work_erase;
396ce0a856SRichard Purdie 	struct work_struct work_write;
404b23aff0SRichard Purdie 	struct mtd_info *mtd;
414b23aff0SRichard Purdie 	int oops_pages;
424b23aff0SRichard Purdie 	int nextpage;
434b23aff0SRichard Purdie 	int nextcount;
444b23aff0SRichard Purdie 
454b23aff0SRichard Purdie 	void *oops_buf;
4647c152b8SRichard Purdie 
4747c152b8SRichard Purdie 	/* writecount and disabling ready are spin lock protected */
4847c152b8SRichard Purdie 	spinlock_t writecount_lock;
494b23aff0SRichard Purdie 	int ready;
504b23aff0SRichard Purdie 	int writecount;
514b23aff0SRichard Purdie } oops_cxt;
524b23aff0SRichard Purdie 
534b23aff0SRichard Purdie static void mtdoops_erase_callback(struct erase_info *done)
544b23aff0SRichard Purdie {
554b23aff0SRichard Purdie 	wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
564b23aff0SRichard Purdie 	wake_up(wait_q);
574b23aff0SRichard Purdie }
584b23aff0SRichard Purdie 
594b23aff0SRichard Purdie static int mtdoops_erase_block(struct mtd_info *mtd, int offset)
604b23aff0SRichard Purdie {
614b23aff0SRichard Purdie 	struct erase_info erase;
624b23aff0SRichard Purdie 	DECLARE_WAITQUEUE(wait, current);
634b23aff0SRichard Purdie 	wait_queue_head_t wait_q;
644b23aff0SRichard Purdie 	int ret;
654b23aff0SRichard Purdie 
664b23aff0SRichard Purdie 	init_waitqueue_head(&wait_q);
674b23aff0SRichard Purdie 	erase.mtd = mtd;
684b23aff0SRichard Purdie 	erase.callback = mtdoops_erase_callback;
694b23aff0SRichard Purdie 	erase.addr = offset;
704b23aff0SRichard Purdie 	erase.len = mtd->erasesize;
714b23aff0SRichard Purdie 	erase.priv = (u_long)&wait_q;
724b23aff0SRichard Purdie 
734b23aff0SRichard Purdie 	set_current_state(TASK_INTERRUPTIBLE);
744b23aff0SRichard Purdie 	add_wait_queue(&wait_q, &wait);
754b23aff0SRichard Purdie 
764b23aff0SRichard Purdie 	ret = mtd->erase(mtd, &erase);
774b23aff0SRichard Purdie 	if (ret) {
784b23aff0SRichard Purdie 		set_current_state(TASK_RUNNING);
794b23aff0SRichard Purdie 		remove_wait_queue(&wait_q, &wait);
804b23aff0SRichard Purdie 		printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
814b23aff0SRichard Purdie 				     "on \"%s\" failed\n",
824b23aff0SRichard Purdie 			erase.addr, erase.len, mtd->name);
834b23aff0SRichard Purdie 		return ret;
844b23aff0SRichard Purdie 	}
854b23aff0SRichard Purdie 
864b23aff0SRichard Purdie 	schedule();  /* Wait for erase to finish. */
874b23aff0SRichard Purdie 	remove_wait_queue(&wait_q, &wait);
884b23aff0SRichard Purdie 
894b23aff0SRichard Purdie 	return 0;
904b23aff0SRichard Purdie }
914b23aff0SRichard Purdie 
926ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt)
934b23aff0SRichard Purdie {
944b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
954b23aff0SRichard Purdie 	size_t retlen;
964b23aff0SRichard Purdie 	u32 count;
974b23aff0SRichard Purdie 	int ret;
984b23aff0SRichard Purdie 
994b23aff0SRichard Purdie 	cxt->nextpage++;
1004b23aff0SRichard Purdie 	if (cxt->nextpage > cxt->oops_pages)
1014b23aff0SRichard Purdie 		cxt->nextpage = 0;
1024b23aff0SRichard Purdie 	cxt->nextcount++;
1034b23aff0SRichard Purdie 	if (cxt->nextcount == 0xffffffff)
1044b23aff0SRichard Purdie 		cxt->nextcount = 0;
1054b23aff0SRichard Purdie 
1064b23aff0SRichard Purdie 	ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4,
1074b23aff0SRichard Purdie 			&retlen, (u_char *) &count);
1082986bd2aSRichard Purdie 	if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) {
10968d09b1bSAndrew Morton 		printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
1104b23aff0SRichard Purdie 				", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE,
1114b23aff0SRichard Purdie 				retlen, ret);
1126ce0a856SRichard Purdie 		schedule_work(&cxt->work_erase);
1136ce0a856SRichard Purdie 		return;
1144b23aff0SRichard Purdie 	}
1154b23aff0SRichard Purdie 
1164b23aff0SRichard Purdie 	/* See if we need to erase the next block */
1176ce0a856SRichard Purdie 	if (count != 0xffffffff) {
1186ce0a856SRichard Purdie 		schedule_work(&cxt->work_erase);
1196ce0a856SRichard Purdie 		return;
1206ce0a856SRichard Purdie 	}
1214b23aff0SRichard Purdie 
1224b23aff0SRichard Purdie 	printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n",
1234b23aff0SRichard Purdie 			cxt->nextpage, cxt->nextcount);
1244b23aff0SRichard Purdie 	cxt->ready = 1;
1254b23aff0SRichard Purdie }
1264b23aff0SRichard Purdie 
1276ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */
1286ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work)
1294b23aff0SRichard Purdie {
1306ce0a856SRichard Purdie 	struct mtdoops_context *cxt =
1316ce0a856SRichard Purdie 			container_of(work, struct mtdoops_context, work_erase);
1324b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
1334b23aff0SRichard Purdie 	int i = 0, j, ret, mod;
1344b23aff0SRichard Purdie 
1354b23aff0SRichard Purdie 	/* We were unregistered */
1364b23aff0SRichard Purdie 	if (!mtd)
1374b23aff0SRichard Purdie 		return;
1384b23aff0SRichard Purdie 
1394b23aff0SRichard Purdie 	mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize;
1404b23aff0SRichard Purdie 	if (mod != 0) {
1414b23aff0SRichard Purdie 		cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE);
1424b23aff0SRichard Purdie 		if (cxt->nextpage > cxt->oops_pages)
1434b23aff0SRichard Purdie 			cxt->nextpage = 0;
1444b23aff0SRichard Purdie 	}
1454b23aff0SRichard Purdie 
1462986bd2aSRichard Purdie 	while (mtd->block_isbad) {
1472986bd2aSRichard Purdie 		ret = mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1482986bd2aSRichard Purdie 		if (!ret)
1492986bd2aSRichard Purdie 			break;
1502986bd2aSRichard Purdie 		if (ret < 0) {
1512986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: block_isbad failed, aborting.\n");
1522986bd2aSRichard Purdie 			return;
1532986bd2aSRichard Purdie 		}
1544b23aff0SRichard Purdie badblock:
1554b23aff0SRichard Purdie 		printk(KERN_WARNING "mtdoops: Bad block at %08x\n",
1564b23aff0SRichard Purdie 				cxt->nextpage * OOPS_PAGE_SIZE);
1574b23aff0SRichard Purdie 		i++;
1584b23aff0SRichard Purdie 		cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE);
1594b23aff0SRichard Purdie 		if (cxt->nextpage > cxt->oops_pages)
1604b23aff0SRichard Purdie 			cxt->nextpage = 0;
1614b23aff0SRichard Purdie 		if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) {
1624b23aff0SRichard Purdie 			printk(KERN_ERR "mtdoops: All blocks bad!\n");
1634b23aff0SRichard Purdie 			return;
1644b23aff0SRichard Purdie 		}
1654b23aff0SRichard Purdie 	}
1664b23aff0SRichard Purdie 
1674b23aff0SRichard Purdie 	for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
1684b23aff0SRichard Purdie 		ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1694b23aff0SRichard Purdie 
1702986bd2aSRichard Purdie 	if (ret >= 0) {
1712986bd2aSRichard Purdie 		printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount);
1722986bd2aSRichard Purdie 		cxt->ready = 1;
1732986bd2aSRichard Purdie 		return;
1744b23aff0SRichard Purdie 	}
1754b23aff0SRichard Purdie 
1762986bd2aSRichard Purdie 	if (mtd->block_markbad && (ret == -EIO)) {
1772986bd2aSRichard Purdie 		ret = mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
1782986bd2aSRichard Purdie 		if (ret < 0) {
1792986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: block_markbad failed, aborting.\n");
1802986bd2aSRichard Purdie 			return;
1812986bd2aSRichard Purdie 		}
1822986bd2aSRichard Purdie 	}
1832986bd2aSRichard Purdie 	goto badblock;
1844b23aff0SRichard Purdie }
1854b23aff0SRichard Purdie 
1866ce0a856SRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work)
1874b23aff0SRichard Purdie {
1884b23aff0SRichard Purdie 	struct mtdoops_context *cxt =
1896ce0a856SRichard Purdie 			container_of(work, struct mtdoops_context, work_write);
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 
1986ce0a856SRichard Purdie 	ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
1996ce0a856SRichard Purdie 					OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
2006ce0a856SRichard Purdie 
2016ce0a856SRichard Purdie 	cxt->writecount = 0;
2026ce0a856SRichard Purdie 
2036ce0a856SRichard Purdie 	if ((retlen != OOPS_PAGE_SIZE) || (ret < 0))
2046ce0a856SRichard Purdie 		printk(KERN_ERR "mtdoops: Write failure at %d (%td of %d written), err %d.\n",
2056ce0a856SRichard Purdie 			cxt->nextpage * OOPS_PAGE_SIZE, retlen,	OOPS_PAGE_SIZE, ret);
2066ce0a856SRichard Purdie 
2076ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2084b23aff0SRichard Purdie }
2094b23aff0SRichard Purdie 
2106ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt)
2114b23aff0SRichard Purdie {
2124b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
2132986bd2aSRichard Purdie 	int ret, page, maxpos = 0;
2144b23aff0SRichard Purdie 	u32 count, maxcount = 0xffffffff;
2154b23aff0SRichard Purdie 	size_t retlen;
2164b23aff0SRichard Purdie 
2174b23aff0SRichard Purdie 	for (page = 0; page < cxt->oops_pages; page++) {
2182986bd2aSRichard Purdie 		ret = mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count);
2192986bd2aSRichard Purdie 		if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) {
2202986bd2aSRichard Purdie 			printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
2212986bd2aSRichard Purdie 				", err %d.\n", page * OOPS_PAGE_SIZE, retlen, ret);
2222986bd2aSRichard Purdie 			continue;
2232986bd2aSRichard Purdie 		}
2242986bd2aSRichard Purdie 
2254b23aff0SRichard Purdie 		if (count == 0xffffffff)
2264b23aff0SRichard Purdie 			continue;
2274b23aff0SRichard Purdie 		if (maxcount == 0xffffffff) {
2284b23aff0SRichard Purdie 			maxcount = count;
2294b23aff0SRichard Purdie 			maxpos = page;
2304b23aff0SRichard Purdie 		} else if ((count < 0x40000000) && (maxcount > 0xc0000000)) {
2314b23aff0SRichard Purdie 			maxcount = count;
2324b23aff0SRichard Purdie 			maxpos = page;
2334b23aff0SRichard Purdie 		} else if ((count > maxcount) && (count < 0xc0000000)) {
2344b23aff0SRichard Purdie 			maxcount = count;
2354b23aff0SRichard Purdie 			maxpos = page;
2364b23aff0SRichard Purdie 		} else if ((count > maxcount) && (count > 0xc0000000)
2374b23aff0SRichard Purdie 					&& (maxcount > 0x80000000)) {
2384b23aff0SRichard Purdie 			maxcount = count;
2394b23aff0SRichard Purdie 			maxpos = page;
2404b23aff0SRichard Purdie 		}
2414b23aff0SRichard Purdie 	}
2424b23aff0SRichard Purdie 	if (maxcount == 0xffffffff) {
2434b23aff0SRichard Purdie 		cxt->nextpage = 0;
2444b23aff0SRichard Purdie 		cxt->nextcount = 1;
2454b23aff0SRichard Purdie 		cxt->ready = 1;
2464b23aff0SRichard Purdie 		printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n",
2474b23aff0SRichard Purdie 				cxt->nextpage, cxt->nextcount);
2486ce0a856SRichard Purdie 		return;
2494b23aff0SRichard Purdie 	}
2504b23aff0SRichard Purdie 
2514b23aff0SRichard Purdie 	cxt->nextpage = maxpos;
2524b23aff0SRichard Purdie 	cxt->nextcount = maxcount;
2534b23aff0SRichard Purdie 
2546ce0a856SRichard Purdie 	mtdoops_inc_counter(cxt);
2554b23aff0SRichard Purdie }
2564b23aff0SRichard Purdie 
2574b23aff0SRichard Purdie 
2584b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd)
2594b23aff0SRichard Purdie {
2604b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
2614b23aff0SRichard Purdie 
2624b23aff0SRichard Purdie 	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
2634b23aff0SRichard Purdie 		return;
2644b23aff0SRichard Purdie 
2654b23aff0SRichard Purdie 	if (mtd->size < (mtd->erasesize * 2)) {
2664b23aff0SRichard Purdie 		printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n",
2674b23aff0SRichard Purdie 				mtd->index);
2684b23aff0SRichard Purdie 		return;
2694b23aff0SRichard Purdie 	}
2704b23aff0SRichard Purdie 
271*79dcd8e9SRichard Purdie 	if (mtd->erasesize < OOPS_PAGE_SIZE) {
272*79dcd8e9SRichard Purdie 		printk(KERN_ERR "Eraseblock size of MTD partition %d too small\n",
273*79dcd8e9SRichard Purdie 				mtd->index);
274*79dcd8e9SRichard Purdie 		return;
275*79dcd8e9SRichard Purdie 	}
276*79dcd8e9SRichard Purdie 
2774b23aff0SRichard Purdie 	cxt->mtd = mtd;
2784b23aff0SRichard Purdie 	cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE;
2794b23aff0SRichard Purdie 
2806ce0a856SRichard Purdie 	find_next_position(cxt);
2814b23aff0SRichard Purdie 
282*79dcd8e9SRichard Purdie 	printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index);
2834b23aff0SRichard Purdie }
2844b23aff0SRichard Purdie 
2854b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd)
2864b23aff0SRichard Purdie {
2874b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
2884b23aff0SRichard Purdie 
2894b23aff0SRichard Purdie 	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
2904b23aff0SRichard Purdie 		return;
2914b23aff0SRichard Purdie 
2924b23aff0SRichard Purdie 	cxt->mtd = NULL;
2934b23aff0SRichard Purdie 	flush_scheduled_work();
2944b23aff0SRichard Purdie }
2954b23aff0SRichard Purdie 
2968691a729SRichard Purdie static void mtdoops_console_sync(void)
2974b23aff0SRichard Purdie {
2988691a729SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
2994b23aff0SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
30047c152b8SRichard Purdie 	unsigned long flags;
3014b23aff0SRichard Purdie 
3026ce0a856SRichard Purdie 	if (!cxt->ready || !mtd || cxt->writecount == 0)
3034b23aff0SRichard Purdie 		return;
3044b23aff0SRichard Purdie 
30547c152b8SRichard Purdie 	/*
30647c152b8SRichard Purdie 	 *  Once ready is 0 and we've held the lock no further writes to the
30747c152b8SRichard Purdie 	 *  buffer will happen
30847c152b8SRichard Purdie 	 */
30947c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
31047c152b8SRichard Purdie 	if (!cxt->ready) {
31147c152b8SRichard Purdie 		spin_unlock_irqrestore(&cxt->writecount_lock, flags);
31247c152b8SRichard Purdie 		return;
31347c152b8SRichard Purdie 	}
3144b23aff0SRichard Purdie 	cxt->ready = 0;
31547c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
3164b23aff0SRichard Purdie 
3176ce0a856SRichard Purdie 	schedule_work(&cxt->work_write);
3184b23aff0SRichard Purdie }
3194b23aff0SRichard Purdie 
3208691a729SRichard Purdie static void
3218691a729SRichard Purdie mtdoops_console_write(struct console *co, const char *s, unsigned int count)
3228691a729SRichard Purdie {
3238691a729SRichard Purdie 	struct mtdoops_context *cxt = co->data;
3248691a729SRichard Purdie 	struct mtd_info *mtd = cxt->mtd;
32547c152b8SRichard Purdie 	unsigned long flags;
3268691a729SRichard Purdie 
3278691a729SRichard Purdie 	if (!oops_in_progress) {
3288691a729SRichard Purdie 		mtdoops_console_sync();
3298691a729SRichard Purdie 		return;
3308691a729SRichard Purdie 	}
3318691a729SRichard Purdie 
3328691a729SRichard Purdie 	if (!cxt->ready || !mtd)
3334b23aff0SRichard Purdie 		return;
3344b23aff0SRichard Purdie 
33547c152b8SRichard Purdie 	/* Locking on writecount ensures sequential writes to the buffer */
33647c152b8SRichard Purdie 	spin_lock_irqsave(&cxt->writecount_lock, flags);
33747c152b8SRichard Purdie 
33847c152b8SRichard Purdie 	/* Check ready status didn't change whilst waiting for the lock */
33947c152b8SRichard Purdie 	if (!cxt->ready)
34047c152b8SRichard Purdie 		return;
34147c152b8SRichard Purdie 
3424b23aff0SRichard Purdie 	if (cxt->writecount == 0) {
3434b23aff0SRichard Purdie 		u32 *stamp = cxt->oops_buf;
3444b23aff0SRichard Purdie 		*stamp = cxt->nextcount;
3454b23aff0SRichard Purdie 		cxt->writecount = 4;
3464b23aff0SRichard Purdie 	}
3474b23aff0SRichard Purdie 
3484b23aff0SRichard Purdie 	if ((count + cxt->writecount) > OOPS_PAGE_SIZE)
3494b23aff0SRichard Purdie 		count = OOPS_PAGE_SIZE - cxt->writecount;
3504b23aff0SRichard Purdie 
351235d6200SPeter Korsgaard 	memcpy(cxt->oops_buf + cxt->writecount, s, count);
352235d6200SPeter Korsgaard 	cxt->writecount += count;
35347c152b8SRichard Purdie 
35447c152b8SRichard Purdie 	spin_unlock_irqrestore(&cxt->writecount_lock, flags);
35547c152b8SRichard Purdie 
35647c152b8SRichard Purdie 	if (cxt->writecount == OOPS_PAGE_SIZE)
35747c152b8SRichard Purdie 		mtdoops_console_sync();
3584b23aff0SRichard Purdie }
3594b23aff0SRichard Purdie 
3604b23aff0SRichard Purdie static int __init mtdoops_console_setup(struct console *co, char *options)
3614b23aff0SRichard Purdie {
3624b23aff0SRichard Purdie 	struct mtdoops_context *cxt = co->data;
3634b23aff0SRichard Purdie 
3644b23aff0SRichard Purdie 	if (cxt->mtd_index != -1)
3654b23aff0SRichard Purdie 		return -EBUSY;
3664b23aff0SRichard Purdie 	if (co->index == -1)
3674b23aff0SRichard Purdie 		return -EINVAL;
3684b23aff0SRichard Purdie 
3694b23aff0SRichard Purdie 	cxt->mtd_index = co->index;
3704b23aff0SRichard Purdie 	return 0;
3714b23aff0SRichard Purdie }
3724b23aff0SRichard Purdie 
3734b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = {
3744b23aff0SRichard Purdie 	.add	= mtdoops_notify_add,
3754b23aff0SRichard Purdie 	.remove	= mtdoops_notify_remove,
3764b23aff0SRichard Purdie };
3774b23aff0SRichard Purdie 
3784b23aff0SRichard Purdie static struct console mtdoops_console = {
3794b23aff0SRichard Purdie 	.name		= "ttyMTD",
3804b23aff0SRichard Purdie 	.write		= mtdoops_console_write,
3814b23aff0SRichard Purdie 	.setup		= mtdoops_console_setup,
3828691a729SRichard Purdie 	.unblank	= mtdoops_console_sync,
3834b23aff0SRichard Purdie 	.index		= -1,
3844b23aff0SRichard Purdie 	.data		= &oops_cxt,
3854b23aff0SRichard Purdie };
3864b23aff0SRichard Purdie 
3874b23aff0SRichard Purdie static int __init mtdoops_console_init(void)
3884b23aff0SRichard Purdie {
3894b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
3904b23aff0SRichard Purdie 
3914b23aff0SRichard Purdie 	cxt->mtd_index = -1;
3924b23aff0SRichard Purdie 	cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE);
3934b23aff0SRichard Purdie 
3944b23aff0SRichard Purdie 	if (!cxt->oops_buf) {
395*79dcd8e9SRichard Purdie 		printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n");
3964b23aff0SRichard Purdie 		return -ENOMEM;
3974b23aff0SRichard Purdie 	}
3984b23aff0SRichard Purdie 
3996ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase);
4006ce0a856SRichard Purdie 	INIT_WORK(&cxt->work_write, mtdoops_workfunc_write);
4014b23aff0SRichard Purdie 
4024b23aff0SRichard Purdie 	register_console(&mtdoops_console);
4034b23aff0SRichard Purdie 	register_mtd_user(&mtdoops_notifier);
4044b23aff0SRichard Purdie 	return 0;
4054b23aff0SRichard Purdie }
4064b23aff0SRichard Purdie 
4074b23aff0SRichard Purdie static void __exit mtdoops_console_exit(void)
4084b23aff0SRichard Purdie {
4094b23aff0SRichard Purdie 	struct mtdoops_context *cxt = &oops_cxt;
4104b23aff0SRichard Purdie 
4114b23aff0SRichard Purdie 	unregister_mtd_user(&mtdoops_notifier);
4124b23aff0SRichard Purdie 	unregister_console(&mtdoops_console);
4134b23aff0SRichard Purdie 	vfree(cxt->oops_buf);
4144b23aff0SRichard Purdie }
4154b23aff0SRichard Purdie 
4164b23aff0SRichard Purdie 
4174b23aff0SRichard Purdie subsys_initcall(mtdoops_console_init);
4184b23aff0SRichard Purdie module_exit(mtdoops_console_exit);
4194b23aff0SRichard Purdie 
4204b23aff0SRichard Purdie MODULE_LICENSE("GPL");
4214b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
4224b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");
423