diff options
Diffstat (limited to 'kernel/printk.c')
-rw-r--r-- | kernel/printk.c | 119 |
1 files changed, 119 insertions, 0 deletions
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 | } | ||