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 36f0482ee3SRichard Purdie #define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 374b23aff0SRichard Purdie #define OOPS_PAGE_SIZE 4096 384b23aff0SRichard Purdie 397903cbabSAdrian Bunk static struct mtdoops_context { 404b23aff0SRichard Purdie int mtd_index; 416ce0a856SRichard Purdie struct work_struct work_erase; 426ce0a856SRichard Purdie struct work_struct work_write; 434b23aff0SRichard Purdie struct mtd_info *mtd; 444b23aff0SRichard Purdie int oops_pages; 454b23aff0SRichard Purdie int nextpage; 464b23aff0SRichard Purdie int nextcount; 47*be95745fSSimon Kagstrom unsigned long *oops_page_used; 48e2a0f25bSAdrian Hunter char *name; 494b23aff0SRichard Purdie 504b23aff0SRichard Purdie void *oops_buf; 5147c152b8SRichard Purdie 5247c152b8SRichard Purdie /* writecount and disabling ready are spin lock protected */ 5347c152b8SRichard Purdie spinlock_t writecount_lock; 544b23aff0SRichard Purdie int ready; 554b23aff0SRichard Purdie int writecount; 564b23aff0SRichard Purdie } oops_cxt; 574b23aff0SRichard Purdie 58*be95745fSSimon Kagstrom static void mark_page_used(struct mtdoops_context *cxt, int page) 59*be95745fSSimon Kagstrom { 60*be95745fSSimon Kagstrom set_bit(page, cxt->oops_page_used); 61*be95745fSSimon Kagstrom } 62*be95745fSSimon Kagstrom 63*be95745fSSimon Kagstrom static void mark_page_unused(struct mtdoops_context *cxt, int page) 64*be95745fSSimon Kagstrom { 65*be95745fSSimon Kagstrom clear_bit(page, cxt->oops_page_used); 66*be95745fSSimon Kagstrom } 67*be95745fSSimon Kagstrom 68*be95745fSSimon Kagstrom static int page_is_used(struct mtdoops_context *cxt, int page) 69*be95745fSSimon Kagstrom { 70*be95745fSSimon Kagstrom return test_bit(page, cxt->oops_page_used); 71*be95745fSSimon Kagstrom } 72*be95745fSSimon Kagstrom 734b23aff0SRichard Purdie static void mtdoops_erase_callback(struct erase_info *done) 744b23aff0SRichard Purdie { 754b23aff0SRichard Purdie wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; 764b23aff0SRichard Purdie wake_up(wait_q); 774b23aff0SRichard Purdie } 784b23aff0SRichard Purdie 79*be95745fSSimon Kagstrom static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset) 804b23aff0SRichard Purdie { 81*be95745fSSimon Kagstrom struct mtd_info *mtd = cxt->mtd; 82*be95745fSSimon Kagstrom u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize; 83*be95745fSSimon Kagstrom u32 start_page = start_page_offset / OOPS_PAGE_SIZE; 84*be95745fSSimon Kagstrom u32 erase_pages = mtd->erasesize / OOPS_PAGE_SIZE; 854b23aff0SRichard Purdie struct erase_info erase; 864b23aff0SRichard Purdie DECLARE_WAITQUEUE(wait, current); 874b23aff0SRichard Purdie wait_queue_head_t wait_q; 884b23aff0SRichard Purdie int ret; 89*be95745fSSimon Kagstrom int page; 904b23aff0SRichard Purdie 914b23aff0SRichard Purdie init_waitqueue_head(&wait_q); 924b23aff0SRichard Purdie erase.mtd = mtd; 934b23aff0SRichard Purdie erase.callback = mtdoops_erase_callback; 944b23aff0SRichard Purdie erase.addr = offset; 954b23aff0SRichard Purdie erase.len = mtd->erasesize; 964b23aff0SRichard Purdie erase.priv = (u_long)&wait_q; 974b23aff0SRichard Purdie 984b23aff0SRichard Purdie set_current_state(TASK_INTERRUPTIBLE); 994b23aff0SRichard Purdie add_wait_queue(&wait_q, &wait); 1004b23aff0SRichard Purdie 1014b23aff0SRichard Purdie ret = mtd->erase(mtd, &erase); 1024b23aff0SRichard Purdie if (ret) { 1034b23aff0SRichard Purdie set_current_state(TASK_RUNNING); 1044b23aff0SRichard Purdie remove_wait_queue(&wait_q, &wait); 105a15b124fSArtem Bityutskiy printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", 106a15b124fSArtem Bityutskiy (unsigned long long)erase.addr, 107a15b124fSArtem Bityutskiy (unsigned long long)erase.len, mtd->name); 1084b23aff0SRichard Purdie return ret; 1094b23aff0SRichard Purdie } 1104b23aff0SRichard Purdie 1114b23aff0SRichard Purdie schedule(); /* Wait for erase to finish. */ 1124b23aff0SRichard Purdie remove_wait_queue(&wait_q, &wait); 1134b23aff0SRichard Purdie 114*be95745fSSimon Kagstrom /* Mark pages as unused */ 115*be95745fSSimon Kagstrom for (page = start_page; page < start_page + erase_pages; page++) 116*be95745fSSimon Kagstrom mark_page_unused(cxt, page); 117*be95745fSSimon Kagstrom 1184b23aff0SRichard Purdie return 0; 1194b23aff0SRichard Purdie } 1204b23aff0SRichard Purdie 1216ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt) 1224b23aff0SRichard Purdie { 1234b23aff0SRichard Purdie cxt->nextpage++; 124ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1254b23aff0SRichard Purdie cxt->nextpage = 0; 1264b23aff0SRichard Purdie cxt->nextcount++; 1274b23aff0SRichard Purdie if (cxt->nextcount == 0xffffffff) 1284b23aff0SRichard Purdie cxt->nextcount = 0; 1294b23aff0SRichard Purdie 130*be95745fSSimon Kagstrom if (page_is_used(cxt, cxt->nextpage)) { 1316ce0a856SRichard Purdie schedule_work(&cxt->work_erase); 1326ce0a856SRichard Purdie return; 1336ce0a856SRichard Purdie } 1344b23aff0SRichard Purdie 135a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n", 1364b23aff0SRichard Purdie cxt->nextpage, cxt->nextcount); 1374b23aff0SRichard Purdie cxt->ready = 1; 1384b23aff0SRichard Purdie } 1394b23aff0SRichard Purdie 1406ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */ 1416ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work) 1424b23aff0SRichard Purdie { 1436ce0a856SRichard Purdie struct mtdoops_context *cxt = 1446ce0a856SRichard Purdie container_of(work, struct mtdoops_context, work_erase); 1454b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 1464b23aff0SRichard Purdie int i = 0, j, ret, mod; 1474b23aff0SRichard Purdie 1484b23aff0SRichard Purdie /* We were unregistered */ 1494b23aff0SRichard Purdie if (!mtd) 1504b23aff0SRichard Purdie return; 1514b23aff0SRichard Purdie 1524b23aff0SRichard Purdie mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize; 1534b23aff0SRichard Purdie if (mod != 0) { 1544b23aff0SRichard Purdie cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE); 155ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1564b23aff0SRichard Purdie cxt->nextpage = 0; 1574b23aff0SRichard Purdie } 1584b23aff0SRichard Purdie 1592986bd2aSRichard Purdie while (mtd->block_isbad) { 1602986bd2aSRichard Purdie ret = mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); 1612986bd2aSRichard Purdie if (!ret) 1622986bd2aSRichard Purdie break; 1632986bd2aSRichard Purdie if (ret < 0) { 164a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: block_isbad failed, aborting\n"); 1652986bd2aSRichard Purdie return; 1662986bd2aSRichard Purdie } 1674b23aff0SRichard Purdie badblock: 168a15b124fSArtem Bityutskiy printk(KERN_WARNING "mtdoops: bad block at %08x\n", 1694b23aff0SRichard Purdie cxt->nextpage * OOPS_PAGE_SIZE); 1704b23aff0SRichard Purdie i++; 1714b23aff0SRichard Purdie cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE); 172ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1734b23aff0SRichard Purdie cxt->nextpage = 0; 174a15b124fSArtem Bityutskiy if (i == cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE)) { 175a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: all blocks bad!\n"); 1764b23aff0SRichard Purdie return; 1774b23aff0SRichard Purdie } 1784b23aff0SRichard Purdie } 1794b23aff0SRichard Purdie 1804b23aff0SRichard Purdie for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) 181*be95745fSSimon Kagstrom ret = mtdoops_erase_block(cxt, cxt->nextpage * OOPS_PAGE_SIZE); 1824b23aff0SRichard Purdie 1832986bd2aSRichard Purdie if (ret >= 0) { 184a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d\n", 185a15b124fSArtem Bityutskiy cxt->nextpage, cxt->nextcount); 1862986bd2aSRichard Purdie cxt->ready = 1; 1872986bd2aSRichard Purdie return; 1884b23aff0SRichard Purdie } 1894b23aff0SRichard Purdie 190a15b124fSArtem Bityutskiy if (mtd->block_markbad && ret == -EIO) { 1912986bd2aSRichard Purdie ret = mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); 1922986bd2aSRichard Purdie if (ret < 0) { 193a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n"); 1942986bd2aSRichard Purdie return; 1952986bd2aSRichard Purdie } 1962986bd2aSRichard Purdie } 1972986bd2aSRichard Purdie goto badblock; 1984b23aff0SRichard Purdie } 1994b23aff0SRichard Purdie 200621e4f8eSRichard Purdie static void mtdoops_write(struct mtdoops_context *cxt, int panic) 2014b23aff0SRichard Purdie { 2026ce0a856SRichard Purdie struct mtd_info *mtd = cxt->mtd; 2036ce0a856SRichard Purdie size_t retlen; 2046ce0a856SRichard Purdie int ret; 2054b23aff0SRichard Purdie 2066ce0a856SRichard Purdie if (cxt->writecount < OOPS_PAGE_SIZE) 2076ce0a856SRichard Purdie memset(cxt->oops_buf + cxt->writecount, 0xff, 2086ce0a856SRichard Purdie OOPS_PAGE_SIZE - cxt->writecount); 2096ce0a856SRichard Purdie 210621e4f8eSRichard Purdie if (panic) 211621e4f8eSRichard Purdie ret = mtd->panic_write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 212621e4f8eSRichard Purdie OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); 213621e4f8eSRichard Purdie else 2146ce0a856SRichard Purdie ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 2156ce0a856SRichard Purdie OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); 2166ce0a856SRichard Purdie 2176ce0a856SRichard Purdie cxt->writecount = 0; 2186ce0a856SRichard Purdie 219a15b124fSArtem Bityutskiy if (retlen != OOPS_PAGE_SIZE || ret < 0) 220a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: write failure at %d (%td of %d written), error %d\n", 2216ce0a856SRichard Purdie cxt->nextpage * OOPS_PAGE_SIZE, retlen, OOPS_PAGE_SIZE, ret); 222*be95745fSSimon Kagstrom mark_page_used(cxt, cxt->nextpage); 2236ce0a856SRichard Purdie 2246ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 2254b23aff0SRichard Purdie } 2264b23aff0SRichard Purdie 227621e4f8eSRichard Purdie 228621e4f8eSRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work) 229621e4f8eSRichard Purdie { 230621e4f8eSRichard Purdie struct mtdoops_context *cxt = 231621e4f8eSRichard Purdie container_of(work, struct mtdoops_context, work_write); 232621e4f8eSRichard Purdie 233621e4f8eSRichard Purdie mtdoops_write(cxt, 0); 234621e4f8eSRichard Purdie } 235621e4f8eSRichard Purdie 2366ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt) 2374b23aff0SRichard Purdie { 2384b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 2392986bd2aSRichard Purdie int ret, page, maxpos = 0; 240f0482ee3SRichard Purdie u32 count[2], maxcount = 0xffffffff; 2414b23aff0SRichard Purdie size_t retlen; 2424b23aff0SRichard Purdie 2434b23aff0SRichard Purdie for (page = 0; page < cxt->oops_pages; page++) { 244*be95745fSSimon Kagstrom /* Assume the page is used */ 245*be95745fSSimon Kagstrom mark_page_used(cxt, page); 246f0482ee3SRichard Purdie ret = mtd->read(mtd, page * OOPS_PAGE_SIZE, 8, &retlen, (u_char *) &count[0]); 247a15b124fSArtem Bityutskiy if (retlen != 8 || (ret < 0 && ret != -EUCLEAN)) { 248a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: read failure at %d (%td of 8 read), err %d\n", 249a15b124fSArtem Bityutskiy page * OOPS_PAGE_SIZE, retlen, ret); 2502986bd2aSRichard Purdie continue; 2512986bd2aSRichard Purdie } 2522986bd2aSRichard Purdie 253*be95745fSSimon Kagstrom if (count[0] == 0xffffffff && count[1] == 0xffffffff) 254*be95745fSSimon Kagstrom mark_page_unused(cxt, page); 255f0482ee3SRichard Purdie if (count[1] != MTDOOPS_KERNMSG_MAGIC) 256f0482ee3SRichard Purdie continue; 257f0482ee3SRichard Purdie if (count[0] == 0xffffffff) 2584b23aff0SRichard Purdie continue; 2594b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 260f0482ee3SRichard Purdie maxcount = count[0]; 2614b23aff0SRichard Purdie maxpos = page; 262a15b124fSArtem Bityutskiy } else if (count[0] < 0x40000000 && maxcount > 0xc0000000) { 263f0482ee3SRichard Purdie maxcount = count[0]; 2644b23aff0SRichard Purdie maxpos = page; 265a15b124fSArtem Bityutskiy } else if (count[0] > maxcount && count[0] < 0xc0000000) { 266f0482ee3SRichard Purdie maxcount = count[0]; 2674b23aff0SRichard Purdie maxpos = page; 268a15b124fSArtem Bityutskiy } else if (count[0] > maxcount && count[0] > 0xc0000000 269a15b124fSArtem Bityutskiy && maxcount > 0x80000000) { 270f0482ee3SRichard Purdie maxcount = count[0]; 2714b23aff0SRichard Purdie maxpos = page; 2724b23aff0SRichard Purdie } 2734b23aff0SRichard Purdie } 2744b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 2754b23aff0SRichard Purdie cxt->nextpage = 0; 2764b23aff0SRichard Purdie cxt->nextcount = 1; 27743b5693dSRichard Purdie schedule_work(&cxt->work_erase); 2786ce0a856SRichard Purdie return; 2794b23aff0SRichard Purdie } 2804b23aff0SRichard Purdie 2814b23aff0SRichard Purdie cxt->nextpage = maxpos; 2824b23aff0SRichard Purdie cxt->nextcount = maxcount; 2834b23aff0SRichard Purdie 2846ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 2854b23aff0SRichard Purdie } 2864b23aff0SRichard Purdie 2874b23aff0SRichard Purdie 2884b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd) 2894b23aff0SRichard Purdie { 2904b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 291*be95745fSSimon Kagstrom u64 mtdoops_pages = mtd->size; 292*be95745fSSimon Kagstrom 293*be95745fSSimon Kagstrom do_div(mtdoops_pages, OOPS_PAGE_SIZE); 2944b23aff0SRichard Purdie 295e2a0f25bSAdrian Hunter if (cxt->name && !strcmp(mtd->name, cxt->name)) 296e2a0f25bSAdrian Hunter cxt->mtd_index = mtd->index; 297e2a0f25bSAdrian Hunter 298a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 2994b23aff0SRichard Purdie return; 3004b23aff0SRichard Purdie 301a15b124fSArtem Bityutskiy if (mtd->size < mtd->erasesize * 2) { 302a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n", 3034b23aff0SRichard Purdie mtd->index); 3044b23aff0SRichard Purdie return; 3054b23aff0SRichard Purdie } 3064b23aff0SRichard Purdie 30779dcd8e9SRichard Purdie if (mtd->erasesize < OOPS_PAGE_SIZE) { 308a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n", 30979dcd8e9SRichard Purdie mtd->index); 31079dcd8e9SRichard Purdie return; 31179dcd8e9SRichard Purdie } 31279dcd8e9SRichard Purdie 313*be95745fSSimon Kagstrom /* oops_page_used is a bit field */ 314*be95745fSSimon Kagstrom cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages, 315*be95745fSSimon Kagstrom BITS_PER_LONG)); 316*be95745fSSimon Kagstrom if (!cxt->oops_page_used) { 317*be95745fSSimon Kagstrom printk(KERN_ERR "Could not allocate page array\n"); 318*be95745fSSimon Kagstrom return; 319*be95745fSSimon Kagstrom } 3204b23aff0SRichard Purdie cxt->mtd = mtd; 32169423d99SAdrian Hunter if (mtd->size > INT_MAX) 32269423d99SAdrian Hunter cxt->oops_pages = INT_MAX / OOPS_PAGE_SIZE; 32369423d99SAdrian Hunter else 32469423d99SAdrian Hunter cxt->oops_pages = (int)mtd->size / OOPS_PAGE_SIZE; 3254b23aff0SRichard Purdie 3266ce0a856SRichard Purdie find_next_position(cxt); 3274b23aff0SRichard Purdie 32879dcd8e9SRichard Purdie printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); 3294b23aff0SRichard Purdie } 3304b23aff0SRichard Purdie 3314b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd) 3324b23aff0SRichard Purdie { 3334b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3344b23aff0SRichard Purdie 335a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 3364b23aff0SRichard Purdie return; 3374b23aff0SRichard Purdie 3384b23aff0SRichard Purdie cxt->mtd = NULL; 3394b23aff0SRichard Purdie flush_scheduled_work(); 3404b23aff0SRichard Purdie } 3414b23aff0SRichard Purdie 3428691a729SRichard Purdie static void mtdoops_console_sync(void) 3434b23aff0SRichard Purdie { 3448691a729SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3454b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 34647c152b8SRichard Purdie unsigned long flags; 3474b23aff0SRichard Purdie 3486ce0a856SRichard Purdie if (!cxt->ready || !mtd || cxt->writecount == 0) 3494b23aff0SRichard Purdie return; 3504b23aff0SRichard Purdie 35147c152b8SRichard Purdie /* 35247c152b8SRichard Purdie * Once ready is 0 and we've held the lock no further writes to the 35347c152b8SRichard Purdie * buffer will happen 35447c152b8SRichard Purdie */ 35547c152b8SRichard Purdie spin_lock_irqsave(&cxt->writecount_lock, flags); 35647c152b8SRichard Purdie if (!cxt->ready) { 35747c152b8SRichard Purdie spin_unlock_irqrestore(&cxt->writecount_lock, flags); 35847c152b8SRichard Purdie return; 35947c152b8SRichard Purdie } 3604b23aff0SRichard Purdie cxt->ready = 0; 36147c152b8SRichard Purdie spin_unlock_irqrestore(&cxt->writecount_lock, flags); 3624b23aff0SRichard Purdie 363621e4f8eSRichard Purdie if (mtd->panic_write && in_interrupt()) 364621e4f8eSRichard Purdie /* Interrupt context, we're going to panic so try and log */ 365621e4f8eSRichard Purdie mtdoops_write(cxt, 1); 366621e4f8eSRichard Purdie else 3676ce0a856SRichard Purdie schedule_work(&cxt->work_write); 3684b23aff0SRichard Purdie } 3694b23aff0SRichard Purdie 3708691a729SRichard Purdie static void 3718691a729SRichard Purdie mtdoops_console_write(struct console *co, const char *s, unsigned int count) 3728691a729SRichard Purdie { 3738691a729SRichard Purdie struct mtdoops_context *cxt = co->data; 3748691a729SRichard Purdie struct mtd_info *mtd = cxt->mtd; 37547c152b8SRichard Purdie unsigned long flags; 3768691a729SRichard Purdie 3778691a729SRichard Purdie if (!oops_in_progress) { 3788691a729SRichard Purdie mtdoops_console_sync(); 3798691a729SRichard Purdie return; 3808691a729SRichard Purdie } 3818691a729SRichard Purdie 3828691a729SRichard Purdie if (!cxt->ready || !mtd) 3834b23aff0SRichard Purdie return; 3844b23aff0SRichard Purdie 38547c152b8SRichard Purdie /* Locking on writecount ensures sequential writes to the buffer */ 38647c152b8SRichard Purdie spin_lock_irqsave(&cxt->writecount_lock, flags); 38747c152b8SRichard Purdie 38847c152b8SRichard Purdie /* Check ready status didn't change whilst waiting for the lock */ 38948ec00acSAdrian Hunter if (!cxt->ready) { 39048ec00acSAdrian Hunter spin_unlock_irqrestore(&cxt->writecount_lock, flags); 39147c152b8SRichard Purdie return; 39248ec00acSAdrian Hunter } 39347c152b8SRichard Purdie 3944b23aff0SRichard Purdie if (cxt->writecount == 0) { 3954b23aff0SRichard Purdie u32 *stamp = cxt->oops_buf; 396f0482ee3SRichard Purdie *stamp++ = cxt->nextcount; 397f0482ee3SRichard Purdie *stamp = MTDOOPS_KERNMSG_MAGIC; 398f0482ee3SRichard Purdie cxt->writecount = 8; 3994b23aff0SRichard Purdie } 4004b23aff0SRichard Purdie 401a15b124fSArtem Bityutskiy if (count + cxt->writecount > OOPS_PAGE_SIZE) 4024b23aff0SRichard Purdie count = OOPS_PAGE_SIZE - cxt->writecount; 4034b23aff0SRichard Purdie 404235d6200SPeter Korsgaard memcpy(cxt->oops_buf + cxt->writecount, s, count); 405235d6200SPeter Korsgaard cxt->writecount += count; 40647c152b8SRichard Purdie 40747c152b8SRichard Purdie spin_unlock_irqrestore(&cxt->writecount_lock, flags); 40847c152b8SRichard Purdie 40947c152b8SRichard Purdie if (cxt->writecount == OOPS_PAGE_SIZE) 41047c152b8SRichard Purdie mtdoops_console_sync(); 4114b23aff0SRichard Purdie } 4124b23aff0SRichard Purdie 4134b23aff0SRichard Purdie static int __init mtdoops_console_setup(struct console *co, char *options) 4144b23aff0SRichard Purdie { 4154b23aff0SRichard Purdie struct mtdoops_context *cxt = co->data; 4164b23aff0SRichard Purdie 417e2a0f25bSAdrian Hunter if (cxt->mtd_index != -1 || cxt->name) 4184b23aff0SRichard Purdie return -EBUSY; 419e2a0f25bSAdrian Hunter if (options) { 420e2a0f25bSAdrian Hunter cxt->name = kstrdup(options, GFP_KERNEL); 421e2a0f25bSAdrian Hunter return 0; 422e2a0f25bSAdrian Hunter } 4234b23aff0SRichard Purdie if (co->index == -1) 4244b23aff0SRichard Purdie return -EINVAL; 4254b23aff0SRichard Purdie 4264b23aff0SRichard Purdie cxt->mtd_index = co->index; 4274b23aff0SRichard Purdie return 0; 4284b23aff0SRichard Purdie } 4294b23aff0SRichard Purdie 4304b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = { 4314b23aff0SRichard Purdie .add = mtdoops_notify_add, 4324b23aff0SRichard Purdie .remove = mtdoops_notify_remove, 4334b23aff0SRichard Purdie }; 4344b23aff0SRichard Purdie 4354b23aff0SRichard Purdie static struct console mtdoops_console = { 4364b23aff0SRichard Purdie .name = "ttyMTD", 4374b23aff0SRichard Purdie .write = mtdoops_console_write, 4384b23aff0SRichard Purdie .setup = mtdoops_console_setup, 4398691a729SRichard Purdie .unblank = mtdoops_console_sync, 4404b23aff0SRichard Purdie .index = -1, 4414b23aff0SRichard Purdie .data = &oops_cxt, 4424b23aff0SRichard Purdie }; 4434b23aff0SRichard Purdie 4444b23aff0SRichard Purdie static int __init mtdoops_console_init(void) 4454b23aff0SRichard Purdie { 4464b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 4474b23aff0SRichard Purdie 4484b23aff0SRichard Purdie cxt->mtd_index = -1; 4494b23aff0SRichard Purdie cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE); 4504b23aff0SRichard Purdie if (!cxt->oops_buf) { 451a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n"); 4524b23aff0SRichard Purdie return -ENOMEM; 4534b23aff0SRichard Purdie } 4544b23aff0SRichard Purdie 455a15b124fSArtem Bityutskiy spin_lock_init(&cxt->writecount_lock); 4566ce0a856SRichard Purdie INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); 4576ce0a856SRichard Purdie INIT_WORK(&cxt->work_write, mtdoops_workfunc_write); 4584b23aff0SRichard Purdie 4594b23aff0SRichard Purdie register_console(&mtdoops_console); 4604b23aff0SRichard Purdie register_mtd_user(&mtdoops_notifier); 4614b23aff0SRichard Purdie return 0; 4624b23aff0SRichard Purdie } 4634b23aff0SRichard Purdie 4644b23aff0SRichard Purdie static void __exit mtdoops_console_exit(void) 4654b23aff0SRichard Purdie { 4664b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 4674b23aff0SRichard Purdie 4684b23aff0SRichard Purdie unregister_mtd_user(&mtdoops_notifier); 4694b23aff0SRichard Purdie unregister_console(&mtdoops_console); 470e2a0f25bSAdrian Hunter kfree(cxt->name); 4714b23aff0SRichard Purdie vfree(cxt->oops_buf); 472*be95745fSSimon Kagstrom vfree(cxt->oops_page_used); 4734b23aff0SRichard Purdie } 4744b23aff0SRichard Purdie 4754b23aff0SRichard Purdie 4764b23aff0SRichard Purdie subsys_initcall(mtdoops_console_init); 4774b23aff0SRichard Purdie module_exit(mtdoops_console_exit); 4784b23aff0SRichard Purdie 4794b23aff0SRichard Purdie MODULE_LICENSE("GPL"); 4804b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); 4814b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); 482