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