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/delay.h> 32#include <linux/mtd/mtd.h> 33#include <linux/slab.h> 34 35static char *oops_part = "reserve"; 36module_param(oops_part, charp, 0444); 37MODULE_PARM_DESC(oops_part, "mtdoops mtd partiton name"); 38 39#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 40#define OOPS_PAGE_SIZE 1024 41 42extern int redirect_console_oops_msg(char *buf, int len); 43 44static struct mtdoops_context { 45 int mtd_index; 46 struct work_struct work_erase; 47 struct mtd_info *mtd; 48 int oops_pages; 49 int nextpage; 50 int nextcount; 51 char *name; 52 53 void *oops_buf; 54 55 /* writecount and disabling ready are spin lock protected */ 56 int ready; 57 int writecount; 58} oops_cxt; 59 60static void mtdoops_erase_callback(struct erase_info *done) 61{ 62 wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; 63 wake_up(wait_q); 64} 65 66static int mtdoops_erase_block(struct mtd_info *mtd, int offset) 67{ 68 struct erase_info erase; 69 DECLARE_WAITQUEUE(wait, current); 70 wait_queue_head_t wait_q; 71 int ret; 72 73 init_waitqueue_head(&wait_q); 74 erase.mtd = mtd; 75 erase.callback = mtdoops_erase_callback; 76 erase.addr = offset; 77 erase.len = mtd->erasesize; 78 erase.priv = (u_long)&wait_q; 79 80 set_current_state(TASK_INTERRUPTIBLE); 81 add_wait_queue(&wait_q, &wait); 82 83 ret = mtd->_erase(mtd, &erase); 84 if (ret) { 85 set_current_state(TASK_RUNNING); 86 remove_wait_queue(&wait_q, &wait); 87 printk (KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] " 88 "on \"%s\" failed\n", 89 (unsigned long long)erase.addr, (unsigned long long)erase.len, mtd->name); 90 return ret; 91 } 92 93 schedule(); /* Wait for erase to finish. */ 94 remove_wait_queue(&wait_q, &wait); 95 96 return 0; 97} 98 99static void mtdoops_inc_counter(struct mtdoops_context *cxt) 100{ 101 struct mtd_info *mtd = cxt->mtd; 102 size_t retlen; 103 u32 count; 104 int ret; 105 106 cxt->nextpage++; 107 if (cxt->nextpage >= cxt->oops_pages) 108 cxt->nextpage = 0; 109 cxt->nextcount++; 110 if (cxt->nextcount == 0xffffffff) 111 cxt->nextcount = 0; 112 113 ret = mtd->_read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4, 114 &retlen, (u_char *) &count); 115 if ((retlen != 4) || ((ret < 0) && (ret != -EUCLEAN))) { 116 printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)" 117 ", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE, 118 retlen, ret); 119 schedule_work(&cxt->work_erase); 120 return; 121 } 122 123 /* See if we need to erase the next block */ 124 if (count != 0xffffffff) { 125 schedule_work(&cxt->work_erase); 126 return; 127 } 128 129 printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n", 130 cxt->nextpage, cxt->nextcount); 131 cxt->ready = 1; 132} 133 134/* Scheduled work - when we can't proceed without erasing a block */ 135static void mtdoops_workfunc_erase(struct work_struct *work) 136{ 137 struct mtdoops_context *cxt = 138 container_of(work, struct mtdoops_context, work_erase); 139 struct mtd_info *mtd = cxt->mtd; 140 int i = 0, j, ret, mod; 141// printk("******** mtdoops_workfunc_erase **********\n"); 142 /* We were unregistered */ 143 if (!mtd) 144 return; 145 146 mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize; 147 if (mod != 0) { 148 cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE); 149 if (cxt->nextpage >= cxt->oops_pages) 150 cxt->nextpage = 0; 151 } 152 153 while (mtd->_block_isbad) { 154 ret = mtd->_block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); 155 if (!ret) 156 break; 157 if (ret < 0) { 158 printk(KERN_ERR "mtdoops: _block_isbad failed, aborting.\n"); 159 return; 160 } 161badblock: 162 printk(KERN_WARNING "mtdoops: Bad block at %08x\n", 163 cxt->nextpage * OOPS_PAGE_SIZE); 164 i++; 165 cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE); 166 if (cxt->nextpage >= cxt->oops_pages) 167 cxt->nextpage = 0; 168 if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) { 169 printk(KERN_ERR "mtdoops: All blocks bad!\n"); 170 return; 171 } 172 } 173 174 for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) 175 ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE); 176 177 if (ret >= 0) { 178 printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount); 179 cxt->ready = 1; 180 return; 181 } 182 183 if (mtd->_block_markbad && (ret == -EIO)) { 184 ret = mtd->_block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); 185 if (ret < 0) { 186 printk(KERN_ERR "mtdoops: block_markbad failed, aborting.\n"); 187 return; 188 } 189 } 190 goto badblock; 191} 192 193static void mtdoops_write(struct mtdoops_context *cxt, int panic) 194{ 195 struct mtd_info *mtd = cxt->mtd; 196 size_t retlen; 197 int ret; 198 char *buff = NULL; 199 int i; 200// printk("********* mtdoops_write ********\n"); 201 if (cxt->writecount < OOPS_PAGE_SIZE) 202 memset(cxt->oops_buf + cxt->writecount, 0xff, 203 OOPS_PAGE_SIZE - cxt->writecount); 204 205 if (panic) 206 ret = mtd->_panic_write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 207 OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); 208 else 209 { 210// printk("********* %d\n",panic); 211 // mtd_write(mtd,0,2048, &retlen, buff); 212 buff = (char *)cxt->oops_buf; 213 for(i = 0; i < OOPS_PAGE_SIZE; i++) 214 printk("%c",buff[i]); 215// printk("***** ret = mtd_write *****\n"); 216 ret = mtd_write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 217 OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); 218 } 219 cxt->writecount = 0; 220 printk("***** if ((relen *****\n"); 221 if ((retlen != OOPS_PAGE_SIZE) || (ret < 0)) 222 printk(KERN_ERR "mtdoops: Write failure at %d (%td of %d written), err %d.\n", 223 cxt->nextpage * OOPS_PAGE_SIZE, retlen, OOPS_PAGE_SIZE, ret); 224// printk("**** mtdoops_inc_counter ****\n"); 225 mtdoops_inc_counter(cxt); 226} 227 228static void find_next_position(struct mtdoops_context *cxt) 229{ 230 struct mtd_info *mtd = cxt->mtd; 231 int ret, page, maxpos = 0; 232 u32 count[2], maxcount = 0xffffffff; 233 size_t retlen; 234 235 for (page = 0; page < cxt->oops_pages; page++) { 236 ret = mtd->_read(mtd, page * OOPS_PAGE_SIZE, 8, &retlen, (u_char *) &count[0]); 237 if ((retlen != 8) || ((ret < 0) && (ret != -EUCLEAN))) { 238 printk(KERN_ERR "mtdoops: Read failure at %d (%td of 8 read)" 239 ", err %d.\n", page * OOPS_PAGE_SIZE, retlen, ret); 240 continue; 241 } 242 243 if (count[1] != MTDOOPS_KERNMSG_MAGIC) 244 continue; 245 if (count[0] == 0xffffffff) 246 continue; 247 if (maxcount == 0xffffffff) { 248 maxcount = count[0]; 249 maxpos = page; 250 } else if ((count[0] < 0x40000000) && (maxcount > 0xc0000000)) { 251 maxcount = count[0]; 252 maxpos = page; 253 } else if ((count[0] > maxcount) && (count[0] < 0xc0000000)) { 254 maxcount = count[0]; 255 maxpos = page; 256 } else if ((count[0] > maxcount) && (count[0] > 0xc0000000) 257 && (maxcount > 0x80000000)) { 258 maxcount = count[0]; 259 maxpos = page; 260 } 261 } 262 if (maxcount == 0xffffffff) { 263 cxt->nextpage = 0; 264 cxt->nextcount = 1; 265 schedule_work(&cxt->work_erase); 266 return; 267 } 268 269 cxt->nextpage = maxpos; 270 cxt->nextcount = maxcount; 271 272 mtdoops_inc_counter(cxt); 273} 274 275 276static void mtdoops_notify_add(struct mtd_info *mtd) 277{ 278 struct mtdoops_context *cxt = &oops_cxt; 279 280 if (cxt->name && !strcmp(mtd->name, cxt->name)) 281 cxt->mtd_index = mtd->index; 282 283 if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) 284 return; 285 286 if (mtd->size < (mtd->erasesize * 1)) { 287 printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n", 288 mtd->index); 289 return; 290 } 291 292 if (mtd->erasesize < OOPS_PAGE_SIZE) { 293 printk(KERN_ERR "Eraseblock size of MTD partition %d too small\n", 294 mtd->index); 295 return; 296 } 297 298 cxt->mtd = mtd; 299 cxt->oops_pages = (int)mtd->size / OOPS_PAGE_SIZE; 300 301 find_next_position(cxt); 302 303 printk(KERN_INFO "mtdoops: Attached to MTD device %d name %s\n", mtd->index, mtd->name); 304} 305 306static void mtdoops_notify_remove(struct mtd_info *mtd) 307{ 308 struct mtdoops_context *cxt = &oops_cxt; 309 310 if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) 311 return; 312 313 cxt->mtd = NULL; 314 flush_scheduled_work(); 315} 316 317static void mtdoops_console_sync(void) 318{ 319 struct mtdoops_context *cxt = &oops_cxt; 320 struct mtd_info *mtd = cxt->mtd; 321 322 if (!cxt->ready || !mtd || cxt->writecount == 0) 323 return; 324 325 /* 326 * Once ready is 0 and we've held the lock no further writes to the 327 * buffer will happen 328 */ 329 if (!cxt->ready) { 330 return; 331 } 332 333 cxt->ready = 0; 334 335 if (mtd->_panic_write){ 336 /* Interrupt context, we're going to panic so try and log */ 337 mtdoops_write(cxt, 1); 338 } else { 339 /* we're going to panic so try and log */ 340 mtdoops_write(cxt, 0); 341 } 342} 343 344static void 345mtdoops_console_write(const char *s, unsigned int count) 346{ 347 struct mtdoops_context *cxt = &oops_cxt; 348 struct mtd_info *mtd = cxt->mtd; 349 350 if (!cxt->ready || !mtd) 351 return; 352 353 /* Check ready status didn't change whilst waiting for the lock */ 354 if (!cxt->ready) { 355 return; 356 } 357 if (cxt->writecount == 0) { 358 u32 *stamp = cxt->oops_buf; 359 *stamp++ = cxt->nextcount; 360 *stamp = MTDOOPS_KERNMSG_MAGIC; 361 cxt->writecount = 8; 362 } 363 364 if ((count + cxt->writecount) > OOPS_PAGE_SIZE) 365 count = OOPS_PAGE_SIZE - cxt->writecount; 366 367 /* Skip the first 8 byte for mtdoops magic number */ 368 memcpy(cxt->oops_buf + cxt->writecount, s + 8, count); 369 cxt->writecount += count; 370 371 //if (cxt->writecount == OOPS_PAGE_SIZE) 372 mtdoops_console_sync(); 373} 374 375static struct mtd_notifier mtdoops_notifier = { 376 .add = mtdoops_notify_add, 377 .remove = mtdoops_notify_remove, 378}; 379 380static int console_panic_event(struct notifier_block *blk, unsigned long event, void *ptr) 381{ 382 char log_buff[OOPS_PAGE_SIZE]; 383// int retlen = 0; 384 int count = 0; 385 386 count = redirect_console_oops_msg(log_buff, OOPS_PAGE_SIZE); 387 mtdoops_console_write(log_buff, count); 388// printk("******** return ********\n"); 389 return NOTIFY_DONE; 390} 391 392static struct notifier_block console_panic_block = { 393 .notifier_call = console_panic_event, 394 .next = NULL, 395 .priority = INT_MAX /* try to do it first */ 396}; 397 398static int __init mtdoops_console_init(void) 399{ 400 struct mtdoops_context *cxt = &oops_cxt; 401 402 cxt->mtd_index = -1; 403 cxt->name = oops_part; 404 cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE); 405 406 if (!cxt->oops_buf) { 407 printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n"); 408 return -ENOMEM; 409 } 410 411 INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); 412 413 register_mtd_user(&mtdoops_notifier); 414 atomic_notifier_chain_register(&panic_notifier_list, &console_panic_block); 415 416 return 0; 417} 418 419static void __exit mtdoops_console_exit(void) 420{ 421 struct mtdoops_context *cxt = &oops_cxt; 422 423 unregister_mtd_user(&mtdoops_notifier); 424 atomic_notifier_chain_unregister(&panic_notifier_list, &console_panic_block); 425 kfree(cxt->name); 426 vfree(cxt->oops_buf); 427} 428 429 430subsys_initcall(mtdoops_console_init); 431module_exit(mtdoops_console_exit); 432 433MODULE_LICENSE("GPL"); 434MODULE_AUTHOR("Richard Purdie <rpurdie@delta.com>"); 435MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); 436