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