14b23aff0SRichard Purdie /* 24b23aff0SRichard Purdie * MTD Oops/Panic logger 34b23aff0SRichard Purdie * 4a1452a37SDavid Woodhouse * Copyright © 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> 32f9f7dd22SDavid Woodhouse #include <linux/interrupt.h> 334b23aff0SRichard Purdie #include <linux/mtd/mtd.h> 342e386e4bSSimon Kagstrom #include <linux/kmsg_dump.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 402e386e4bSSimon Kagstrom #define MTDOOPS_HEADER_SIZE 8 419507b0c8SSimon Kagstrom 429507b0c8SSimon Kagstrom static unsigned long record_size = 4096; 439507b0c8SSimon Kagstrom module_param(record_size, ulong, 0400); 449507b0c8SSimon Kagstrom MODULE_PARM_DESC(record_size, 459507b0c8SSimon Kagstrom "record size for MTD OOPS pages in bytes (default 4096)"); 464b23aff0SRichard Purdie 472e386e4bSSimon Kagstrom static char mtddev[80]; 482e386e4bSSimon Kagstrom module_param_string(mtddev, mtddev, 80, 0400); 492e386e4bSSimon Kagstrom MODULE_PARM_DESC(mtddev, 502e386e4bSSimon Kagstrom "name or index number of the MTD device to use"); 512e386e4bSSimon Kagstrom 522e386e4bSSimon Kagstrom static int dump_oops = 1; 532e386e4bSSimon Kagstrom module_param(dump_oops, int, 0600); 542e386e4bSSimon Kagstrom MODULE_PARM_DESC(dump_oops, 552e386e4bSSimon Kagstrom "set to 1 to dump oopses, 0 to only dump panics (default 1)"); 562e386e4bSSimon Kagstrom 577903cbabSAdrian Bunk static struct mtdoops_context { 582e386e4bSSimon Kagstrom struct kmsg_dumper dump; 592e386e4bSSimon Kagstrom 604b23aff0SRichard Purdie int mtd_index; 616ce0a856SRichard Purdie struct work_struct work_erase; 626ce0a856SRichard Purdie struct work_struct work_write; 634b23aff0SRichard Purdie struct mtd_info *mtd; 644b23aff0SRichard Purdie int oops_pages; 654b23aff0SRichard Purdie int nextpage; 664b23aff0SRichard Purdie int nextcount; 67be95745fSSimon Kagstrom unsigned long *oops_page_used; 684b23aff0SRichard Purdie 694b23aff0SRichard Purdie void *oops_buf; 704b23aff0SRichard Purdie } oops_cxt; 714b23aff0SRichard Purdie 72be95745fSSimon Kagstrom static void mark_page_used(struct mtdoops_context *cxt, int page) 73be95745fSSimon Kagstrom { 74be95745fSSimon Kagstrom set_bit(page, cxt->oops_page_used); 75be95745fSSimon Kagstrom } 76be95745fSSimon Kagstrom 77be95745fSSimon Kagstrom static void mark_page_unused(struct mtdoops_context *cxt, int page) 78be95745fSSimon Kagstrom { 79be95745fSSimon Kagstrom clear_bit(page, cxt->oops_page_used); 80be95745fSSimon Kagstrom } 81be95745fSSimon Kagstrom 82be95745fSSimon Kagstrom static int page_is_used(struct mtdoops_context *cxt, int page) 83be95745fSSimon Kagstrom { 84be95745fSSimon Kagstrom return test_bit(page, cxt->oops_page_used); 85be95745fSSimon Kagstrom } 86be95745fSSimon Kagstrom 874b23aff0SRichard Purdie static void mtdoops_erase_callback(struct erase_info *done) 884b23aff0SRichard Purdie { 894b23aff0SRichard Purdie wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; 904b23aff0SRichard Purdie wake_up(wait_q); 914b23aff0SRichard Purdie } 924b23aff0SRichard Purdie 93be95745fSSimon Kagstrom static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset) 944b23aff0SRichard Purdie { 95be95745fSSimon Kagstrom struct mtd_info *mtd = cxt->mtd; 96be95745fSSimon Kagstrom u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize; 979507b0c8SSimon Kagstrom u32 start_page = start_page_offset / record_size; 989507b0c8SSimon Kagstrom u32 erase_pages = mtd->erasesize / record_size; 994b23aff0SRichard Purdie struct erase_info erase; 1004b23aff0SRichard Purdie DECLARE_WAITQUEUE(wait, current); 1014b23aff0SRichard Purdie wait_queue_head_t wait_q; 1024b23aff0SRichard Purdie int ret; 103be95745fSSimon Kagstrom int page; 1044b23aff0SRichard Purdie 1054b23aff0SRichard Purdie init_waitqueue_head(&wait_q); 1064b23aff0SRichard Purdie erase.mtd = mtd; 1074b23aff0SRichard Purdie erase.callback = mtdoops_erase_callback; 1084b23aff0SRichard Purdie erase.addr = offset; 1094b23aff0SRichard Purdie erase.len = mtd->erasesize; 1104b23aff0SRichard Purdie erase.priv = (u_long)&wait_q; 1114b23aff0SRichard Purdie 1124b23aff0SRichard Purdie set_current_state(TASK_INTERRUPTIBLE); 1134b23aff0SRichard Purdie add_wait_queue(&wait_q, &wait); 1144b23aff0SRichard Purdie 1154b23aff0SRichard Purdie ret = mtd->erase(mtd, &erase); 1164b23aff0SRichard Purdie if (ret) { 1174b23aff0SRichard Purdie set_current_state(TASK_RUNNING); 1184b23aff0SRichard Purdie remove_wait_queue(&wait_q, &wait); 119a15b124fSArtem Bityutskiy printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", 120a15b124fSArtem Bityutskiy (unsigned long long)erase.addr, 1212e386e4bSSimon Kagstrom (unsigned long long)erase.len, mtddev); 1224b23aff0SRichard Purdie return ret; 1234b23aff0SRichard Purdie } 1244b23aff0SRichard Purdie 1254b23aff0SRichard Purdie schedule(); /* Wait for erase to finish. */ 1264b23aff0SRichard Purdie remove_wait_queue(&wait_q, &wait); 1274b23aff0SRichard Purdie 128be95745fSSimon Kagstrom /* Mark pages as unused */ 129be95745fSSimon Kagstrom for (page = start_page; page < start_page + erase_pages; page++) 130be95745fSSimon Kagstrom mark_page_unused(cxt, page); 131be95745fSSimon Kagstrom 1324b23aff0SRichard Purdie return 0; 1334b23aff0SRichard Purdie } 1344b23aff0SRichard Purdie 1356ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt) 1364b23aff0SRichard Purdie { 1374b23aff0SRichard Purdie cxt->nextpage++; 138ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1394b23aff0SRichard Purdie cxt->nextpage = 0; 1404b23aff0SRichard Purdie cxt->nextcount++; 1414b23aff0SRichard Purdie if (cxt->nextcount == 0xffffffff) 1424b23aff0SRichard Purdie cxt->nextcount = 0; 1434b23aff0SRichard Purdie 144be95745fSSimon Kagstrom if (page_is_used(cxt, cxt->nextpage)) { 1456ce0a856SRichard Purdie schedule_work(&cxt->work_erase); 1466ce0a856SRichard Purdie return; 1476ce0a856SRichard Purdie } 1484b23aff0SRichard Purdie 149a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n", 1504b23aff0SRichard Purdie cxt->nextpage, cxt->nextcount); 1514b23aff0SRichard Purdie } 1524b23aff0SRichard Purdie 1536ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */ 1546ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work) 1554b23aff0SRichard Purdie { 1566ce0a856SRichard Purdie struct mtdoops_context *cxt = 1576ce0a856SRichard Purdie container_of(work, struct mtdoops_context, work_erase); 1584b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 1594b23aff0SRichard Purdie int i = 0, j, ret, mod; 1604b23aff0SRichard Purdie 1614b23aff0SRichard Purdie /* We were unregistered */ 1624b23aff0SRichard Purdie if (!mtd) 1634b23aff0SRichard Purdie return; 1644b23aff0SRichard Purdie 1659507b0c8SSimon Kagstrom mod = (cxt->nextpage * record_size) % mtd->erasesize; 1664b23aff0SRichard Purdie if (mod != 0) { 1679507b0c8SSimon Kagstrom cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size); 168ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1694b23aff0SRichard Purdie cxt->nextpage = 0; 1704b23aff0SRichard Purdie } 1714b23aff0SRichard Purdie 1722986bd2aSRichard Purdie while (mtd->block_isbad) { 1739507b0c8SSimon Kagstrom ret = mtd->block_isbad(mtd, cxt->nextpage * record_size); 1742986bd2aSRichard Purdie if (!ret) 1752986bd2aSRichard Purdie break; 1762986bd2aSRichard Purdie if (ret < 0) { 177a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: block_isbad failed, aborting\n"); 1782986bd2aSRichard Purdie return; 1792986bd2aSRichard Purdie } 1804b23aff0SRichard Purdie badblock: 1819507b0c8SSimon Kagstrom printk(KERN_WARNING "mtdoops: bad block at %08lx\n", 1829507b0c8SSimon Kagstrom cxt->nextpage * record_size); 1834b23aff0SRichard Purdie i++; 1849507b0c8SSimon Kagstrom cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size); 185ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1864b23aff0SRichard Purdie cxt->nextpage = 0; 1879507b0c8SSimon Kagstrom if (i == cxt->oops_pages / (mtd->erasesize / record_size)) { 188a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: all blocks bad!\n"); 1894b23aff0SRichard Purdie return; 1904b23aff0SRichard Purdie } 1914b23aff0SRichard Purdie } 1924b23aff0SRichard Purdie 1934b23aff0SRichard Purdie for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) 1949507b0c8SSimon Kagstrom ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size); 1954b23aff0SRichard Purdie 1962986bd2aSRichard Purdie if (ret >= 0) { 197a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d\n", 198a15b124fSArtem Bityutskiy cxt->nextpage, cxt->nextcount); 1992986bd2aSRichard Purdie return; 2004b23aff0SRichard Purdie } 2014b23aff0SRichard Purdie 202a15b124fSArtem Bityutskiy if (mtd->block_markbad && ret == -EIO) { 2039507b0c8SSimon Kagstrom ret = mtd->block_markbad(mtd, cxt->nextpage * record_size); 2042986bd2aSRichard Purdie if (ret < 0) { 205a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n"); 2062986bd2aSRichard Purdie return; 2072986bd2aSRichard Purdie } 2082986bd2aSRichard Purdie } 2092986bd2aSRichard Purdie goto badblock; 2104b23aff0SRichard Purdie } 2114b23aff0SRichard Purdie 212621e4f8eSRichard Purdie static void mtdoops_write(struct mtdoops_context *cxt, int panic) 2134b23aff0SRichard Purdie { 2146ce0a856SRichard Purdie struct mtd_info *mtd = cxt->mtd; 2156ce0a856SRichard Purdie size_t retlen; 2162e386e4bSSimon Kagstrom u32 *hdr; 2176ce0a856SRichard Purdie int ret; 2184b23aff0SRichard Purdie 2192e386e4bSSimon Kagstrom /* Add mtdoops header to the buffer */ 2202e386e4bSSimon Kagstrom hdr = cxt->oops_buf; 2212e386e4bSSimon Kagstrom hdr[0] = cxt->nextcount; 2222e386e4bSSimon Kagstrom hdr[1] = MTDOOPS_KERNMSG_MAGIC; 2236ce0a856SRichard Purdie 224621e4f8eSRichard Purdie if (panic) 2259507b0c8SSimon Kagstrom ret = mtd->panic_write(mtd, cxt->nextpage * record_size, 2269507b0c8SSimon Kagstrom record_size, &retlen, cxt->oops_buf); 227621e4f8eSRichard Purdie else 2289507b0c8SSimon Kagstrom ret = mtd->write(mtd, cxt->nextpage * record_size, 2299507b0c8SSimon Kagstrom record_size, &retlen, cxt->oops_buf); 2306ce0a856SRichard Purdie 2319507b0c8SSimon Kagstrom if (retlen != record_size || ret < 0) 2329507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n", 2339507b0c8SSimon Kagstrom cxt->nextpage * record_size, retlen, record_size, ret); 234be95745fSSimon Kagstrom mark_page_used(cxt, cxt->nextpage); 2352e386e4bSSimon Kagstrom memset(cxt->oops_buf, 0xff, record_size); 2366ce0a856SRichard Purdie 2376ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 2384b23aff0SRichard Purdie } 2394b23aff0SRichard Purdie 240621e4f8eSRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work) 241621e4f8eSRichard Purdie { 242621e4f8eSRichard Purdie struct mtdoops_context *cxt = 243621e4f8eSRichard Purdie container_of(work, struct mtdoops_context, work_write); 244621e4f8eSRichard Purdie 245621e4f8eSRichard Purdie mtdoops_write(cxt, 0); 246621e4f8eSRichard Purdie } 247621e4f8eSRichard Purdie 2486ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt) 2494b23aff0SRichard Purdie { 2504b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 2512986bd2aSRichard Purdie int ret, page, maxpos = 0; 252f0482ee3SRichard Purdie u32 count[2], maxcount = 0xffffffff; 2534b23aff0SRichard Purdie size_t retlen; 2544b23aff0SRichard Purdie 2554b23aff0SRichard Purdie for (page = 0; page < cxt->oops_pages; page++) { 256be95745fSSimon Kagstrom /* Assume the page is used */ 257be95745fSSimon Kagstrom mark_page_used(cxt, page); 2582e386e4bSSimon Kagstrom ret = mtd->read(mtd, page * record_size, MTDOOPS_HEADER_SIZE, 2592e386e4bSSimon Kagstrom &retlen, (u_char *) &count[0]); 2602e386e4bSSimon Kagstrom if (retlen != MTDOOPS_HEADER_SIZE || 2612e386e4bSSimon Kagstrom (ret < 0 && ret != -EUCLEAN)) { 2622e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: read failure at %ld (%td of %d read), err %d\n", 2632e386e4bSSimon Kagstrom page * record_size, retlen, 2642e386e4bSSimon Kagstrom MTDOOPS_HEADER_SIZE, ret); 2652986bd2aSRichard Purdie continue; 2662986bd2aSRichard Purdie } 2672986bd2aSRichard Purdie 268be95745fSSimon Kagstrom if (count[0] == 0xffffffff && count[1] == 0xffffffff) 269be95745fSSimon Kagstrom mark_page_unused(cxt, page); 270f0482ee3SRichard Purdie if (count[0] == 0xffffffff) 2714b23aff0SRichard Purdie continue; 2724b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 273f0482ee3SRichard Purdie maxcount = count[0]; 2744b23aff0SRichard Purdie maxpos = page; 275a15b124fSArtem Bityutskiy } else if (count[0] < 0x40000000 && maxcount > 0xc0000000) { 276f0482ee3SRichard Purdie maxcount = count[0]; 2774b23aff0SRichard Purdie maxpos = page; 278a15b124fSArtem Bityutskiy } else if (count[0] > maxcount && count[0] < 0xc0000000) { 279f0482ee3SRichard Purdie maxcount = count[0]; 2804b23aff0SRichard Purdie maxpos = page; 281a15b124fSArtem Bityutskiy } else if (count[0] > maxcount && count[0] > 0xc0000000 282a15b124fSArtem Bityutskiy && maxcount > 0x80000000) { 283f0482ee3SRichard Purdie maxcount = count[0]; 2844b23aff0SRichard Purdie maxpos = page; 2854b23aff0SRichard Purdie } 2864b23aff0SRichard Purdie } 2874b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 2884b23aff0SRichard Purdie cxt->nextpage = 0; 2894b23aff0SRichard Purdie cxt->nextcount = 1; 29043b5693dSRichard Purdie schedule_work(&cxt->work_erase); 2916ce0a856SRichard Purdie return; 2924b23aff0SRichard Purdie } 2934b23aff0SRichard Purdie 2944b23aff0SRichard Purdie cxt->nextpage = maxpos; 2954b23aff0SRichard Purdie cxt->nextcount = maxcount; 2964b23aff0SRichard Purdie 2976ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 2984b23aff0SRichard Purdie } 2994b23aff0SRichard Purdie 3002e386e4bSSimon Kagstrom static void mtdoops_do_dump(struct kmsg_dumper *dumper, 3012e386e4bSSimon Kagstrom enum kmsg_dump_reason reason, const char *s1, unsigned long l1, 3022e386e4bSSimon Kagstrom const char *s2, unsigned long l2) 3032e386e4bSSimon Kagstrom { 3042e386e4bSSimon Kagstrom struct mtdoops_context *cxt = container_of(dumper, 3052e386e4bSSimon Kagstrom struct mtdoops_context, dump); 3062e386e4bSSimon Kagstrom unsigned long s1_start, s2_start; 3072e386e4bSSimon Kagstrom unsigned long l1_cpy, l2_cpy; 3082e386e4bSSimon Kagstrom char *dst; 3092e386e4bSSimon Kagstrom 310*fc2d557cSSeiji Aguchi if (reason != KMSG_DUMP_OOPS && 311*fc2d557cSSeiji Aguchi reason != KMSG_DUMP_PANIC && 312*fc2d557cSSeiji Aguchi reason != KMSG_DUMP_KEXEC) 313*fc2d557cSSeiji Aguchi return; 314*fc2d557cSSeiji Aguchi 3152e386e4bSSimon Kagstrom /* Only dump oopses if dump_oops is set */ 3162e386e4bSSimon Kagstrom if (reason == KMSG_DUMP_OOPS && !dump_oops) 3172e386e4bSSimon Kagstrom return; 3182e386e4bSSimon Kagstrom 3192e386e4bSSimon Kagstrom dst = cxt->oops_buf + MTDOOPS_HEADER_SIZE; /* Skip the header */ 3202e386e4bSSimon Kagstrom l2_cpy = min(l2, record_size - MTDOOPS_HEADER_SIZE); 3212e386e4bSSimon Kagstrom l1_cpy = min(l1, record_size - MTDOOPS_HEADER_SIZE - l2_cpy); 3222e386e4bSSimon Kagstrom 3232e386e4bSSimon Kagstrom s2_start = l2 - l2_cpy; 3242e386e4bSSimon Kagstrom s1_start = l1 - l1_cpy; 3252e386e4bSSimon Kagstrom 3262e386e4bSSimon Kagstrom memcpy(dst, s1 + s1_start, l1_cpy); 3272e386e4bSSimon Kagstrom memcpy(dst + l1_cpy, s2 + s2_start, l2_cpy); 3282e386e4bSSimon Kagstrom 3292e386e4bSSimon Kagstrom /* Panics must be written immediately */ 3300f4bd46eSKOSAKI Motohiro if (reason != KMSG_DUMP_OOPS) { 3312e386e4bSSimon Kagstrom if (!cxt->mtd->panic_write) 3322e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: Cannot write from panic without panic_write\n"); 3332e386e4bSSimon Kagstrom else 3342e386e4bSSimon Kagstrom mtdoops_write(cxt, 1); 3352e386e4bSSimon Kagstrom return; 3362e386e4bSSimon Kagstrom } 3372e386e4bSSimon Kagstrom 3382e386e4bSSimon Kagstrom /* For other cases, schedule work to write it "nicely" */ 3392e386e4bSSimon Kagstrom schedule_work(&cxt->work_write); 3402e386e4bSSimon Kagstrom } 3414b23aff0SRichard Purdie 3424b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd) 3434b23aff0SRichard Purdie { 3444b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3452e386e4bSSimon Kagstrom u64 mtdoops_pages = div_u64(mtd->size, record_size); 3462e386e4bSSimon Kagstrom int err; 347be95745fSSimon Kagstrom 3482e386e4bSSimon Kagstrom if (!strcmp(mtd->name, mtddev)) 349e2a0f25bSAdrian Hunter cxt->mtd_index = mtd->index; 350e2a0f25bSAdrian Hunter 351a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 3524b23aff0SRichard Purdie return; 3534b23aff0SRichard Purdie 354a15b124fSArtem Bityutskiy if (mtd->size < mtd->erasesize * 2) { 355a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n", 3564b23aff0SRichard Purdie mtd->index); 3574b23aff0SRichard Purdie return; 3584b23aff0SRichard Purdie } 3599507b0c8SSimon Kagstrom if (mtd->erasesize < record_size) { 360a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n", 36179dcd8e9SRichard Purdie mtd->index); 36279dcd8e9SRichard Purdie return; 36379dcd8e9SRichard Purdie } 3641114e3d0SSimon Kagstrom if (mtd->size > MTDOOPS_MAX_MTD_SIZE) { 3651114e3d0SSimon Kagstrom printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n", 3661114e3d0SSimon Kagstrom mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024); 3671114e3d0SSimon Kagstrom return; 3681114e3d0SSimon Kagstrom } 3691114e3d0SSimon Kagstrom 370be95745fSSimon Kagstrom /* oops_page_used is a bit field */ 371be95745fSSimon Kagstrom cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages, 372be95745fSSimon Kagstrom BITS_PER_LONG)); 373be95745fSSimon Kagstrom if (!cxt->oops_page_used) { 3742e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: could not allocate page array\n"); 3752e386e4bSSimon Kagstrom return; 3762e386e4bSSimon Kagstrom } 3772e386e4bSSimon Kagstrom 3782e386e4bSSimon Kagstrom cxt->dump.dump = mtdoops_do_dump; 3792e386e4bSSimon Kagstrom err = kmsg_dump_register(&cxt->dump); 3802e386e4bSSimon Kagstrom if (err) { 3812e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: registering kmsg dumper failed, error %d\n", err); 3822e386e4bSSimon Kagstrom vfree(cxt->oops_page_used); 3832e386e4bSSimon Kagstrom cxt->oops_page_used = NULL; 384be95745fSSimon Kagstrom return; 385be95745fSSimon Kagstrom } 3861114e3d0SSimon Kagstrom 3874b23aff0SRichard Purdie cxt->mtd = mtd; 3889507b0c8SSimon Kagstrom cxt->oops_pages = (int)mtd->size / record_size; 3896ce0a856SRichard Purdie find_next_position(cxt); 39079dcd8e9SRichard Purdie printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); 3914b23aff0SRichard Purdie } 3924b23aff0SRichard Purdie 3934b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd) 3944b23aff0SRichard Purdie { 3954b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3964b23aff0SRichard Purdie 397a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 3984b23aff0SRichard Purdie return; 3994b23aff0SRichard Purdie 4002e386e4bSSimon Kagstrom if (kmsg_dump_unregister(&cxt->dump) < 0) 4012e386e4bSSimon Kagstrom printk(KERN_WARNING "mtdoops: could not unregister kmsg_dumper\n"); 4022e386e4bSSimon Kagstrom 4034b23aff0SRichard Purdie cxt->mtd = NULL; 4044b23aff0SRichard Purdie flush_scheduled_work(); 4054b23aff0SRichard Purdie } 4064b23aff0SRichard Purdie 4074b23aff0SRichard Purdie 4084b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = { 4094b23aff0SRichard Purdie .add = mtdoops_notify_add, 4104b23aff0SRichard Purdie .remove = mtdoops_notify_remove, 4114b23aff0SRichard Purdie }; 4124b23aff0SRichard Purdie 4132e386e4bSSimon Kagstrom static int __init mtdoops_init(void) 4144b23aff0SRichard Purdie { 4154b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 4162e386e4bSSimon Kagstrom int mtd_index; 4172e386e4bSSimon Kagstrom char *endp; 4184b23aff0SRichard Purdie 4192e386e4bSSimon Kagstrom if (strlen(mtddev) == 0) { 4202e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: mtd device (mtddev=name/number) must be supplied\n"); 4212e386e4bSSimon Kagstrom return -EINVAL; 4222e386e4bSSimon Kagstrom } 4239507b0c8SSimon Kagstrom if ((record_size & 4095) != 0) { 4249507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n"); 4259507b0c8SSimon Kagstrom return -EINVAL; 4269507b0c8SSimon Kagstrom } 4279507b0c8SSimon Kagstrom if (record_size < 4096) { 4289507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n"); 4299507b0c8SSimon Kagstrom return -EINVAL; 4309507b0c8SSimon Kagstrom } 4312e386e4bSSimon Kagstrom 4322e386e4bSSimon Kagstrom /* Setup the MTD device to use */ 4334b23aff0SRichard Purdie cxt->mtd_index = -1; 4342e386e4bSSimon Kagstrom mtd_index = simple_strtoul(mtddev, &endp, 0); 4352e386e4bSSimon Kagstrom if (*endp == '\0') 4362e386e4bSSimon Kagstrom cxt->mtd_index = mtd_index; 4372e386e4bSSimon Kagstrom 4389507b0c8SSimon Kagstrom cxt->oops_buf = vmalloc(record_size); 4394b23aff0SRichard Purdie if (!cxt->oops_buf) { 440a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n"); 4414b23aff0SRichard Purdie return -ENOMEM; 4424b23aff0SRichard Purdie } 4432e386e4bSSimon Kagstrom memset(cxt->oops_buf, 0xff, record_size); 4444b23aff0SRichard Purdie 4456ce0a856SRichard Purdie INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); 4466ce0a856SRichard Purdie INIT_WORK(&cxt->work_write, mtdoops_workfunc_write); 4474b23aff0SRichard Purdie 4484b23aff0SRichard Purdie register_mtd_user(&mtdoops_notifier); 4494b23aff0SRichard Purdie return 0; 4504b23aff0SRichard Purdie } 4514b23aff0SRichard Purdie 4522e386e4bSSimon Kagstrom static void __exit mtdoops_exit(void) 4534b23aff0SRichard Purdie { 4544b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 4554b23aff0SRichard Purdie 4564b23aff0SRichard Purdie unregister_mtd_user(&mtdoops_notifier); 4574b23aff0SRichard Purdie vfree(cxt->oops_buf); 458be95745fSSimon Kagstrom vfree(cxt->oops_page_used); 4594b23aff0SRichard Purdie } 4604b23aff0SRichard Purdie 4614b23aff0SRichard Purdie 4622e386e4bSSimon Kagstrom module_init(mtdoops_init); 4632e386e4bSSimon Kagstrom module_exit(mtdoops_exit); 4644b23aff0SRichard Purdie 4654b23aff0SRichard Purdie MODULE_LICENSE("GPL"); 4664b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); 4674b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); 468