12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 24b23aff0SRichard Purdie /* 34b23aff0SRichard Purdie * MTD Oops/Panic logger 44b23aff0SRichard Purdie * 5a1452a37SDavid Woodhouse * Copyright © 2007 Nokia Corporation. All rights reserved. 64b23aff0SRichard Purdie * 74b23aff0SRichard Purdie * Author: Richard Purdie <rpurdie@openedhand.com> 84b23aff0SRichard Purdie */ 94b23aff0SRichard Purdie 104b23aff0SRichard Purdie #include <linux/kernel.h> 114b23aff0SRichard Purdie #include <linux/module.h> 124b23aff0SRichard Purdie #include <linux/console.h> 134b23aff0SRichard Purdie #include <linux/vmalloc.h> 144b23aff0SRichard Purdie #include <linux/workqueue.h> 154b23aff0SRichard Purdie #include <linux/sched.h> 164b23aff0SRichard Purdie #include <linux/wait.h> 17621e4f8eSRichard Purdie #include <linux/delay.h> 18f9f7dd22SDavid Woodhouse #include <linux/interrupt.h> 194b23aff0SRichard Purdie #include <linux/mtd/mtd.h> 202e386e4bSSimon Kagstrom #include <linux/kmsg_dump.h> 214b23aff0SRichard Purdie 221114e3d0SSimon Kagstrom /* Maximum MTD partition size */ 231114e3d0SSimon Kagstrom #define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024) 241114e3d0SSimon Kagstrom 259507b0c8SSimon Kagstrom static unsigned long record_size = 4096; 269507b0c8SSimon Kagstrom module_param(record_size, ulong, 0400); 279507b0c8SSimon Kagstrom MODULE_PARM_DESC(record_size, 289507b0c8SSimon Kagstrom "record size for MTD OOPS pages in bytes (default 4096)"); 294b23aff0SRichard Purdie 302e386e4bSSimon Kagstrom static char mtddev[80]; 312e386e4bSSimon Kagstrom module_param_string(mtddev, mtddev, 80, 0400); 322e386e4bSSimon Kagstrom MODULE_PARM_DESC(mtddev, 332e386e4bSSimon Kagstrom "name or index number of the MTD device to use"); 342e386e4bSSimon Kagstrom 352e386e4bSSimon Kagstrom static int dump_oops = 1; 362e386e4bSSimon Kagstrom module_param(dump_oops, int, 0600); 372e386e4bSSimon Kagstrom MODULE_PARM_DESC(dump_oops, 382e386e4bSSimon Kagstrom "set to 1 to dump oopses, 0 to only dump panics (default 1)"); 392e386e4bSSimon Kagstrom 40*0bd359eeSJean-Marc Eurin #define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 41*0bd359eeSJean-Marc Eurin 42*0bd359eeSJean-Marc Eurin struct mtdoops_hdr { 43*0bd359eeSJean-Marc Eurin u32 seq; 44*0bd359eeSJean-Marc Eurin u32 magic; 45*0bd359eeSJean-Marc Eurin } __packed; 46*0bd359eeSJean-Marc Eurin 477903cbabSAdrian Bunk static struct mtdoops_context { 482e386e4bSSimon Kagstrom struct kmsg_dumper dump; 492e386e4bSSimon Kagstrom 504b23aff0SRichard Purdie int mtd_index; 516ce0a856SRichard Purdie struct work_struct work_erase; 526ce0a856SRichard Purdie struct work_struct work_write; 534b23aff0SRichard Purdie struct mtd_info *mtd; 544b23aff0SRichard Purdie int oops_pages; 554b23aff0SRichard Purdie int nextpage; 564b23aff0SRichard Purdie int nextcount; 57be95745fSSimon Kagstrom unsigned long *oops_page_used; 584b23aff0SRichard Purdie 5940ddbbacSJohn Ogness unsigned long oops_buf_busy; 604b23aff0SRichard Purdie void *oops_buf; 614b23aff0SRichard Purdie } oops_cxt; 624b23aff0SRichard Purdie 63be95745fSSimon Kagstrom static void mark_page_used(struct mtdoops_context *cxt, int page) 64be95745fSSimon Kagstrom { 65be95745fSSimon Kagstrom set_bit(page, cxt->oops_page_used); 66be95745fSSimon Kagstrom } 67be95745fSSimon Kagstrom 68be95745fSSimon Kagstrom static void mark_page_unused(struct mtdoops_context *cxt, int page) 69be95745fSSimon Kagstrom { 70be95745fSSimon Kagstrom clear_bit(page, cxt->oops_page_used); 71be95745fSSimon Kagstrom } 72be95745fSSimon Kagstrom 73be95745fSSimon Kagstrom static int page_is_used(struct mtdoops_context *cxt, int page) 74be95745fSSimon Kagstrom { 75be95745fSSimon Kagstrom return test_bit(page, cxt->oops_page_used); 76be95745fSSimon Kagstrom } 77be95745fSSimon Kagstrom 78be95745fSSimon Kagstrom static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset) 794b23aff0SRichard Purdie { 80be95745fSSimon Kagstrom struct mtd_info *mtd = cxt->mtd; 81be95745fSSimon Kagstrom u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize; 829507b0c8SSimon Kagstrom u32 start_page = start_page_offset / record_size; 839507b0c8SSimon Kagstrom u32 erase_pages = mtd->erasesize / record_size; 844b23aff0SRichard Purdie struct erase_info erase; 854b23aff0SRichard Purdie int ret; 86be95745fSSimon Kagstrom int page; 874b23aff0SRichard Purdie 884b23aff0SRichard Purdie erase.addr = offset; 894b23aff0SRichard Purdie erase.len = mtd->erasesize; 904b23aff0SRichard Purdie 917e1f0dc0SArtem Bityutskiy ret = mtd_erase(mtd, &erase); 924b23aff0SRichard Purdie if (ret) { 93a15b124fSArtem Bityutskiy printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", 94a15b124fSArtem Bityutskiy (unsigned long long)erase.addr, 952e386e4bSSimon Kagstrom (unsigned long long)erase.len, mtddev); 964b23aff0SRichard Purdie return ret; 974b23aff0SRichard Purdie } 984b23aff0SRichard Purdie 99be95745fSSimon Kagstrom /* Mark pages as unused */ 100be95745fSSimon Kagstrom for (page = start_page; page < start_page + erase_pages; page++) 101be95745fSSimon Kagstrom mark_page_unused(cxt, page); 102be95745fSSimon Kagstrom 1034b23aff0SRichard Purdie return 0; 1044b23aff0SRichard Purdie } 1054b23aff0SRichard Purdie 1066ce0a856SRichard Purdie static void mtdoops_inc_counter(struct mtdoops_context *cxt) 1074b23aff0SRichard Purdie { 1084b23aff0SRichard Purdie cxt->nextpage++; 109ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1104b23aff0SRichard Purdie cxt->nextpage = 0; 1114b23aff0SRichard Purdie cxt->nextcount++; 1124b23aff0SRichard Purdie if (cxt->nextcount == 0xffffffff) 1134b23aff0SRichard Purdie cxt->nextcount = 0; 1144b23aff0SRichard Purdie 115be95745fSSimon Kagstrom if (page_is_used(cxt, cxt->nextpage)) { 1166ce0a856SRichard Purdie schedule_work(&cxt->work_erase); 1176ce0a856SRichard Purdie return; 1186ce0a856SRichard Purdie } 1194b23aff0SRichard Purdie 120a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n", 1214b23aff0SRichard Purdie cxt->nextpage, cxt->nextcount); 1224b23aff0SRichard Purdie } 1234b23aff0SRichard Purdie 1246ce0a856SRichard Purdie /* Scheduled work - when we can't proceed without erasing a block */ 1256ce0a856SRichard Purdie static void mtdoops_workfunc_erase(struct work_struct *work) 1264b23aff0SRichard Purdie { 1276ce0a856SRichard Purdie struct mtdoops_context *cxt = 1286ce0a856SRichard Purdie container_of(work, struct mtdoops_context, work_erase); 1294b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 1304b23aff0SRichard Purdie int i = 0, j, ret, mod; 1314b23aff0SRichard Purdie 1324b23aff0SRichard Purdie /* We were unregistered */ 1334b23aff0SRichard Purdie if (!mtd) 1344b23aff0SRichard Purdie return; 1354b23aff0SRichard Purdie 1369507b0c8SSimon Kagstrom mod = (cxt->nextpage * record_size) % mtd->erasesize; 1374b23aff0SRichard Purdie if (mod != 0) { 1389507b0c8SSimon Kagstrom cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size); 139ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1404b23aff0SRichard Purdie cxt->nextpage = 0; 1414b23aff0SRichard Purdie } 1424b23aff0SRichard Purdie 1439cb93fbbSBrian Norris while ((ret = mtd_block_isbad(mtd, cxt->nextpage * record_size)) > 0) { 1444b23aff0SRichard Purdie badblock: 1459507b0c8SSimon Kagstrom printk(KERN_WARNING "mtdoops: bad block at %08lx\n", 1469507b0c8SSimon Kagstrom cxt->nextpage * record_size); 1474b23aff0SRichard Purdie i++; 1489507b0c8SSimon Kagstrom cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size); 149ecd5b310SRichard Purdie if (cxt->nextpage >= cxt->oops_pages) 1504b23aff0SRichard Purdie cxt->nextpage = 0; 1519507b0c8SSimon Kagstrom if (i == cxt->oops_pages / (mtd->erasesize / record_size)) { 152a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: all blocks bad!\n"); 1534b23aff0SRichard Purdie return; 1544b23aff0SRichard Purdie } 1554b23aff0SRichard Purdie } 1564b23aff0SRichard Purdie 1579cb93fbbSBrian Norris if (ret < 0) { 1589cb93fbbSBrian Norris printk(KERN_ERR "mtdoops: mtd_block_isbad failed, aborting\n"); 1599cb93fbbSBrian Norris return; 1609cb93fbbSBrian Norris } 1619cb93fbbSBrian Norris 1624b23aff0SRichard Purdie for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) 1639507b0c8SSimon Kagstrom ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size); 1644b23aff0SRichard Purdie 1652986bd2aSRichard Purdie if (ret >= 0) { 166a15b124fSArtem Bityutskiy printk(KERN_DEBUG "mtdoops: ready %d, %d\n", 167a15b124fSArtem Bityutskiy cxt->nextpage, cxt->nextcount); 1682986bd2aSRichard Purdie return; 1694b23aff0SRichard Purdie } 1704b23aff0SRichard Purdie 171bb4a0986SArtem Bityutskiy if (ret == -EIO) { 1725942ddbcSArtem Bityutskiy ret = mtd_block_markbad(mtd, cxt->nextpage * record_size); 173bb4a0986SArtem Bityutskiy if (ret < 0 && ret != -EOPNOTSUPP) { 174a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n"); 1752986bd2aSRichard Purdie return; 1762986bd2aSRichard Purdie } 1772986bd2aSRichard Purdie } 1782986bd2aSRichard Purdie goto badblock; 1794b23aff0SRichard Purdie } 1804b23aff0SRichard Purdie 181621e4f8eSRichard Purdie static void mtdoops_write(struct mtdoops_context *cxt, int panic) 1824b23aff0SRichard Purdie { 1836ce0a856SRichard Purdie struct mtd_info *mtd = cxt->mtd; 1846ce0a856SRichard Purdie size_t retlen; 185*0bd359eeSJean-Marc Eurin struct mtdoops_hdr *hdr; 1866ce0a856SRichard Purdie int ret; 1874b23aff0SRichard Purdie 18840ddbbacSJohn Ogness if (test_and_set_bit(0, &cxt->oops_buf_busy)) 18940ddbbacSJohn Ogness return; 19040ddbbacSJohn Ogness 1912e386e4bSSimon Kagstrom /* Add mtdoops header to the buffer */ 192*0bd359eeSJean-Marc Eurin hdr = (struct mtdoops_hdr *)cxt->oops_buf; 193*0bd359eeSJean-Marc Eurin hdr->seq = cxt->nextcount; 194*0bd359eeSJean-Marc Eurin hdr->magic = MTDOOPS_KERNMSG_MAGIC; 1956ce0a856SRichard Purdie 196016c1291SArtem Bityutskiy if (panic) { 1977ae79d7fSArtem Bityutskiy ret = mtd_panic_write(mtd, cxt->nextpage * record_size, 1989507b0c8SSimon Kagstrom record_size, &retlen, cxt->oops_buf); 199016c1291SArtem Bityutskiy if (ret == -EOPNOTSUPP) { 200016c1291SArtem Bityutskiy printk(KERN_ERR "mtdoops: Cannot write from panic without panic_write\n"); 20140ddbbacSJohn Ogness goto out; 202016c1291SArtem Bityutskiy } 203016c1291SArtem Bityutskiy } else 204eda95cbfSArtem Bityutskiy ret = mtd_write(mtd, cxt->nextpage * record_size, 2059507b0c8SSimon Kagstrom record_size, &retlen, cxt->oops_buf); 2066ce0a856SRichard Purdie 2079507b0c8SSimon Kagstrom if (retlen != record_size || ret < 0) 2089507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n", 2099507b0c8SSimon Kagstrom cxt->nextpage * record_size, retlen, record_size, ret); 210be95745fSSimon Kagstrom mark_page_used(cxt, cxt->nextpage); 2112e386e4bSSimon Kagstrom memset(cxt->oops_buf, 0xff, record_size); 2126ce0a856SRichard Purdie 2136ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 21440ddbbacSJohn Ogness out: 21540ddbbacSJohn Ogness clear_bit(0, &cxt->oops_buf_busy); 2164b23aff0SRichard Purdie } 2174b23aff0SRichard Purdie 218621e4f8eSRichard Purdie static void mtdoops_workfunc_write(struct work_struct *work) 219621e4f8eSRichard Purdie { 220621e4f8eSRichard Purdie struct mtdoops_context *cxt = 221621e4f8eSRichard Purdie container_of(work, struct mtdoops_context, work_write); 222621e4f8eSRichard Purdie 223621e4f8eSRichard Purdie mtdoops_write(cxt, 0); 224621e4f8eSRichard Purdie } 225621e4f8eSRichard Purdie 2266ce0a856SRichard Purdie static void find_next_position(struct mtdoops_context *cxt) 2274b23aff0SRichard Purdie { 2284b23aff0SRichard Purdie struct mtd_info *mtd = cxt->mtd; 229*0bd359eeSJean-Marc Eurin struct mtdoops_hdr hdr; 2302986bd2aSRichard Purdie int ret, page, maxpos = 0; 231*0bd359eeSJean-Marc Eurin u32 maxcount = 0xffffffff; 2324b23aff0SRichard Purdie size_t retlen; 2334b23aff0SRichard Purdie 2344b23aff0SRichard Purdie for (page = 0; page < cxt->oops_pages; page++) { 235bb4a0986SArtem Bityutskiy if (mtd_block_isbad(mtd, page * record_size)) 2363538c563SRoman Tereshonkov continue; 237be95745fSSimon Kagstrom /* Assume the page is used */ 238be95745fSSimon Kagstrom mark_page_used(cxt, page); 239*0bd359eeSJean-Marc Eurin ret = mtd_read(mtd, page * record_size, sizeof(hdr), 240*0bd359eeSJean-Marc Eurin &retlen, (u_char *)&hdr); 241*0bd359eeSJean-Marc Eurin if (retlen != sizeof(hdr) || 242d57f4054SBrian Norris (ret < 0 && !mtd_is_bitflip(ret))) { 243*0bd359eeSJean-Marc Eurin printk(KERN_ERR "mtdoops: read failure at %ld (%zu of %zu read), err %d\n", 244*0bd359eeSJean-Marc Eurin page * record_size, retlen, sizeof(hdr), ret); 2452986bd2aSRichard Purdie continue; 2462986bd2aSRichard Purdie } 2472986bd2aSRichard Purdie 248*0bd359eeSJean-Marc Eurin if (hdr.seq == 0xffffffff && hdr.magic == 0xffffffff) 249be95745fSSimon Kagstrom mark_page_unused(cxt, page); 250*0bd359eeSJean-Marc Eurin if (hdr.seq == 0xffffffff || hdr.magic != MTDOOPS_KERNMSG_MAGIC) 2514b23aff0SRichard Purdie continue; 2524b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 253*0bd359eeSJean-Marc Eurin maxcount = hdr.seq; 2544b23aff0SRichard Purdie maxpos = page; 255*0bd359eeSJean-Marc Eurin } else if (hdr.seq < 0x40000000 && maxcount > 0xc0000000) { 256*0bd359eeSJean-Marc Eurin maxcount = hdr.seq; 2574b23aff0SRichard Purdie maxpos = page; 258*0bd359eeSJean-Marc Eurin } else if (hdr.seq > maxcount && hdr.seq < 0xc0000000) { 259*0bd359eeSJean-Marc Eurin maxcount = hdr.seq; 2604b23aff0SRichard Purdie maxpos = page; 261*0bd359eeSJean-Marc Eurin } else if (hdr.seq > maxcount && hdr.seq > 0xc0000000 262a15b124fSArtem Bityutskiy && maxcount > 0x80000000) { 263*0bd359eeSJean-Marc Eurin maxcount = hdr.seq; 2644b23aff0SRichard Purdie maxpos = page; 2654b23aff0SRichard Purdie } 2664b23aff0SRichard Purdie } 2674b23aff0SRichard Purdie if (maxcount == 0xffffffff) { 268cd409c61SMatthieu CASTET cxt->nextpage = cxt->oops_pages - 1; 269cd409c61SMatthieu CASTET cxt->nextcount = 0; 2704b23aff0SRichard Purdie } 271cd409c61SMatthieu CASTET else { 2724b23aff0SRichard Purdie cxt->nextpage = maxpos; 2734b23aff0SRichard Purdie cxt->nextcount = maxcount; 274cd409c61SMatthieu CASTET } 2754b23aff0SRichard Purdie 2766ce0a856SRichard Purdie mtdoops_inc_counter(cxt); 2774b23aff0SRichard Purdie } 2784b23aff0SRichard Purdie 2792e386e4bSSimon Kagstrom static void mtdoops_do_dump(struct kmsg_dumper *dumper, 280e2ae715dSKay Sievers enum kmsg_dump_reason reason) 2812e386e4bSSimon Kagstrom { 2822e386e4bSSimon Kagstrom struct mtdoops_context *cxt = container_of(dumper, 2832e386e4bSSimon Kagstrom struct mtdoops_context, dump); 284f9f3f02dSJohn Ogness struct kmsg_dump_iter iter; 285fc2d557cSSeiji Aguchi 2862e386e4bSSimon Kagstrom /* Only dump oopses if dump_oops is set */ 2872e386e4bSSimon Kagstrom if (reason == KMSG_DUMP_OOPS && !dump_oops) 2882e386e4bSSimon Kagstrom return; 2892e386e4bSSimon Kagstrom 290f9f3f02dSJohn Ogness kmsg_dump_rewind(&iter); 291f9f3f02dSJohn Ogness 29240ddbbacSJohn Ogness if (test_and_set_bit(0, &cxt->oops_buf_busy)) 29340ddbbacSJohn Ogness return; 294*0bd359eeSJean-Marc Eurin kmsg_dump_get_buffer(&iter, true, 295*0bd359eeSJean-Marc Eurin cxt->oops_buf + sizeof(struct mtdoops_hdr), 296*0bd359eeSJean-Marc Eurin record_size - sizeof(struct mtdoops_hdr), NULL); 29740ddbbacSJohn Ogness clear_bit(0, &cxt->oops_buf_busy); 2982e386e4bSSimon Kagstrom 299c1cf1d57SMark Tomlinson if (reason != KMSG_DUMP_OOPS) { 3002e386e4bSSimon Kagstrom /* Panics must be written immediately */ 3012e386e4bSSimon Kagstrom mtdoops_write(cxt, 1); 302c1cf1d57SMark Tomlinson } else { 3032e386e4bSSimon Kagstrom /* For other cases, schedule work to write it "nicely" */ 3042e386e4bSSimon Kagstrom schedule_work(&cxt->work_write); 3052e386e4bSSimon Kagstrom } 306c1cf1d57SMark Tomlinson } 3074b23aff0SRichard Purdie 3084b23aff0SRichard Purdie static void mtdoops_notify_add(struct mtd_info *mtd) 3094b23aff0SRichard Purdie { 3104b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3112e386e4bSSimon Kagstrom u64 mtdoops_pages = div_u64(mtd->size, record_size); 3122e386e4bSSimon Kagstrom int err; 313be95745fSSimon Kagstrom 3142e386e4bSSimon Kagstrom if (!strcmp(mtd->name, mtddev)) 315e2a0f25bSAdrian Hunter cxt->mtd_index = mtd->index; 316e2a0f25bSAdrian Hunter 317a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 3184b23aff0SRichard Purdie return; 3194b23aff0SRichard Purdie 320a15b124fSArtem Bityutskiy if (mtd->size < mtd->erasesize * 2) { 321a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n", 3224b23aff0SRichard Purdie mtd->index); 3234b23aff0SRichard Purdie return; 3244b23aff0SRichard Purdie } 3259507b0c8SSimon Kagstrom if (mtd->erasesize < record_size) { 326a15b124fSArtem Bityutskiy printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n", 32779dcd8e9SRichard Purdie mtd->index); 32879dcd8e9SRichard Purdie return; 32979dcd8e9SRichard Purdie } 3301114e3d0SSimon Kagstrom if (mtd->size > MTDOOPS_MAX_MTD_SIZE) { 3311114e3d0SSimon Kagstrom printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n", 3321114e3d0SSimon Kagstrom mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024); 3331114e3d0SSimon Kagstrom return; 3341114e3d0SSimon Kagstrom } 3351114e3d0SSimon Kagstrom 336be95745fSSimon Kagstrom /* oops_page_used is a bit field */ 33742bc47b3SKees Cook cxt->oops_page_used = 33842bc47b3SKees Cook vmalloc(array_size(sizeof(unsigned long), 33942bc47b3SKees Cook DIV_ROUND_UP(mtdoops_pages, 34042bc47b3SKees Cook BITS_PER_LONG))); 341be95745fSSimon Kagstrom if (!cxt->oops_page_used) { 3422e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: could not allocate page array\n"); 3432e386e4bSSimon Kagstrom return; 3442e386e4bSSimon Kagstrom } 3452e386e4bSSimon Kagstrom 346e2ae715dSKay Sievers cxt->dump.max_reason = KMSG_DUMP_OOPS; 3472e386e4bSSimon Kagstrom cxt->dump.dump = mtdoops_do_dump; 3482e386e4bSSimon Kagstrom err = kmsg_dump_register(&cxt->dump); 3492e386e4bSSimon Kagstrom if (err) { 3502e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: registering kmsg dumper failed, error %d\n", err); 3512e386e4bSSimon Kagstrom vfree(cxt->oops_page_used); 3522e386e4bSSimon Kagstrom cxt->oops_page_used = NULL; 353be95745fSSimon Kagstrom return; 354be95745fSSimon Kagstrom } 3551114e3d0SSimon Kagstrom 3564b23aff0SRichard Purdie cxt->mtd = mtd; 3579507b0c8SSimon Kagstrom cxt->oops_pages = (int)mtd->size / record_size; 3586ce0a856SRichard Purdie find_next_position(cxt); 35979dcd8e9SRichard Purdie printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); 3604b23aff0SRichard Purdie } 3614b23aff0SRichard Purdie 3624b23aff0SRichard Purdie static void mtdoops_notify_remove(struct mtd_info *mtd) 3634b23aff0SRichard Purdie { 3644b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3654b23aff0SRichard Purdie 366a15b124fSArtem Bityutskiy if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) 3674b23aff0SRichard Purdie return; 3684b23aff0SRichard Purdie 3692e386e4bSSimon Kagstrom if (kmsg_dump_unregister(&cxt->dump) < 0) 3702e386e4bSSimon Kagstrom printk(KERN_WARNING "mtdoops: could not unregister kmsg_dumper\n"); 3712e386e4bSSimon Kagstrom 3724b23aff0SRichard Purdie cxt->mtd = NULL; 37343829731STejun Heo flush_work(&cxt->work_erase); 37443829731STejun Heo flush_work(&cxt->work_write); 3754b23aff0SRichard Purdie } 3764b23aff0SRichard Purdie 3774b23aff0SRichard Purdie 3784b23aff0SRichard Purdie static struct mtd_notifier mtdoops_notifier = { 3794b23aff0SRichard Purdie .add = mtdoops_notify_add, 3804b23aff0SRichard Purdie .remove = mtdoops_notify_remove, 3814b23aff0SRichard Purdie }; 3824b23aff0SRichard Purdie 3832e386e4bSSimon Kagstrom static int __init mtdoops_init(void) 3844b23aff0SRichard Purdie { 3854b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 3862e386e4bSSimon Kagstrom int mtd_index; 3872e386e4bSSimon Kagstrom char *endp; 3884b23aff0SRichard Purdie 3892e386e4bSSimon Kagstrom if (strlen(mtddev) == 0) { 3902e386e4bSSimon Kagstrom printk(KERN_ERR "mtdoops: mtd device (mtddev=name/number) must be supplied\n"); 3912e386e4bSSimon Kagstrom return -EINVAL; 3922e386e4bSSimon Kagstrom } 3939507b0c8SSimon Kagstrom if ((record_size & 4095) != 0) { 3949507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n"); 3959507b0c8SSimon Kagstrom return -EINVAL; 3969507b0c8SSimon Kagstrom } 3979507b0c8SSimon Kagstrom if (record_size < 4096) { 3989507b0c8SSimon Kagstrom printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n"); 3999507b0c8SSimon Kagstrom return -EINVAL; 4009507b0c8SSimon Kagstrom } 4012e386e4bSSimon Kagstrom 4022e386e4bSSimon Kagstrom /* Setup the MTD device to use */ 4034b23aff0SRichard Purdie cxt->mtd_index = -1; 4042e386e4bSSimon Kagstrom mtd_index = simple_strtoul(mtddev, &endp, 0); 4052e386e4bSSimon Kagstrom if (*endp == '\0') 4062e386e4bSSimon Kagstrom cxt->mtd_index = mtd_index; 4072e386e4bSSimon Kagstrom 4089507b0c8SSimon Kagstrom cxt->oops_buf = vmalloc(record_size); 409313ea21aSZhen Lei if (!cxt->oops_buf) 4104b23aff0SRichard Purdie return -ENOMEM; 4112e386e4bSSimon Kagstrom memset(cxt->oops_buf, 0xff, record_size); 41240ddbbacSJohn Ogness cxt->oops_buf_busy = 0; 4134b23aff0SRichard Purdie 4146ce0a856SRichard Purdie INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); 4156ce0a856SRichard Purdie INIT_WORK(&cxt->work_write, mtdoops_workfunc_write); 4164b23aff0SRichard Purdie 4174b23aff0SRichard Purdie register_mtd_user(&mtdoops_notifier); 4184b23aff0SRichard Purdie return 0; 4194b23aff0SRichard Purdie } 4204b23aff0SRichard Purdie 4212e386e4bSSimon Kagstrom static void __exit mtdoops_exit(void) 4224b23aff0SRichard Purdie { 4234b23aff0SRichard Purdie struct mtdoops_context *cxt = &oops_cxt; 4244b23aff0SRichard Purdie 4254b23aff0SRichard Purdie unregister_mtd_user(&mtdoops_notifier); 4264b23aff0SRichard Purdie vfree(cxt->oops_buf); 427be95745fSSimon Kagstrom vfree(cxt->oops_page_used); 4284b23aff0SRichard Purdie } 4294b23aff0SRichard Purdie 4304b23aff0SRichard Purdie 4312e386e4bSSimon Kagstrom module_init(mtdoops_init); 4322e386e4bSSimon Kagstrom module_exit(mtdoops_exit); 4334b23aff0SRichard Purdie 4344b23aff0SRichard Purdie MODULE_LICENSE("GPL"); 4354b23aff0SRichard Purdie MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); 4364b23aff0SRichard Purdie MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); 437