linux/drivers/mtd/mtdoops.c
<<
>>
Prefs
   1/*
   2 * MTD Oops/Panic logger
   3 *
   4 * Copyright (C) 2007 Nokia Corporation. All rights reserved.
   5 *
   6 * Author: Richard Purdie <rpurdie@openedhand.com>
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License
  10 * version 2 as published by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, write to the Free Software
  19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20 * 02110-1301 USA
  21 *
  22 */
  23
  24#include <linux/kernel.h>
  25#include <linux/module.h>
  26#include <linux/console.h>
  27#include <linux/vmalloc.h>
  28#include <linux/workqueue.h>
  29#include <linux/sched.h>
  30#include <linux/wait.h>
  31#include <linux/mtd/mtd.h>
  32
  33#define OOPS_PAGE_SIZE 4096
  34
  35static struct mtdoops_context {
  36        int mtd_index;
  37        struct work_struct work;
  38        struct mtd_info *mtd;
  39        int oops_pages;
  40        int nextpage;
  41        int nextcount;
  42
  43        void *oops_buf;
  44        int ready;
  45        int writecount;
  46} oops_cxt;
  47
  48static void mtdoops_erase_callback(struct erase_info *done)
  49{
  50        wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
  51        wake_up(wait_q);
  52}
  53
  54static int mtdoops_erase_block(struct mtd_info *mtd, int offset)
  55{
  56        struct erase_info erase;
  57        DECLARE_WAITQUEUE(wait, current);
  58        wait_queue_head_t wait_q;
  59        int ret;
  60
  61        init_waitqueue_head(&wait_q);
  62        erase.mtd = mtd;
  63        erase.callback = mtdoops_erase_callback;
  64        erase.addr = offset;
  65        if (mtd->erasesize < OOPS_PAGE_SIZE)
  66                erase.len = OOPS_PAGE_SIZE;
  67        else
  68                erase.len = mtd->erasesize;
  69        erase.priv = (u_long)&wait_q;
  70
  71        set_current_state(TASK_INTERRUPTIBLE);
  72        add_wait_queue(&wait_q, &wait);
  73
  74        ret = mtd->erase(mtd, &erase);
  75        if (ret) {
  76                set_current_state(TASK_RUNNING);
  77                remove_wait_queue(&wait_q, &wait);
  78                printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
  79                                     "on \"%s\" failed\n",
  80                        erase.addr, erase.len, mtd->name);
  81                return ret;
  82        }
  83
  84        schedule();  /* Wait for erase to finish. */
  85        remove_wait_queue(&wait_q, &wait);
  86
  87        return 0;
  88}
  89
  90static int mtdoops_inc_counter(struct mtdoops_context *cxt)
  91{
  92        struct mtd_info *mtd = cxt->mtd;
  93        size_t retlen;
  94        u32 count;
  95        int ret;
  96
  97        cxt->nextpage++;
  98        if (cxt->nextpage > cxt->oops_pages)
  99                cxt->nextpage = 0;
 100        cxt->nextcount++;
 101        if (cxt->nextcount == 0xffffffff)
 102                cxt->nextcount = 0;
 103
 104        ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4,
 105                        &retlen, (u_char *) &count);
 106        if ((retlen != 4) || (ret < 0)) {
 107                printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
 108                                ", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE,
 109                                retlen, ret);
 110                return 1;
 111        }
 112
 113        /* See if we need to erase the next block */
 114        if (count != 0xffffffff)
 115                return 1;
 116
 117        printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n",
 118                        cxt->nextpage, cxt->nextcount);
 119        cxt->ready = 1;
 120        return 0;
 121}
 122
 123static void mtdoops_prepare(struct mtdoops_context *cxt)
 124{
 125        struct mtd_info *mtd = cxt->mtd;
 126        int i = 0, j, ret, mod;
 127
 128        /* We were unregistered */
 129        if (!mtd)
 130                return;
 131
 132        mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize;
 133        if (mod != 0) {
 134                cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE);
 135                if (cxt->nextpage > cxt->oops_pages)
 136                        cxt->nextpage = 0;
 137        }
 138
 139        while (mtd->block_isbad &&
 140                        mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE)) {
 141badblock:
 142                printk(KERN_WARNING "mtdoops: Bad block at %08x\n",
 143                                cxt->nextpage * OOPS_PAGE_SIZE);
 144                i++;
 145                cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE);
 146                if (cxt->nextpage > cxt->oops_pages)
 147                        cxt->nextpage = 0;
 148                if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) {
 149                        printk(KERN_ERR "mtdoops: All blocks bad!\n");
 150                        return;
 151                }
 152        }
 153
 154        for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
 155                ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
 156
 157        if (ret < 0) {
 158                if (mtd->block_markbad)
 159                        mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
 160                goto badblock;
 161        }
 162
 163        printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount);
 164
 165        cxt->ready = 1;
 166}
 167
 168static void mtdoops_workfunc(struct work_struct *work)
 169{
 170        struct mtdoops_context *cxt =
 171                        container_of(work, struct mtdoops_context, work);
 172
 173        mtdoops_prepare(cxt);
 174}
 175
 176static int find_next_position(struct mtdoops_context *cxt)
 177{
 178        struct mtd_info *mtd = cxt->mtd;
 179        int page, maxpos = 0;
 180        u32 count, maxcount = 0xffffffff;
 181        size_t retlen;
 182
 183        for (page = 0; page < cxt->oops_pages; page++) {
 184                mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count);
 185                if (count == 0xffffffff)
 186                        continue;
 187                if (maxcount == 0xffffffff) {
 188                        maxcount = count;
 189                        maxpos = page;
 190                } else if ((count < 0x40000000) && (maxcount > 0xc0000000)) {
 191                        maxcount = count;
 192                        maxpos = page;
 193                } else if ((count > maxcount) && (count < 0xc0000000)) {
 194                        maxcount = count;
 195                        maxpos = page;
 196                } else if ((count > maxcount) && (count > 0xc0000000)
 197                                        && (maxcount > 0x80000000)) {
 198                        maxcount = count;
 199                        maxpos = page;
 200                }
 201        }
 202        if (maxcount == 0xffffffff) {
 203                cxt->nextpage = 0;
 204                cxt->nextcount = 1;
 205                cxt->ready = 1;
 206                printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n",
 207                                cxt->nextpage, cxt->nextcount);
 208                return 0;
 209        }
 210
 211        cxt->nextpage = maxpos;
 212        cxt->nextcount = maxcount;
 213
 214        return mtdoops_inc_counter(cxt);
 215}
 216
 217
 218static void mtdoops_notify_add(struct mtd_info *mtd)
 219{
 220        struct mtdoops_context *cxt = &oops_cxt;
 221        int ret;
 222
 223        if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
 224                return;
 225
 226        if (mtd->size < (mtd->erasesize * 2)) {
 227                printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n",
 228                                mtd->index);
 229                return;
 230        }
 231
 232        cxt->mtd = mtd;
 233        cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE;
 234
 235        ret = find_next_position(cxt);
 236        if (ret == 1)
 237                mtdoops_prepare(cxt);
 238
 239        printk(KERN_DEBUG "mtdoops: Attached to MTD device %d\n", mtd->index);
 240}
 241
 242static void mtdoops_notify_remove(struct mtd_info *mtd)
 243{
 244        struct mtdoops_context *cxt = &oops_cxt;
 245
 246        if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
 247                return;
 248
 249        cxt->mtd = NULL;
 250        flush_scheduled_work();
 251}
 252
 253static void mtdoops_console_sync(void)
 254{
 255        struct mtdoops_context *cxt = &oops_cxt;
 256        struct mtd_info *mtd = cxt->mtd;
 257        size_t retlen;
 258        int ret;
 259
 260        if (!cxt->ready || !mtd)
 261                return;
 262
 263        if (cxt->writecount == 0)
 264                return;
 265
 266        if (cxt->writecount < OOPS_PAGE_SIZE)
 267                memset(cxt->oops_buf + cxt->writecount, 0xff,
 268                                        OOPS_PAGE_SIZE - cxt->writecount);
 269
 270        ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
 271                                        OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
 272        cxt->ready = 0;
 273        cxt->writecount = 0;
 274
 275        if ((retlen != OOPS_PAGE_SIZE) || (ret < 0))
 276                printk(KERN_ERR "mtdoops: Write failure at %d (%td of %d written), err %d.\n",
 277                        cxt->nextpage * OOPS_PAGE_SIZE, retlen, OOPS_PAGE_SIZE, ret);
 278
 279        ret = mtdoops_inc_counter(cxt);
 280        if (ret == 1)
 281                schedule_work(&cxt->work);
 282}
 283
 284static void
 285mtdoops_console_write(struct console *co, const char *s, unsigned int count)
 286{
 287        struct mtdoops_context *cxt = co->data;
 288        struct mtd_info *mtd = cxt->mtd;
 289        int i;
 290
 291        if (!oops_in_progress) {
 292                mtdoops_console_sync();
 293                return;
 294        }
 295
 296        if (!cxt->ready || !mtd)
 297                return;
 298
 299        if (cxt->writecount == 0) {
 300                u32 *stamp = cxt->oops_buf;
 301                *stamp = cxt->nextcount;
 302                cxt->writecount = 4;
 303        }
 304
 305        if ((count + cxt->writecount) > OOPS_PAGE_SIZE)
 306                count = OOPS_PAGE_SIZE - cxt->writecount;
 307
 308        for (i = 0; i < count; i++, s++)
 309                *((char *)(cxt->oops_buf) + cxt->writecount + i) = *s;
 310
 311        cxt->writecount = cxt->writecount + count;
 312}
 313
 314static int __init mtdoops_console_setup(struct console *co, char *options)
 315{
 316        struct mtdoops_context *cxt = co->data;
 317
 318        if (cxt->mtd_index != -1)
 319                return -EBUSY;
 320        if (co->index == -1)
 321                return -EINVAL;
 322
 323        cxt->mtd_index = co->index;
 324        return 0;
 325}
 326
 327static struct mtd_notifier mtdoops_notifier = {
 328        .add    = mtdoops_notify_add,
 329        .remove = mtdoops_notify_remove,
 330};
 331
 332static struct console mtdoops_console = {
 333        .name           = "ttyMTD",
 334        .write          = mtdoops_console_write,
 335        .setup          = mtdoops_console_setup,
 336        .unblank        = mtdoops_console_sync,
 337        .flags          = CON_PRINTBUFFER,
 338        .index          = -1,
 339        .data           = &oops_cxt,
 340};
 341
 342static int __init mtdoops_console_init(void)
 343{
 344        struct mtdoops_context *cxt = &oops_cxt;
 345
 346        cxt->mtd_index = -1;
 347        cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE);
 348
 349        if (!cxt->oops_buf) {
 350                printk(KERN_ERR "Failed to allocate oops buffer workspace\n");
 351                return -ENOMEM;
 352        }
 353
 354        INIT_WORK(&cxt->work, mtdoops_workfunc);
 355
 356        register_console(&mtdoops_console);
 357        register_mtd_user(&mtdoops_notifier);
 358        return 0;
 359}
 360
 361static void __exit mtdoops_console_exit(void)
 362{
 363        struct mtdoops_context *cxt = &oops_cxt;
 364
 365        unregister_mtd_user(&mtdoops_notifier);
 366        unregister_console(&mtdoops_console);
 367        vfree(cxt->oops_buf);
 368}
 369
 370
 371subsys_initcall(mtdoops_console_init);
 372module_exit(mtdoops_console_exit);
 373
 374MODULE_LICENSE("GPL");
 375MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
 376MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");
 377