#include <linux/workqueue.h>
#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>
+#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00
#define OOPS_PAGE_SIZE 4096
static struct mtdoops_context {
int mtd_index;
- struct work_struct work;
+ struct work_struct work_erase;
+ struct work_struct work_write;
struct mtd_info *mtd;
int oops_pages;
int nextpage;
int nextcount;
+ char *name;
void *oops_buf;
+
+ /* writecount and disabling ready are spin lock protected */
+ spinlock_t writecount_lock;
int ready;
int writecount;
} oops_cxt;
erase.mtd = mtd;
erase.callback = mtdoops_erase_callback;
erase.addr = offset;
- if (mtd->erasesize < OOPS_PAGE_SIZE)
- erase.len = OOPS_PAGE_SIZE;
- else
- erase.len = mtd->erasesize;
+ erase.len = mtd->erasesize;
erase.priv = (u_long)&wait_q;
set_current_state(TASK_INTERRUPTIBLE);
if (ret) {
set_current_state(TASK_RUNNING);
remove_wait_queue(&wait_q, &wait);
- printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
+ printk (KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] "
"on \"%s\" failed\n",
- erase.addr, erase.len, mtd->name);
+ (unsigned long long)erase.addr, (unsigned long long)erase.len, mtd->name);
return ret;
}
return 0;
}
-static int mtdoops_inc_counter(struct mtdoops_context *cxt)
+static void mtdoops_inc_counter(struct mtdoops_context *cxt)
{
struct mtd_info *mtd = cxt->mtd;
size_t retlen;
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)
printk(KERN_ERR "mtdoops: Read failure at %d (%td of 4 read)"
", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE,
retlen, ret);
- return 1;
+ schedule_work(&cxt->work_erase);
+ return;
}
/* See if we need to erase the next block */
- if (count != 0xffffffff)
- return 1;
+ if (count != 0xffffffff) {
+ schedule_work(&cxt->work_erase);
+ return;
+ }
printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n",
cxt->nextpage, cxt->nextcount);
cxt->ready = 1;
- return 0;
}
-static void mtdoops_prepare(struct mtdoops_context *cxt)
+/* Scheduled work - when we can't proceed without erasing a block */
+static void mtdoops_workfunc_erase(struct work_struct *work)
{
+ struct mtdoops_context *cxt =
+ container_of(work, struct mtdoops_context, work_erase);
struct mtd_info *mtd = cxt->mtd;
int i = 0, j, ret, mod;
mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize;
if (mod != 0) {
cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE);
- if (cxt->nextpage > cxt->oops_pages)
+ if (cxt->nextpage >= cxt->oops_pages)
cxt->nextpage = 0;
}
cxt->nextpage * OOPS_PAGE_SIZE);
i++;
cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE);
- if (cxt->nextpage > cxt->oops_pages)
+ 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");
goto badblock;
}
-static void mtdoops_workfunc(struct work_struct *work)
+static void mtdoops_write(struct mtdoops_context *cxt, int panic)
{
- struct mtdoops_context *cxt =
- container_of(work, struct mtdoops_context, work);
+ struct mtd_info *mtd = cxt->mtd;
+ size_t retlen;
+ int ret;
+
+ if (cxt->writecount < OOPS_PAGE_SIZE)
+ memset(cxt->oops_buf + cxt->writecount, 0xff,
+ OOPS_PAGE_SIZE - cxt->writecount);
- mtdoops_prepare(cxt);
+ 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);
+
+ cxt->writecount = 0;
+
+ 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);
+
+ mtdoops_inc_counter(cxt);
}
-static int find_next_position(struct mtdoops_context *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)"
+ ret = mtd->read(mtd, page * OOPS_PAGE_SIZE, 8, &retlen, (u_char *) &count[0]);
+ if ((retlen != 8) || ((ret < 0) && (ret != -EUCLEAN))) {
+ printk(KERN_ERR "mtdoops: Read failure at %d (%td of 8 read)"
", err %d.\n", page * OOPS_PAGE_SIZE, retlen, ret);
continue;
}
- if (count == 0xffffffff)
+ if (count[1] != MTDOOPS_KERNMSG_MAGIC)
+ continue;
+ if (count[0] == 0xffffffff)
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)
+ } else if ((count[0] > maxcount) && (count[0] > 0xc0000000)
&& (maxcount > 0x80000000)) {
- maxcount = count;
+ 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 0;
+ schedule_work(&cxt->work_erase);
+ return;
}
cxt->nextpage = maxpos;
cxt->nextcount = maxcount;
- return mtdoops_inc_counter(cxt);
+ mtdoops_inc_counter(cxt);
}
static void mtdoops_notify_add(struct mtd_info *mtd)
{
struct mtdoops_context *cxt = &oops_cxt;
- int ret;
+
+ if (cxt->name && !strcmp(mtd->name, cxt->name))
+ cxt->mtd_index = mtd->index;
if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
return;
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;
+ if (mtd->size > INT_MAX)
+ cxt->oops_pages = INT_MAX / OOPS_PAGE_SIZE;
+ else
+ cxt->oops_pages = (int)mtd->size / OOPS_PAGE_SIZE;
- ret = find_next_position(cxt);
- if (ret == 1)
- mtdoops_prepare(cxt);
+ find_next_position(cxt);
- printk(KERN_DEBUG "mtdoops: Attached to MTD device %d\n", mtd->index);
+ printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index);
}
static void mtdoops_notify_remove(struct mtd_info *mtd)
{
struct mtdoops_context *cxt = &oops_cxt;
struct mtd_info *mtd = cxt->mtd;
- size_t retlen;
- int ret;
+ unsigned long flags;
- if (!cxt->ready || !mtd)
+ if (!cxt->ready || !mtd || cxt->writecount == 0)
return;
- if (cxt->writecount == 0)
+ /*
+ * 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);
return;
-
- if (cxt->writecount < OOPS_PAGE_SIZE)
- memset(cxt->oops_buf + cxt->writecount, 0xff,
- OOPS_PAGE_SIZE - cxt->writecount);
-
- ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
- OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
+ }
cxt->ready = 0;
- cxt->writecount = 0;
-
- 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);
+ spin_unlock_irqrestore(&cxt->writecount_lock, flags);
- ret = mtdoops_inc_counter(cxt);
- if (ret == 1)
- schedule_work(&cxt->work);
+ 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
{
struct mtdoops_context *cxt = co->data;
struct mtd_info *mtd = cxt->mtd;
+ unsigned long flags;
if (!oops_in_progress) {
mtdoops_console_sync();
if (!cxt->ready || !mtd)
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) {
+ spin_unlock_irqrestore(&cxt->writecount_lock, flags);
+ return;
+ }
+
if (cxt->writecount == 0) {
u32 *stamp = cxt->oops_buf;
- *stamp = cxt->nextcount;
- cxt->writecount = 4;
+ *stamp++ = cxt->nextcount;
+ *stamp = MTDOOPS_KERNMSG_MAGIC;
+ cxt->writecount = 8;
}
if ((count + cxt->writecount) > OOPS_PAGE_SIZE)
memcpy(cxt->oops_buf + cxt->writecount, s, count);
cxt->writecount += count;
+
+ spin_unlock_irqrestore(&cxt->writecount_lock, flags);
+
+ if (cxt->writecount == OOPS_PAGE_SIZE)
+ mtdoops_console_sync();
}
static int __init mtdoops_console_setup(struct console *co, char *options)
{
struct mtdoops_context *cxt = co->data;
- if (cxt->mtd_index != -1)
+ if (cxt->mtd_index != -1 || cxt->name)
return -EBUSY;
+ if (options) {
+ cxt->name = kstrdup(options, GFP_KERNEL);
+ return 0;
+ }
if (co->index == -1)
return -EINVAL;
.write = mtdoops_console_write,
.setup = mtdoops_console_setup,
.unblank = mtdoops_console_sync,
- .flags = CON_PRINTBUFFER,
.index = -1,
.data = &oops_cxt,
};
cxt->mtd_index = -1;
cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE);
+ spin_lock_init(&cxt->writecount_lock);
if (!cxt->oops_buf) {
- printk(KERN_ERR "Failed to allocate oops buffer workspace\n");
+ printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n");
return -ENOMEM;
}
- INIT_WORK(&cxt->work, mtdoops_workfunc);
+ 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);
unregister_mtd_user(&mtdoops_notifier);
unregister_console(&mtdoops_console);
+ kfree(cxt->name);
vfree(cxt->oops_buf);
}