diff options
author | Simon Kagstrom <simon.kagstrom@netinsight.net> | 2009-10-16 08:09:18 -0400 |
---|---|---|
committer | David Woodhouse <David.Woodhouse@intel.com> | 2009-11-30 07:01:49 -0500 |
commit | 456b565cc52fbcdaa2e19ffdf40d9dd3b726d603 (patch) | |
tree | c73029f31756289ed54183e97e0613f87cef6ed5 | |
parent | 7cb777a3d71f9d1f7eb149c7a504d21f24219ae8 (diff) |
core: Add kernel message dumper to call on oopses and panics
The core functionality is implemented as per Linus suggestion from
http://lists.infradead.org/pipermail/linux-mtd/2009-October/027620.html
(with the kmsg_dump implementation by Linus). A struct kmsg_dumper has
been added which contains a callback to dump the kernel log buffers on
crashes. The kmsg_dump function gets called from oops_exit() and panic()
and invokes this callbacks with the crash reason.
[dwmw2: Fix log_end handling]
Signed-off-by: Simon Kagstrom <simon.kagstrom@netinsight.net>
Reviewed-by: Anders Grafstrom <anders.grafstrom@netinsight.net>
Reviewed-by: Linus Torvalds <torvalds@linux-foundation.org>
Acked-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
-rw-r--r-- | include/linux/kmsg_dump.h | 44 | ||||
-rw-r--r-- | kernel/panic.c | 3 | ||||
-rw-r--r-- | kernel/printk.c | 119 |
3 files changed, 166 insertions, 0 deletions
diff --git a/include/linux/kmsg_dump.h b/include/linux/kmsg_dump.h new file mode 100644 index 000000000000..7f089ec5ef32 --- /dev/null +++ b/include/linux/kmsg_dump.h | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * linux/include/kmsg_dump.h | ||
3 | * | ||
4 | * Copyright (C) 2009 Net Insight AB | ||
5 | * | ||
6 | * Author: Simon Kagstrom <simon.kagstrom@netinsight.net> | ||
7 | * | ||
8 | * This file is subject to the terms and conditions of the GNU General Public | ||
9 | * License. See the file COPYING in the main directory of this archive | ||
10 | * for more details. | ||
11 | */ | ||
12 | #ifndef _LINUX_KMSG_DUMP_H | ||
13 | #define _LINUX_KMSG_DUMP_H | ||
14 | |||
15 | #include <linux/list.h> | ||
16 | |||
17 | enum kmsg_dump_reason { | ||
18 | KMSG_DUMP_OOPS, | ||
19 | KMSG_DUMP_PANIC, | ||
20 | }; | ||
21 | |||
22 | /** | ||
23 | * struct kmsg_dumper - kernel crash message dumper structure | ||
24 | * @dump: The callback which gets called on crashes. The buffer is passed | ||
25 | * as two sections, where s1 (length l1) contains the older | ||
26 | * messages and s2 (length l2) contains the newer. | ||
27 | * @list: Entry in the dumper list (private) | ||
28 | * @registered: Flag that specifies if this is already registered | ||
29 | */ | ||
30 | struct kmsg_dumper { | ||
31 | void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason, | ||
32 | const char *s1, unsigned long l1, | ||
33 | const char *s2, unsigned long l2); | ||
34 | struct list_head list; | ||
35 | int registered; | ||
36 | }; | ||
37 | |||
38 | void kmsg_dump(enum kmsg_dump_reason reason); | ||
39 | |||
40 | int kmsg_dump_register(struct kmsg_dumper *dumper); | ||
41 | |||
42 | int kmsg_dump_unregister(struct kmsg_dumper *dumper); | ||
43 | |||
44 | #endif /* _LINUX_KMSG_DUMP_H */ | ||
diff --git a/kernel/panic.c b/kernel/panic.c index bcdef26e3332..8c43226a544d 100644 --- a/kernel/panic.c +++ b/kernel/panic.c | |||
@@ -10,6 +10,7 @@ | |||
10 | */ | 10 | */ |
11 | #include <linux/debug_locks.h> | 11 | #include <linux/debug_locks.h> |
12 | #include <linux/interrupt.h> | 12 | #include <linux/interrupt.h> |
13 | #include <linux/kmsg_dump.h> | ||
13 | #include <linux/kallsyms.h> | 14 | #include <linux/kallsyms.h> |
14 | #include <linux/notifier.h> | 15 | #include <linux/notifier.h> |
15 | #include <linux/module.h> | 16 | #include <linux/module.h> |
@@ -74,6 +75,7 @@ NORET_TYPE void panic(const char * fmt, ...) | |||
74 | dump_stack(); | 75 | dump_stack(); |
75 | #endif | 76 | #endif |
76 | 77 | ||
78 | kmsg_dump(KMSG_DUMP_PANIC); | ||
77 | /* | 79 | /* |
78 | * If we have crashed and we have a crash kernel loaded let it handle | 80 | * If we have crashed and we have a crash kernel loaded let it handle |
79 | * everything else. | 81 | * everything else. |
@@ -338,6 +340,7 @@ void oops_exit(void) | |||
338 | { | 340 | { |
339 | do_oops_enter_exit(); | 341 | do_oops_enter_exit(); |
340 | print_oops_end_marker(); | 342 | print_oops_end_marker(); |
343 | kmsg_dump(KMSG_DUMP_OOPS); | ||
341 | } | 344 | } |
342 | 345 | ||
343 | #ifdef WANT_WARN_ON_SLOWPATH | 346 | #ifdef WANT_WARN_ON_SLOWPATH |
diff --git a/kernel/printk.c b/kernel/printk.c index f38b07f78a4e..051d1f50648f 100644 --- a/kernel/printk.c +++ b/kernel/printk.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <linux/bootmem.h> | 33 | #include <linux/bootmem.h> |
34 | #include <linux/syscalls.h> | 34 | #include <linux/syscalls.h> |
35 | #include <linux/kexec.h> | 35 | #include <linux/kexec.h> |
36 | #include <linux/kmsg_dump.h> | ||
36 | 37 | ||
37 | #include <asm/uaccess.h> | 38 | #include <asm/uaccess.h> |
38 | 39 | ||
@@ -1405,3 +1406,121 @@ bool printk_timed_ratelimit(unsigned long *caller_jiffies, | |||
1405 | } | 1406 | } |
1406 | EXPORT_SYMBOL(printk_timed_ratelimit); | 1407 | EXPORT_SYMBOL(printk_timed_ratelimit); |
1407 | #endif | 1408 | #endif |
1409 | |||
1410 | static DEFINE_SPINLOCK(dump_list_lock); | ||
1411 | static LIST_HEAD(dump_list); | ||
1412 | |||
1413 | /** | ||
1414 | * kmsg_dump_register - register a kernel log dumper. | ||
1415 | * @dump: pointer to the kmsg_dumper structure | ||
1416 | * | ||
1417 | * Adds a kernel log dumper to the system. The dump callback in the | ||
1418 | * structure will be called when the kernel oopses or panics and must be | ||
1419 | * set. Returns zero on success and %-EINVAL or %-EBUSY otherwise. | ||
1420 | */ | ||
1421 | int kmsg_dump_register(struct kmsg_dumper *dumper) | ||
1422 | { | ||
1423 | unsigned long flags; | ||
1424 | int err = -EBUSY; | ||
1425 | |||
1426 | /* The dump callback needs to be set */ | ||
1427 | if (!dumper->dump) | ||
1428 | return -EINVAL; | ||
1429 | |||
1430 | spin_lock_irqsave(&dump_list_lock, flags); | ||
1431 | /* Don't allow registering multiple times */ | ||
1432 | if (!dumper->registered) { | ||
1433 | dumper->registered = 1; | ||
1434 | list_add_tail(&dumper->list, &dump_list); | ||
1435 | err = 0; | ||
1436 | } | ||
1437 | spin_unlock_irqrestore(&dump_list_lock, flags); | ||
1438 | |||
1439 | return err; | ||
1440 | } | ||
1441 | EXPORT_SYMBOL_GPL(kmsg_dump_register); | ||
1442 | |||
1443 | /** | ||
1444 | * kmsg_dump_unregister - unregister a kmsg dumper. | ||
1445 | * @dump: pointer to the kmsg_dumper structure | ||
1446 | * | ||
1447 | * Removes a dump device from the system. Returns zero on success and | ||
1448 | * %-EINVAL otherwise. | ||
1449 | */ | ||
1450 | int kmsg_dump_unregister(struct kmsg_dumper *dumper) | ||
1451 | { | ||
1452 | unsigned long flags; | ||
1453 | int err = -EINVAL; | ||
1454 | |||
1455 | spin_lock_irqsave(&dump_list_lock, flags); | ||
1456 | if (dumper->registered) { | ||
1457 | dumper->registered = 0; | ||
1458 | list_del(&dumper->list); | ||
1459 | err = 0; | ||
1460 | } | ||
1461 | spin_unlock_irqrestore(&dump_list_lock, flags); | ||
1462 | |||
1463 | return err; | ||
1464 | } | ||
1465 | EXPORT_SYMBOL_GPL(kmsg_dump_unregister); | ||
1466 | |||
1467 | static const char const *kmsg_reasons[] = { | ||
1468 | [KMSG_DUMP_OOPS] = "oops", | ||
1469 | [KMSG_DUMP_PANIC] = "panic", | ||
1470 | }; | ||
1471 | |||
1472 | static const char *kmsg_to_str(enum kmsg_dump_reason reason) | ||
1473 | { | ||
1474 | if (reason >= ARRAY_SIZE(kmsg_reasons) || reason < 0) | ||
1475 | return "unknown"; | ||
1476 | |||
1477 | return kmsg_reasons[reason]; | ||
1478 | } | ||
1479 | |||
1480 | /** | ||
1481 | * kmsg_dump - dump kernel log to kernel message dumpers. | ||
1482 | * @reason: the reason (oops, panic etc) for dumping | ||
1483 | * | ||
1484 | * Iterate through each of the dump devices and call the oops/panic | ||
1485 | * callbacks with the log buffer. | ||
1486 | */ | ||
1487 | void kmsg_dump(enum kmsg_dump_reason reason) | ||
1488 | { | ||
1489 | unsigned long end; | ||
1490 | unsigned chars; | ||
1491 | struct kmsg_dumper *dumper; | ||
1492 | const char *s1, *s2; | ||
1493 | unsigned long l1, l2; | ||
1494 | unsigned long flags; | ||
1495 | |||
1496 | /* Theoretically, the log could move on after we do this, but | ||
1497 | there's not a lot we can do about that. The new messages | ||
1498 | will overwrite the start of what we dump. */ | ||
1499 | spin_lock_irqsave(&logbuf_lock, flags); | ||
1500 | end = log_end & LOG_BUF_MASK; | ||
1501 | chars = logged_chars; | ||
1502 | spin_unlock_irqrestore(&logbuf_lock, flags); | ||
1503 | |||
1504 | if (logged_chars > end) { | ||
1505 | s1 = log_buf + log_buf_len - logged_chars + end; | ||
1506 | l1 = logged_chars - end; | ||
1507 | |||
1508 | s2 = log_buf; | ||
1509 | l2 = end; | ||
1510 | } else { | ||
1511 | s1 = ""; | ||
1512 | l1 = 0; | ||
1513 | |||
1514 | s2 = log_buf + end - logged_chars; | ||
1515 | l2 = logged_chars; | ||
1516 | } | ||
1517 | |||
1518 | if (!spin_trylock_irqsave(&dump_list_lock, flags)) { | ||
1519 | printk(KERN_ERR "dump_kmsg: dump list lock is held during %s, skipping dump\n", | ||
1520 | kmsg_to_str(reason)); | ||
1521 | return; | ||
1522 | } | ||
1523 | list_for_each_entry(dumper, &dump_list, list) | ||
1524 | dumper->dump(dumper, reason, s1, l1, s2, l2); | ||
1525 | spin_unlock_irqrestore(&dump_list_lock, flags); | ||
1526 | } | ||