diff options
Diffstat (limited to 'drivers/mtd/mtdoops.c')
| -rw-r--r-- | drivers/mtd/mtdoops.c | 420 |
1 files changed, 216 insertions, 204 deletions
diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index d3cf05012b4..97bb8f6304d 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -1,7 +1,7 @@ /* * MTD Oops/Panic logger * - * Copyright (C) 2007 Nokia Corporation. All rights reserved. + * Copyright © 2007 Nokia Corporation. All rights reserved. * * Author: Richard Purdie <rpurdie@openedhand.com> * @@ -29,13 +29,34 @@ #include <linux/sched.h> #include <linux/wait.h> #include <linux/delay.h> -#include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/mtd/mtd.h> +#include <linux/kmsg_dump.h> -#define OOPS_PAGE_SIZE 4096 +/* Maximum MTD partition size */ +#define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024) + +#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 +#define MTDOOPS_HEADER_SIZE 8 + +static unsigned long record_size = 4096; +module_param(record_size, ulong, 0400); +MODULE_PARM_DESC(record_size, + "record size for MTD OOPS pages in bytes (default 4096)"); + +static char mtddev[80]; +module_param_string(mtddev, mtddev, 80, 0400); +MODULE_PARM_DESC(mtddev, + "name or index number of the MTD device to use"); + +static int dump_oops = 1; +module_param(dump_oops, int, 0600); +MODULE_PARM_DESC(dump_oops, + "set to 1 to dump oopses, 0 to only dump panics (default 1)"); + +static struct mtdoops_context { + struct kmsg_dumper dump; -struct mtdoops_context { int mtd_index; struct work_struct work_erase; struct work_struct work_write; @@ -43,27 +64,43 @@ struct mtdoops_context { int oops_pages; int nextpage; int nextcount; + unsigned long *oops_page_used; void *oops_buf; - - /* writecount and disabling ready are spin lock protected */ - spinlock_t writecount_lock; - int ready; - int writecount; } oops_cxt; +static void mark_page_used(struct mtdoops_context *cxt, int page) +{ + set_bit(page, cxt->oops_page_used); +} + +static void mark_page_unused(struct mtdoops_context *cxt, int page) +{ + clear_bit(page, cxt->oops_page_used); +} + +static int page_is_used(struct mtdoops_context *cxt, int page) +{ + return test_bit(page, cxt->oops_page_used); +} + static void mtdoops_erase_callback(struct erase_info *done) { wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; wake_up(wait_q); } -static int mtdoops_erase_block(struct mtd_info *mtd, int offset) +static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset) { + struct mtd_info *mtd = cxt->mtd; + u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize; + u32 start_page = start_page_offset / record_size; + u32 erase_pages = mtd->erasesize / record_size; struct erase_info erase; DECLARE_WAITQUEUE(wait, current); wait_queue_head_t wait_q; int ret; + int page; init_waitqueue_head(&wait_q); erase.mtd = mtd; @@ -75,55 +112,42 @@ static int mtdoops_erase_block(struct mtd_info *mtd, int offset) set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&wait_q, &wait); - ret = mtd->erase(mtd, &erase); + ret = mtd_erase(mtd, &erase); if (ret) { set_current_state(TASK_RUNNING); remove_wait_queue(&wait_q, &wait); - printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] " - "on \"%s\" failed\n", - erase.addr, erase.len, mtd->name); + printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", + (unsigned long long)erase.addr, + (unsigned long long)erase.len, mtddev); return ret; } schedule(); /* Wait for erase to finish. */ remove_wait_queue(&wait_q, &wait); + /* Mark pages as unused */ + for (page = start_page; page < start_page + erase_pages; page++) + mark_page_unused(cxt, page); + return 0; } static void mtdoops_inc_counter(struct mtdoops_context *cxt) { - struct mtd_info *mtd = cxt->mtd; - size_t retlen; - u32 count; - int ret; - cxt->nextpage++; - if (cxt->nextpage > cxt->oops_pages) + if (cxt->nextpage >= cxt->oops_pages) cxt->nextpage = 0; cxt->nextcount++; if (cxt->nextcount == 0xffffffff) cxt->nextcount = 0; - ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4, - &retlen, (u_char *) &count); - if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) { - printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)" - ", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE, - retlen, ret); - schedule_work(&cxt->work_erase); - return; - } - - /* See if we need to erase the next block */ - if (count != 0xffffffff) { + if (page_is_used(cxt, cxt->nextpage)) { schedule_work(&cxt->work_erase); return; } - printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n", - cxt->nextpage, cxt->nextcount); - cxt->ready = 1; + printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n", + cxt->nextpage, cxt->nextcount); } /* Scheduled work - when we can't proceed without erasing a block */ @@ -138,47 +162,45 @@ static void mtdoops_workfunc_erase(struct work_struct *work) if (!mtd) return; - mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize; + mod = (cxt->nextpage * record_size) % mtd->erasesize; if (mod != 0) { - cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE); - if (cxt->nextpage > cxt->oops_pages) + cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size); + if (cxt->nextpage >= cxt->oops_pages) cxt->nextpage = 0; } - while (mtd->block_isbad) { - ret = mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); - if (!ret) - break; - if (ret < 0) { - printk(KERN_ERR "mtdoops: block_isbad failed, aborting.\n"); - return; - } + while ((ret = mtd_block_isbad(mtd, cxt->nextpage * record_size)) > 0) { badblock: - printk(KERN_WARNING "mtdoops: Bad block at %08x\n", - cxt->nextpage * OOPS_PAGE_SIZE); + printk(KERN_WARNING "mtdoops: bad block at %08lx\n", + cxt->nextpage * record_size); i++; - cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE); - if (cxt->nextpage > cxt->oops_pages) + cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size); + if (cxt->nextpage >= cxt->oops_pages) cxt->nextpage = 0; - if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) { - printk(KERN_ERR "mtdoops: All blocks bad!\n"); + if (i == cxt->oops_pages / (mtd->erasesize / record_size)) { + printk(KERN_ERR "mtdoops: all blocks bad!\n"); return; } } + if (ret < 0) { + printk(KERN_ERR "mtdoops: mtd_block_isbad failed, aborting\n"); + return; + } + for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) - ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE); + ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size); if (ret >= 0) { - printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount); - cxt->ready = 1; + printk(KERN_DEBUG "mtdoops: ready %d, %d\n", + cxt->nextpage, cxt->nextcount); return; } - if (mtd->block_markbad && (ret == -EIO)) { - ret = mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); - if (ret < 0) { - printk(KERN_ERR "mtdoops: block_markbad failed, aborting.\n"); + if (ret == -EIO) { + ret = mtd_block_markbad(mtd, cxt->nextpage * record_size); + if (ret < 0 && ret != -EOPNOTSUPP) { + printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n"); return; } } @@ -189,250 +211,240 @@ static void mtdoops_write(struct mtdoops_context *cxt, int panic) { struct mtd_info *mtd = cxt->mtd; size_t retlen; + u32 *hdr; int ret; - if (cxt->writecount < OOPS_PAGE_SIZE) - memset(cxt->oops_buf + cxt->writecount, 0xff, - OOPS_PAGE_SIZE - cxt->writecount); - - if (panic) - ret = mtd->panic_write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, - OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); - else - ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, - OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); + /* Add mtdoops header to the buffer */ + hdr = cxt->oops_buf; + hdr[0] = cxt->nextcount; + hdr[1] = MTDOOPS_KERNMSG_MAGIC; - cxt->writecount = 0; + if (panic) { + ret = mtd_panic_write(mtd, cxt->nextpage * record_size, + record_size, &retlen, cxt->oops_buf); + if (ret == -EOPNOTSUPP) { + printk(KERN_ERR "mtdoops: Cannot write from panic without panic_write\n"); + return; + } + } else + ret = mtd_write(mtd, cxt->nextpage * record_size, + record_size, &retlen, cxt->oops_buf); - if ((retlen != OOPS_PAGE_SIZE) || (ret < 0)) - printk(KERN_ERR "mtdoops: Write failure at %d (%td of %d written), err %d.\n", - cxt->nextpage * OOPS_PAGE_SIZE, retlen, OOPS_PAGE_SIZE, ret); + if (retlen != record_size || ret < 0) + printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n", + cxt->nextpage * record_size, retlen, record_size, ret); + mark_page_used(cxt, cxt->nextpage); + memset(cxt->oops_buf, 0xff, record_size); mtdoops_inc_counter(cxt); } - static void mtdoops_workfunc_write(struct work_struct *work) { struct mtdoops_context *cxt = container_of(work, struct mtdoops_context, work_write); mtdoops_write(cxt, 0); -} +} static void find_next_position(struct mtdoops_context *cxt) { struct mtd_info *mtd = cxt->mtd; int ret, page, maxpos = 0; - u32 count, maxcount = 0xffffffff; + u32 count[2], maxcount = 0xffffffff; size_t retlen; for (page = 0; page < cxt->oops_pages; page++) { - ret = mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count); - if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) { - printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)" - ", err %d.\n", page * OOPS_PAGE_SIZE, retlen, ret); + if (mtd_block_isbad(mtd, page * record_size)) + continue; + /* Assume the page is used */ + mark_page_used(cxt, page); + ret = mtd_read(mtd, page * record_size, MTDOOPS_HEADER_SIZE, + &retlen, (u_char *)&count[0]); + if (retlen != MTDOOPS_HEADER_SIZE || + (ret < 0 && !mtd_is_bitflip(ret))) { + printk(KERN_ERR "mtdoops: read failure at %ld (%td of %d read), err %d\n", + page * record_size, retlen, + MTDOOPS_HEADER_SIZE, ret); continue; } - if (count == 0xffffffff) + if (count[0] == 0xffffffff && count[1] == 0xffffffff) + mark_page_unused(cxt, page); + if (count[0] == 0xffffffff || count[1] != MTDOOPS_KERNMSG_MAGIC) continue; if (maxcount == 0xffffffff) { - maxcount = count; + maxcount = count[0]; maxpos = page; - } else if ((count < 0x40000000) && (maxcount > 0xc0000000)) { - maxcount = count; + } else if (count[0] < 0x40000000 && maxcount > 0xc0000000) { + maxcount = count[0]; maxpos = page; - } else if ((count > maxcount) && (count < 0xc0000000)) { - maxcount = count; + } else if (count[0] > maxcount && count[0] < 0xc0000000) { + maxcount = count[0]; maxpos = page; - } else if ((count > maxcount) && (count > 0xc0000000) - && (maxcount > 0x80000000)) { - maxcount = count; + } else if (count[0] > maxcount && count[0] > 0xc0000000 + && maxcount > 0x80000000) { + maxcount = count[0]; maxpos = page; } } if (maxcount == 0xffffffff) { - cxt->nextpage = 0; - cxt->nextcount = 1; - cxt->ready = 1; - printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n", - cxt->nextpage, cxt->nextcount); - return; + cxt->nextpage = cxt->oops_pages - 1; + cxt->nextcount = 0; + } + else { + cxt->nextpage = maxpos; + cxt->nextcount = maxcount; } - - cxt->nextpage = maxpos; - cxt->nextcount = maxcount; mtdoops_inc_counter(cxt); } - -static void mtdoops_notify_add(struct mtd_info *mtd) +static void mtdoops_do_dump(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason) { - struct mtdoops_context *cxt = &oops_cxt; + struct mtdoops_context *cxt = container_of(dumper, + struct mtdoops_context, dump); - if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) + /* Only dump oopses if dump_oops is set */ + if (reason == KMSG_DUMP_OOPS && !dump_oops) return; - if (mtd->size < (mtd->erasesize * 2)) { - printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n", - mtd->index); - return; - } - - if (mtd->erasesize < OOPS_PAGE_SIZE) { - printk(KERN_ERR "Eraseblock size of MTD partition %d too small\n", - mtd->index); - return; - } - - cxt->mtd = mtd; - cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE; + kmsg_dump_get_buffer(dumper, true, cxt->oops_buf + MTDOOPS_HEADER_SIZE, + record_size - MTDOOPS_HEADER_SIZE, NULL); - find_next_position(cxt); + /* Panics must be written immediately */ + if (reason != KMSG_DUMP_OOPS) + mtdoops_write(cxt, 1); - printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); + /* For other cases, schedule work to write it "nicely" */ + schedule_work(&cxt->work_write); } -static void mtdoops_notify_remove(struct mtd_info *mtd) +static void mtdoops_notify_add(struct mtd_info *mtd) { struct mtdoops_context *cxt = &oops_cxt; + u64 mtdoops_pages = div_u64(mtd->size, record_size); + int err; - if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) - return; + if (!strcmp(mtd->name, mtddev)) + cxt->mtd_index = mtd->index; - cxt->mtd = NULL; - flush_scheduled_work(); -} - -static void mtdoops_console_sync(void) -{ - struct mtdoops_context *cxt = &oops_cxt; - struct mtd_info *mtd = cxt->mtd; - unsigned long flags; - - if (!cxt->ready || !mtd || cxt->writecount == 0) + if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) return; - /* - * Once ready is 0 and we've held the lock no further writes to the - * buffer will happen - */ - spin_lock_irqsave(&cxt->writecount_lock, flags); - if (!cxt->ready) { - spin_unlock_irqrestore(&cxt->writecount_lock, flags); + if (mtd->size < mtd->erasesize * 2) { + printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n", + mtd->index); return; } - cxt->ready = 0; - spin_unlock_irqrestore(&cxt->writecount_lock, flags); - - if (mtd->panic_write && in_interrupt()) - /* Interrupt context, we're going to panic so try and log */ - mtdoops_write(cxt, 1); - else - schedule_work(&cxt->work_write); -} - -static void -mtdoops_console_write(struct console *co, const char *s, unsigned int count) -{ - struct mtdoops_context *cxt = co->data; - struct mtd_info *mtd = cxt->mtd; - unsigned long flags; - - if (!oops_in_progress) { - mtdoops_console_sync(); + if (mtd->erasesize < record_size) { + printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n", + mtd->index); return; } - - if (!cxt->ready || !mtd) + if (mtd->size > MTDOOPS_MAX_MTD_SIZE) { + printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n", + mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024); return; + } - /* Locking on writecount ensures sequential writes to the buffer */ - spin_lock_irqsave(&cxt->writecount_lock, flags); - - /* Check ready status didn't change whilst waiting for the lock */ - if (!cxt->ready) + /* oops_page_used is a bit field */ + cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages, + BITS_PER_LONG) * sizeof(unsigned long)); + if (!cxt->oops_page_used) { + printk(KERN_ERR "mtdoops: could not allocate page array\n"); return; - - if (cxt->writecount == 0) { - u32 *stamp = cxt->oops_buf; - *stamp = cxt->nextcount; - cxt->writecount = 4; } - if ((count + cxt->writecount) > OOPS_PAGE_SIZE) - count = OOPS_PAGE_SIZE - cxt->writecount; - - memcpy(cxt->oops_buf + cxt->writecount, s, count); - cxt->writecount += count; - - spin_unlock_irqrestore(&cxt->writecount_lock, flags); + cxt->dump.max_reason = KMSG_DUMP_OOPS; + cxt->dump.dump = mtdoops_do_dump; + err = kmsg_dump_register(&cxt->dump); + if (err) { + printk(KERN_ERR "mtdoops: registering kmsg dumper failed, error %d\n", err); + vfree(cxt->oops_page_used); + cxt->oops_page_used = NULL; + return; + } - if (cxt->writecount == OOPS_PAGE_SIZE) - mtdoops_console_sync(); + cxt->mtd = mtd; + cxt->oops_pages = (int)mtd->size / record_size; + find_next_position(cxt); + printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); } -static int __init mtdoops_console_setup(struct console *co, char *options) +static void mtdoops_notify_remove(struct mtd_info *mtd) { - struct mtdoops_context *cxt = co->data; + struct mtdoops_context *cxt = &oops_cxt; - if (cxt->mtd_index != -1) - return -EBUSY; - if (co->index == -1) - return -EINVAL; + if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) + return; - cxt->mtd_index = co->index; - return 0; + if (kmsg_dump_unregister(&cxt->dump) < 0) + printk(KERN_WARNING "mtdoops: could not unregister kmsg_dumper\n"); + + cxt->mtd = NULL; + flush_work(&cxt->work_erase); + flush_work(&cxt->work_write); } + static struct mtd_notifier mtdoops_notifier = { .add = mtdoops_notify_add, .remove = mtdoops_notify_remove, }; -static struct console mtdoops_console = { - .name = "ttyMTD", - .write = mtdoops_console_write, - .setup = mtdoops_console_setup, - .unblank = mtdoops_console_sync, - .index = -1, - .data = &oops_cxt, -}; - -static int __init mtdoops_console_init(void) +static int __init mtdoops_init(void) { struct mtdoops_context *cxt = &oops_cxt; + int mtd_index; + char *endp; + + if (strlen(mtddev) == 0) { + printk(KERN_ERR "mtdoops: mtd device (mtddev=name/number) must be supplied\n"); + return -EINVAL; + } + if ((record_size & 4095) != 0) { + printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n"); + return -EINVAL; + } + if (record_size < 4096) { + printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n"); + return -EINVAL; + } + /* Setup the MTD device to use */ cxt->mtd_index = -1; - cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE); + mtd_index = simple_strtoul(mtddev, &endp, 0); + if (*endp == '\0') + cxt->mtd_index = mtd_index; + cxt->oops_buf = vmalloc(record_size); if (!cxt->oops_buf) { - printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n"); + printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n"); return -ENOMEM; } + memset(cxt->oops_buf, 0xff, record_size); INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); INIT_WORK(&cxt->work_write, mtdoops_workfunc_write); - register_console(&mtdoops_console); register_mtd_user(&mtdoops_notifier); return 0; } -static void __exit mtdoops_console_exit(void) +static void __exit mtdoops_exit(void) { struct mtdoops_context *cxt = &oops_cxt; unregister_mtd_user(&mtdoops_notifier); - unregister_console(&mtdoops_console); vfree(cxt->oops_buf); + vfree(cxt->oops_page_used); } -subsys_initcall(mtdoops_console_init); -module_exit(mtdoops_console_exit); +module_init(mtdoops_init); +module_exit(mtdoops_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); |
