diff options
| -rw-r--r-- | MAINTAINERS | 2 | ||||
| -rw-r--r-- | arch/powerpc/platforms/pseries/nvram.c | 61 | ||||
| -rw-r--r-- | arch/x86/platform/mrst/early_printk_mrst.c | 13 | ||||
| -rw-r--r-- | drivers/base/dd.c | 2 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max8997.c | 5 | ||||
| -rw-r--r-- | drivers/extcon/extcon_class.c | 2 | ||||
| -rw-r--r-- | drivers/extcon/extcon_gpio.c | 2 | ||||
| -rw-r--r-- | drivers/mtd/mtdoops.c | 22 | ||||
| -rw-r--r-- | fs/pstore/platform.c | 34 | ||||
| -rw-r--r-- | include/linux/kmsg_dump.h | 45 | ||||
| -rw-r--r-- | kernel/printk.c | 241 | ||||
| -rw-r--r-- | tools/hv/hv_kvp_daemon.c | 10 |
12 files changed, 289 insertions, 150 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index a81b298d9c75..eb22272b2116 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
| @@ -7421,7 +7421,7 @@ F: include/linux/vlynq.h | |||
| 7421 | 7421 | ||
| 7422 | VME SUBSYSTEM | 7422 | VME SUBSYSTEM |
| 7423 | M: Martyn Welch <martyn.welch@ge.com> | 7423 | M: Martyn Welch <martyn.welch@ge.com> |
| 7424 | M: Manohar Vanga <manohar.vanga@cern.ch> | 7424 | M: Manohar Vanga <manohar.vanga@gmail.com> |
| 7425 | M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 7425 | M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| 7426 | L: devel@driverdev.osuosl.org | 7426 | L: devel@driverdev.osuosl.org |
| 7427 | S: Maintained | 7427 | S: Maintained |
diff --git a/arch/powerpc/platforms/pseries/nvram.c b/arch/powerpc/platforms/pseries/nvram.c index 36f957f31842..8733a86ad52e 100644 --- a/arch/powerpc/platforms/pseries/nvram.c +++ b/arch/powerpc/platforms/pseries/nvram.c | |||
| @@ -68,9 +68,7 @@ static const char *pseries_nvram_os_partitions[] = { | |||
| 68 | }; | 68 | }; |
| 69 | 69 | ||
| 70 | static void oops_to_nvram(struct kmsg_dumper *dumper, | 70 | static void oops_to_nvram(struct kmsg_dumper *dumper, |
| 71 | enum kmsg_dump_reason reason, | 71 | enum kmsg_dump_reason reason); |
| 72 | const char *old_msgs, unsigned long old_len, | ||
| 73 | const char *new_msgs, unsigned long new_len); | ||
| 74 | 72 | ||
| 75 | static struct kmsg_dumper nvram_kmsg_dumper = { | 73 | static struct kmsg_dumper nvram_kmsg_dumper = { |
| 76 | .dump = oops_to_nvram | 74 | .dump = oops_to_nvram |
| @@ -504,28 +502,6 @@ int __init pSeries_nvram_init(void) | |||
| 504 | } | 502 | } |
| 505 | 503 | ||
| 506 | /* | 504 | /* |
| 507 | * Try to capture the last capture_len bytes of the printk buffer. Return | ||
| 508 | * the amount actually captured. | ||
| 509 | */ | ||
| 510 | static size_t capture_last_msgs(const char *old_msgs, size_t old_len, | ||
| 511 | const char *new_msgs, size_t new_len, | ||
| 512 | char *captured, size_t capture_len) | ||
| 513 | { | ||
| 514 | if (new_len >= capture_len) { | ||
| 515 | memcpy(captured, new_msgs + (new_len - capture_len), | ||
| 516 | capture_len); | ||
| 517 | return capture_len; | ||
| 518 | } else { | ||
| 519 | /* Grab the end of old_msgs. */ | ||
| 520 | size_t old_tail_len = min(old_len, capture_len - new_len); | ||
| 521 | memcpy(captured, old_msgs + (old_len - old_tail_len), | ||
| 522 | old_tail_len); | ||
| 523 | memcpy(captured + old_tail_len, new_msgs, new_len); | ||
| 524 | return old_tail_len + new_len; | ||
| 525 | } | ||
| 526 | } | ||
| 527 | |||
| 528 | /* | ||
| 529 | * Are we using the ibm,rtas-log for oops/panic reports? And if so, | 505 | * Are we using the ibm,rtas-log for oops/panic reports? And if so, |
| 530 | * would logging this oops/panic overwrite an RTAS event that rtas_errd | 506 | * would logging this oops/panic overwrite an RTAS event that rtas_errd |
| 531 | * hasn't had a chance to read and process? Return 1 if so, else 0. | 507 | * hasn't had a chance to read and process? Return 1 if so, else 0. |
| @@ -541,27 +517,6 @@ static int clobbering_unread_rtas_event(void) | |||
| 541 | NVRAM_RTAS_READ_TIMEOUT); | 517 | NVRAM_RTAS_READ_TIMEOUT); |
| 542 | } | 518 | } |
| 543 | 519 | ||
| 544 | /* Squeeze out each line's <n> severity prefix. */ | ||
| 545 | static size_t elide_severities(char *buf, size_t len) | ||
| 546 | { | ||
| 547 | char *in, *out, *buf_end = buf + len; | ||
| 548 | /* Assume a <n> at the very beginning marks the start of a line. */ | ||
| 549 | int newline = 1; | ||
| 550 | |||
| 551 | in = out = buf; | ||
| 552 | while (in < buf_end) { | ||
| 553 | if (newline && in+3 <= buf_end && | ||
| 554 | *in == '<' && isdigit(in[1]) && in[2] == '>') { | ||
| 555 | in += 3; | ||
| 556 | newline = 0; | ||
| 557 | } else { | ||
| 558 | newline = (*in == '\n'); | ||
| 559 | *out++ = *in++; | ||
| 560 | } | ||
| 561 | } | ||
| 562 | return out - buf; | ||
| 563 | } | ||
| 564 | |||
| 565 | /* Derived from logfs_compress() */ | 520 | /* Derived from logfs_compress() */ |
| 566 | static int nvram_compress(const void *in, void *out, size_t inlen, | 521 | static int nvram_compress(const void *in, void *out, size_t inlen, |
| 567 | size_t outlen) | 522 | size_t outlen) |
| @@ -619,9 +574,7 @@ static int zip_oops(size_t text_len) | |||
| 619 | * partition. If that's too much, go back and capture uncompressed text. | 574 | * partition. If that's too much, go back and capture uncompressed text. |
| 620 | */ | 575 | */ |
| 621 | static void oops_to_nvram(struct kmsg_dumper *dumper, | 576 | static void oops_to_nvram(struct kmsg_dumper *dumper, |
| 622 | enum kmsg_dump_reason reason, | 577 | enum kmsg_dump_reason reason) |
| 623 | const char *old_msgs, unsigned long old_len, | ||
| 624 | const char *new_msgs, unsigned long new_len) | ||
| 625 | { | 578 | { |
| 626 | static unsigned int oops_count = 0; | 579 | static unsigned int oops_count = 0; |
| 627 | static bool panicking = false; | 580 | static bool panicking = false; |
| @@ -660,14 +613,14 @@ static void oops_to_nvram(struct kmsg_dumper *dumper, | |||
| 660 | return; | 613 | return; |
| 661 | 614 | ||
| 662 | if (big_oops_buf) { | 615 | if (big_oops_buf) { |
| 663 | text_len = capture_last_msgs(old_msgs, old_len, | 616 | kmsg_dump_get_buffer(dumper, false, |
| 664 | new_msgs, new_len, big_oops_buf, big_oops_buf_sz); | 617 | big_oops_buf, big_oops_buf_sz, &text_len); |
| 665 | text_len = elide_severities(big_oops_buf, text_len); | ||
| 666 | rc = zip_oops(text_len); | 618 | rc = zip_oops(text_len); |
| 667 | } | 619 | } |
| 668 | if (rc != 0) { | 620 | if (rc != 0) { |
| 669 | text_len = capture_last_msgs(old_msgs, old_len, | 621 | kmsg_dump_rewind(dumper); |
| 670 | new_msgs, new_len, oops_data, oops_data_sz); | 622 | kmsg_dump_get_buffer(dumper, true, |
| 623 | oops_data, oops_data_sz, &text_len); | ||
| 671 | err_type = ERR_TYPE_KERNEL_PANIC; | 624 | err_type = ERR_TYPE_KERNEL_PANIC; |
| 672 | *oops_len = (u16) text_len; | 625 | *oops_len = (u16) text_len; |
| 673 | } | 626 | } |
diff --git a/arch/x86/platform/mrst/early_printk_mrst.c b/arch/x86/platform/mrst/early_printk_mrst.c index 3c6e328483c7..028454f0c3a5 100644 --- a/arch/x86/platform/mrst/early_printk_mrst.c +++ b/arch/x86/platform/mrst/early_printk_mrst.c | |||
| @@ -110,19 +110,16 @@ static struct kmsg_dumper dw_dumper; | |||
| 110 | static int dumper_registered; | 110 | static int dumper_registered; |
| 111 | 111 | ||
| 112 | static void dw_kmsg_dump(struct kmsg_dumper *dumper, | 112 | static void dw_kmsg_dump(struct kmsg_dumper *dumper, |
| 113 | enum kmsg_dump_reason reason, | 113 | enum kmsg_dump_reason reason) |
| 114 | const char *s1, unsigned long l1, | ||
| 115 | const char *s2, unsigned long l2) | ||
| 116 | { | 114 | { |
| 117 | int i; | 115 | static char line[1024]; |
| 116 | size_t len; | ||
| 118 | 117 | ||
| 119 | /* When run to this, we'd better re-init the HW */ | 118 | /* When run to this, we'd better re-init the HW */ |
| 120 | mrst_early_console_init(); | 119 | mrst_early_console_init(); |
| 121 | 120 | ||
| 122 | for (i = 0; i < l1; i++) | 121 | while (kmsg_dump_get_line(dumper, true, line, sizeof(line), &len)) |
| 123 | early_mrst_console.write(&early_mrst_console, s1 + i, 1); | 122 | early_mrst_console.write(&early_mrst_console, line, len); |
| 124 | for (i = 0; i < l2; i++) | ||
| 125 | early_mrst_console.write(&early_mrst_console, s2 + i, 1); | ||
| 126 | } | 123 | } |
| 127 | 124 | ||
| 128 | /* Set the ratio rate to 115200, 8n1, IRQ disabled */ | 125 | /* Set the ratio rate to 115200, 8n1, IRQ disabled */ |
diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 1b1cbb571d38..dcb8a6e48692 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c | |||
| @@ -100,7 +100,7 @@ static void driver_deferred_probe_add(struct device *dev) | |||
| 100 | mutex_lock(&deferred_probe_mutex); | 100 | mutex_lock(&deferred_probe_mutex); |
| 101 | if (list_empty(&dev->p->deferred_probe)) { | 101 | if (list_empty(&dev->p->deferred_probe)) { |
| 102 | dev_dbg(dev, "Added to deferred list\n"); | 102 | dev_dbg(dev, "Added to deferred list\n"); |
| 103 | list_add(&dev->p->deferred_probe, &deferred_probe_pending_list); | 103 | list_add_tail(&dev->p->deferred_probe, &deferred_probe_pending_list); |
| 104 | } | 104 | } |
| 105 | mutex_unlock(&deferred_probe_mutex); | 105 | mutex_unlock(&deferred_probe_mutex); |
| 106 | } | 106 | } |
diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 23416e443765..a4ed30bd9a41 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c | |||
| @@ -116,8 +116,8 @@ const char *max8997_extcon_cable[] = { | |||
| 116 | [5] = "Charge-downstream", | 116 | [5] = "Charge-downstream", |
| 117 | [6] = "MHL", | 117 | [6] = "MHL", |
| 118 | [7] = "Dock-desk", | 118 | [7] = "Dock-desk", |
| 119 | [7] = "Dock-card", | 119 | [8] = "Dock-card", |
| 120 | [8] = "JIG", | 120 | [9] = "JIG", |
| 121 | 121 | ||
| 122 | NULL, | 122 | NULL, |
| 123 | }; | 123 | }; |
| @@ -514,6 +514,7 @@ static int __devexit max8997_muic_remove(struct platform_device *pdev) | |||
| 514 | 514 | ||
| 515 | extcon_dev_unregister(info->edev); | 515 | extcon_dev_unregister(info->edev); |
| 516 | 516 | ||
| 517 | kfree(info->edev); | ||
| 517 | kfree(info); | 518 | kfree(info); |
| 518 | 519 | ||
| 519 | return 0; | 520 | return 0; |
diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index f598a700ec15..159aeb07b3ba 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c | |||
| @@ -762,7 +762,7 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) | |||
| 762 | #if defined(CONFIG_ANDROID) | 762 | #if defined(CONFIG_ANDROID) |
| 763 | if (switch_class) | 763 | if (switch_class) |
| 764 | ret = class_compat_create_link(switch_class, edev->dev, | 764 | ret = class_compat_create_link(switch_class, edev->dev, |
| 765 | dev); | 765 | NULL); |
| 766 | #endif /* CONFIG_ANDROID */ | 766 | #endif /* CONFIG_ANDROID */ |
| 767 | 767 | ||
| 768 | spin_lock_init(&edev->lock); | 768 | spin_lock_init(&edev->lock); |
diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c index fe7a07b47336..8a0dcc11c7c7 100644 --- a/drivers/extcon/extcon_gpio.c +++ b/drivers/extcon/extcon_gpio.c | |||
| @@ -125,6 +125,7 @@ static int __devinit gpio_extcon_probe(struct platform_device *pdev) | |||
| 125 | if (ret < 0) | 125 | if (ret < 0) |
| 126 | goto err_request_irq; | 126 | goto err_request_irq; |
| 127 | 127 | ||
| 128 | platform_set_drvdata(pdev, extcon_data); | ||
| 128 | /* Perform initial detection */ | 129 | /* Perform initial detection */ |
| 129 | gpio_extcon_work(&extcon_data->work.work); | 130 | gpio_extcon_work(&extcon_data->work.work); |
| 130 | 131 | ||
| @@ -146,6 +147,7 @@ static int __devexit gpio_extcon_remove(struct platform_device *pdev) | |||
| 146 | struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev); | 147 | struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev); |
| 147 | 148 | ||
| 148 | cancel_delayed_work_sync(&extcon_data->work); | 149 | cancel_delayed_work_sync(&extcon_data->work); |
| 150 | free_irq(extcon_data->irq, extcon_data); | ||
| 149 | gpio_free(extcon_data->gpio); | 151 | gpio_free(extcon_data->gpio); |
| 150 | extcon_dev_unregister(&extcon_data->edev); | 152 | extcon_dev_unregister(&extcon_data->edev); |
| 151 | devm_kfree(&pdev->dev, extcon_data); | 153 | devm_kfree(&pdev->dev, extcon_data); |
diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index ae36d7e1e913..551e316e4454 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c | |||
| @@ -304,32 +304,17 @@ static void find_next_position(struct mtdoops_context *cxt) | |||
| 304 | } | 304 | } |
| 305 | 305 | ||
| 306 | static void mtdoops_do_dump(struct kmsg_dumper *dumper, | 306 | static void mtdoops_do_dump(struct kmsg_dumper *dumper, |
| 307 | enum kmsg_dump_reason reason, const char *s1, unsigned long l1, | 307 | enum kmsg_dump_reason reason) |
| 308 | const char *s2, unsigned long l2) | ||
| 309 | { | 308 | { |
| 310 | struct mtdoops_context *cxt = container_of(dumper, | 309 | struct mtdoops_context *cxt = container_of(dumper, |
| 311 | struct mtdoops_context, dump); | 310 | struct mtdoops_context, dump); |
| 312 | unsigned long s1_start, s2_start; | ||
| 313 | unsigned long l1_cpy, l2_cpy; | ||
| 314 | char *dst; | ||
| 315 | |||
| 316 | if (reason != KMSG_DUMP_OOPS && | ||
| 317 | reason != KMSG_DUMP_PANIC) | ||
| 318 | return; | ||
| 319 | 311 | ||
| 320 | /* Only dump oopses if dump_oops is set */ | 312 | /* Only dump oopses if dump_oops is set */ |
| 321 | if (reason == KMSG_DUMP_OOPS && !dump_oops) | 313 | if (reason == KMSG_DUMP_OOPS && !dump_oops) |
| 322 | return; | 314 | return; |
| 323 | 315 | ||
| 324 | dst = cxt->oops_buf + MTDOOPS_HEADER_SIZE; /* Skip the header */ | 316 | kmsg_dump_get_buffer(dumper, true, cxt->oops_buf + MTDOOPS_HEADER_SIZE, |
| 325 | l2_cpy = min(l2, record_size - MTDOOPS_HEADER_SIZE); | 317 | record_size - MTDOOPS_HEADER_SIZE, NULL); |
| 326 | l1_cpy = min(l1, record_size - MTDOOPS_HEADER_SIZE - l2_cpy); | ||
| 327 | |||
| 328 | s2_start = l2 - l2_cpy; | ||
| 329 | s1_start = l1 - l1_cpy; | ||
| 330 | |||
| 331 | memcpy(dst, s1 + s1_start, l1_cpy); | ||
| 332 | memcpy(dst + l1_cpy, s2 + s2_start, l2_cpy); | ||
| 333 | 318 | ||
| 334 | /* Panics must be written immediately */ | 319 | /* Panics must be written immediately */ |
| 335 | if (reason != KMSG_DUMP_OOPS) | 320 | if (reason != KMSG_DUMP_OOPS) |
| @@ -375,6 +360,7 @@ static void mtdoops_notify_add(struct mtd_info *mtd) | |||
| 375 | return; | 360 | return; |
| 376 | } | 361 | } |
| 377 | 362 | ||
| 363 | cxt->dump.max_reason = KMSG_DUMP_OOPS; | ||
| 378 | cxt->dump.dump = mtdoops_do_dump; | 364 | cxt->dump.dump = mtdoops_do_dump; |
| 379 | err = kmsg_dump_register(&cxt->dump); | 365 | err = kmsg_dump_register(&cxt->dump); |
| 380 | if (err) { | 366 | if (err) { |
diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 82c585f715e3..03ce7a9b81cc 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c | |||
| @@ -94,20 +94,15 @@ static const char *get_reason_str(enum kmsg_dump_reason reason) | |||
| 94 | * as we can from the end of the buffer. | 94 | * as we can from the end of the buffer. |
| 95 | */ | 95 | */ |
| 96 | static void pstore_dump(struct kmsg_dumper *dumper, | 96 | static void pstore_dump(struct kmsg_dumper *dumper, |
| 97 | enum kmsg_dump_reason reason, | 97 | enum kmsg_dump_reason reason) |
| 98 | const char *s1, unsigned long l1, | ||
| 99 | const char *s2, unsigned long l2) | ||
| 100 | { | 98 | { |
| 101 | unsigned long s1_start, s2_start; | 99 | unsigned long total = 0; |
| 102 | unsigned long l1_cpy, l2_cpy; | ||
| 103 | unsigned long size, total = 0; | ||
| 104 | char *dst; | ||
| 105 | const char *why; | 100 | const char *why; |
| 106 | u64 id; | 101 | u64 id; |
| 107 | int hsize, ret; | ||
| 108 | unsigned int part = 1; | 102 | unsigned int part = 1; |
| 109 | unsigned long flags = 0; | 103 | unsigned long flags = 0; |
| 110 | int is_locked = 0; | 104 | int is_locked = 0; |
| 105 | int ret; | ||
| 111 | 106 | ||
| 112 | why = get_reason_str(reason); | 107 | why = get_reason_str(reason); |
| 113 | 108 | ||
| @@ -119,30 +114,25 @@ static void pstore_dump(struct kmsg_dumper *dumper, | |||
| 119 | spin_lock_irqsave(&psinfo->buf_lock, flags); | 114 | spin_lock_irqsave(&psinfo->buf_lock, flags); |
| 120 | oopscount++; | 115 | oopscount++; |
| 121 | while (total < kmsg_bytes) { | 116 | while (total < kmsg_bytes) { |
| 117 | char *dst; | ||
| 118 | unsigned long size; | ||
| 119 | int hsize; | ||
| 120 | size_t len; | ||
| 121 | |||
| 122 | dst = psinfo->buf; | 122 | dst = psinfo->buf; |
| 123 | hsize = sprintf(dst, "%s#%d Part%d\n", why, oopscount, part); | 123 | hsize = sprintf(dst, "%s#%d Part%d\n", why, oopscount, part); |
| 124 | size = psinfo->bufsize - hsize; | 124 | size = psinfo->bufsize - hsize; |
| 125 | dst += hsize; | 125 | dst += hsize; |
| 126 | 126 | ||
| 127 | l2_cpy = min(l2, size); | 127 | if (!kmsg_dump_get_buffer(dumper, true, dst, size, &len)) |
| 128 | l1_cpy = min(l1, size - l2_cpy); | ||
| 129 | |||
| 130 | if (l1_cpy + l2_cpy == 0) | ||
| 131 | break; | 128 | break; |
| 132 | 129 | ||
| 133 | s2_start = l2 - l2_cpy; | ||
| 134 | s1_start = l1 - l1_cpy; | ||
| 135 | |||
| 136 | memcpy(dst, s1 + s1_start, l1_cpy); | ||
| 137 | memcpy(dst + l1_cpy, s2 + s2_start, l2_cpy); | ||
| 138 | |||
| 139 | ret = psinfo->write(PSTORE_TYPE_DMESG, reason, &id, part, | 130 | ret = psinfo->write(PSTORE_TYPE_DMESG, reason, &id, part, |
| 140 | hsize + l1_cpy + l2_cpy, psinfo); | 131 | hsize + len, psinfo); |
| 141 | if (ret == 0 && reason == KMSG_DUMP_OOPS && pstore_is_mounted()) | 132 | if (ret == 0 && reason == KMSG_DUMP_OOPS && pstore_is_mounted()) |
| 142 | pstore_new_entry = 1; | 133 | pstore_new_entry = 1; |
| 143 | l1 -= l1_cpy; | 134 | |
| 144 | l2 -= l2_cpy; | 135 | total += hsize + len; |
| 145 | total += l1_cpy + l2_cpy; | ||
| 146 | part++; | 136 | part++; |
| 147 | } | 137 | } |
| 148 | if (in_nmi()) { | 138 | if (in_nmi()) { |
diff --git a/include/linux/kmsg_dump.h b/include/linux/kmsg_dump.h index 35f7237ec972..d6bd50110ec2 100644 --- a/include/linux/kmsg_dump.h +++ b/include/linux/kmsg_dump.h | |||
| @@ -21,6 +21,7 @@ | |||
| 21 | * is passed to the kernel. | 21 | * is passed to the kernel. |
| 22 | */ | 22 | */ |
| 23 | enum kmsg_dump_reason { | 23 | enum kmsg_dump_reason { |
| 24 | KMSG_DUMP_UNDEF, | ||
| 24 | KMSG_DUMP_PANIC, | 25 | KMSG_DUMP_PANIC, |
| 25 | KMSG_DUMP_OOPS, | 26 | KMSG_DUMP_OOPS, |
| 26 | KMSG_DUMP_EMERG, | 27 | KMSG_DUMP_EMERG, |
| @@ -31,23 +32,37 @@ enum kmsg_dump_reason { | |||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * struct kmsg_dumper - kernel crash message dumper structure | 34 | * struct kmsg_dumper - kernel crash message dumper structure |
| 34 | * @dump: The callback which gets called on crashes. The buffer is passed | ||
| 35 | * as two sections, where s1 (length l1) contains the older | ||
| 36 | * messages and s2 (length l2) contains the newer. | ||
| 37 | * @list: Entry in the dumper list (private) | 35 | * @list: Entry in the dumper list (private) |
| 36 | * @dump: Call into dumping code which will retrieve the data with | ||
| 37 | * through the record iterator | ||
| 38 | * @max_reason: filter for highest reason number that should be dumped | ||
| 38 | * @registered: Flag that specifies if this is already registered | 39 | * @registered: Flag that specifies if this is already registered |
| 39 | */ | 40 | */ |
| 40 | struct kmsg_dumper { | 41 | struct kmsg_dumper { |
| 41 | void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason, | ||
| 42 | const char *s1, unsigned long l1, | ||
| 43 | const char *s2, unsigned long l2); | ||
| 44 | struct list_head list; | 42 | struct list_head list; |
| 45 | int registered; | 43 | void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason); |
| 44 | enum kmsg_dump_reason max_reason; | ||
| 45 | bool active; | ||
| 46 | bool registered; | ||
| 47 | |||
| 48 | /* private state of the kmsg iterator */ | ||
| 49 | u32 cur_idx; | ||
| 50 | u32 next_idx; | ||
| 51 | u64 cur_seq; | ||
| 52 | u64 next_seq; | ||
| 46 | }; | 53 | }; |
| 47 | 54 | ||
| 48 | #ifdef CONFIG_PRINTK | 55 | #ifdef CONFIG_PRINTK |
| 49 | void kmsg_dump(enum kmsg_dump_reason reason); | 56 | void kmsg_dump(enum kmsg_dump_reason reason); |
| 50 | 57 | ||
| 58 | bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog, | ||
| 59 | char *line, size_t size, size_t *len); | ||
| 60 | |||
| 61 | bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog, | ||
| 62 | char *buf, size_t size, size_t *len); | ||
| 63 | |||
| 64 | void kmsg_dump_rewind(struct kmsg_dumper *dumper); | ||
| 65 | |||
| 51 | int kmsg_dump_register(struct kmsg_dumper *dumper); | 66 | int kmsg_dump_register(struct kmsg_dumper *dumper); |
| 52 | 67 | ||
| 53 | int kmsg_dump_unregister(struct kmsg_dumper *dumper); | 68 | int kmsg_dump_unregister(struct kmsg_dumper *dumper); |
| @@ -56,6 +71,22 @@ static inline void kmsg_dump(enum kmsg_dump_reason reason) | |||
| 56 | { | 71 | { |
| 57 | } | 72 | } |
| 58 | 73 | ||
| 74 | static inline bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog, | ||
| 75 | const char *line, size_t size, size_t *len) | ||
| 76 | { | ||
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | static inline bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog, | ||
| 81 | char *buf, size_t size, size_t *len) | ||
| 82 | { | ||
| 83 | return false; | ||
| 84 | } | ||
| 85 | |||
| 86 | static inline void kmsg_dump_rewind(struct kmsg_dumper *dumper) | ||
| 87 | { | ||
| 88 | } | ||
| 89 | |||
| 59 | static inline int kmsg_dump_register(struct kmsg_dumper *dumper) | 90 | static inline int kmsg_dump_register(struct kmsg_dumper *dumper) |
| 60 | { | 91 | { |
| 61 | return -EINVAL; | 92 | return -EINVAL; |
diff --git a/kernel/printk.c b/kernel/printk.c index 32462d2b364a..a2276b916769 100644 --- a/kernel/printk.c +++ b/kernel/printk.c | |||
| @@ -227,10 +227,10 @@ static u32 clear_idx; | |||
| 227 | #define LOG_LINE_MAX 1024 | 227 | #define LOG_LINE_MAX 1024 |
| 228 | 228 | ||
| 229 | /* record buffer */ | 229 | /* record buffer */ |
| 230 | #if !defined(CONFIG_64BIT) || defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) | 230 | #if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) |
| 231 | #define LOG_ALIGN 4 | 231 | #define LOG_ALIGN 4 |
| 232 | #else | 232 | #else |
| 233 | #define LOG_ALIGN 8 | 233 | #define LOG_ALIGN __alignof__(struct log) |
| 234 | #endif | 234 | #endif |
| 235 | #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) | 235 | #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) |
| 236 | static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN); | 236 | static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN); |
| @@ -414,7 +414,9 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf, | |||
| 414 | if (!user) | 414 | if (!user) |
| 415 | return -EBADF; | 415 | return -EBADF; |
| 416 | 416 | ||
| 417 | mutex_lock(&user->lock); | 417 | ret = mutex_lock_interruptible(&user->lock); |
| 418 | if (ret) | ||
| 419 | return ret; | ||
| 418 | raw_spin_lock(&logbuf_lock); | 420 | raw_spin_lock(&logbuf_lock); |
| 419 | while (user->seq == log_next_seq) { | 421 | while (user->seq == log_next_seq) { |
| 420 | if (file->f_flags & O_NONBLOCK) { | 422 | if (file->f_flags & O_NONBLOCK) { |
| @@ -878,7 +880,9 @@ static int syslog_print(char __user *buf, int size) | |||
| 878 | syslog_seq++; | 880 | syslog_seq++; |
| 879 | raw_spin_unlock_irq(&logbuf_lock); | 881 | raw_spin_unlock_irq(&logbuf_lock); |
| 880 | 882 | ||
| 881 | if (len > 0 && copy_to_user(buf, text, len)) | 883 | if (len > size) |
| 884 | len = -EINVAL; | ||
| 885 | else if (len > 0 && copy_to_user(buf, text, len)) | ||
| 882 | len = -EFAULT; | 886 | len = -EFAULT; |
| 883 | 887 | ||
| 884 | kfree(text); | 888 | kfree(text); |
| @@ -909,7 +913,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear) | |||
| 909 | /* | 913 | /* |
| 910 | * Find first record that fits, including all following records, | 914 | * Find first record that fits, including all following records, |
| 911 | * into the user-provided buffer for this dump. | 915 | * into the user-provided buffer for this dump. |
| 912 | */ | 916 | */ |
| 913 | seq = clear_seq; | 917 | seq = clear_seq; |
| 914 | idx = clear_idx; | 918 | idx = clear_idx; |
| 915 | while (seq < log_next_seq) { | 919 | while (seq < log_next_seq) { |
| @@ -919,6 +923,8 @@ static int syslog_print_all(char __user *buf, int size, bool clear) | |||
| 919 | idx = log_next(idx); | 923 | idx = log_next(idx); |
| 920 | seq++; | 924 | seq++; |
| 921 | } | 925 | } |
| 926 | |||
| 927 | /* move first record forward until length fits into the buffer */ | ||
| 922 | seq = clear_seq; | 928 | seq = clear_seq; |
| 923 | idx = clear_idx; | 929 | idx = clear_idx; |
| 924 | while (len > size && seq < log_next_seq) { | 930 | while (len > size && seq < log_next_seq) { |
| @@ -929,7 +935,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear) | |||
| 929 | seq++; | 935 | seq++; |
| 930 | } | 936 | } |
| 931 | 937 | ||
| 932 | /* last message in this dump */ | 938 | /* last message fitting into this dump */ |
| 933 | next_seq = log_next_seq; | 939 | next_seq = log_next_seq; |
| 934 | 940 | ||
| 935 | len = 0; | 941 | len = 0; |
| @@ -974,6 +980,7 @@ int do_syslog(int type, char __user *buf, int len, bool from_file) | |||
| 974 | { | 980 | { |
| 975 | bool clear = false; | 981 | bool clear = false; |
| 976 | static int saved_console_loglevel = -1; | 982 | static int saved_console_loglevel = -1; |
| 983 | static DEFINE_MUTEX(syslog_mutex); | ||
| 977 | int error; | 984 | int error; |
| 978 | 985 | ||
| 979 | error = check_syslog_permissions(type, from_file); | 986 | error = check_syslog_permissions(type, from_file); |
| @@ -1000,11 +1007,17 @@ int do_syslog(int type, char __user *buf, int len, bool from_file) | |||
| 1000 | error = -EFAULT; | 1007 | error = -EFAULT; |
| 1001 | goto out; | 1008 | goto out; |
| 1002 | } | 1009 | } |
| 1010 | error = mutex_lock_interruptible(&syslog_mutex); | ||
| 1011 | if (error) | ||
| 1012 | goto out; | ||
| 1003 | error = wait_event_interruptible(log_wait, | 1013 | error = wait_event_interruptible(log_wait, |
| 1004 | syslog_seq != log_next_seq); | 1014 | syslog_seq != log_next_seq); |
| 1005 | if (error) | 1015 | if (error) { |
| 1016 | mutex_unlock(&syslog_mutex); | ||
| 1006 | goto out; | 1017 | goto out; |
| 1018 | } | ||
| 1007 | error = syslog_print(buf, len); | 1019 | error = syslog_print(buf, len); |
| 1020 | mutex_unlock(&syslog_mutex); | ||
| 1008 | break; | 1021 | break; |
| 1009 | /* Read/clear last kernel messages */ | 1022 | /* Read/clear last kernel messages */ |
| 1010 | case SYSLOG_ACTION_READ_CLEAR: | 1023 | case SYSLOG_ACTION_READ_CLEAR: |
| @@ -2300,48 +2313,210 @@ module_param_named(always_kmsg_dump, always_kmsg_dump, bool, S_IRUGO | S_IWUSR); | |||
| 2300 | * kmsg_dump - dump kernel log to kernel message dumpers. | 2313 | * kmsg_dump - dump kernel log to kernel message dumpers. |
| 2301 | * @reason: the reason (oops, panic etc) for dumping | 2314 | * @reason: the reason (oops, panic etc) for dumping |
| 2302 | * | 2315 | * |
| 2303 | * Iterate through each of the dump devices and call the oops/panic | 2316 | * Call each of the registered dumper's dump() callback, which can |
| 2304 | * callbacks with the log buffer. | 2317 | * retrieve the kmsg records with kmsg_dump_get_line() or |
| 2318 | * kmsg_dump_get_buffer(). | ||
| 2305 | */ | 2319 | */ |
| 2306 | void kmsg_dump(enum kmsg_dump_reason reason) | 2320 | void kmsg_dump(enum kmsg_dump_reason reason) |
| 2307 | { | 2321 | { |
| 2308 | u64 idx; | ||
| 2309 | struct kmsg_dumper *dumper; | 2322 | struct kmsg_dumper *dumper; |
| 2310 | const char *s1, *s2; | ||
| 2311 | unsigned long l1, l2; | ||
| 2312 | unsigned long flags; | 2323 | unsigned long flags; |
| 2313 | 2324 | ||
| 2314 | if ((reason > KMSG_DUMP_OOPS) && !always_kmsg_dump) | 2325 | if ((reason > KMSG_DUMP_OOPS) && !always_kmsg_dump) |
| 2315 | return; | 2326 | return; |
| 2316 | 2327 | ||
| 2317 | /* Theoretically, the log could move on after we do this, but | 2328 | rcu_read_lock(); |
| 2318 | there's not a lot we can do about that. The new messages | 2329 | list_for_each_entry_rcu(dumper, &dump_list, list) { |
| 2319 | will overwrite the start of what we dump. */ | 2330 | if (dumper->max_reason && reason > dumper->max_reason) |
| 2331 | continue; | ||
| 2332 | |||
| 2333 | /* initialize iterator with data about the stored records */ | ||
| 2334 | dumper->active = true; | ||
| 2335 | |||
| 2336 | raw_spin_lock_irqsave(&logbuf_lock, flags); | ||
| 2337 | dumper->cur_seq = clear_seq; | ||
| 2338 | dumper->cur_idx = clear_idx; | ||
| 2339 | dumper->next_seq = log_next_seq; | ||
| 2340 | dumper->next_idx = log_next_idx; | ||
| 2341 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); | ||
| 2342 | |||
| 2343 | /* invoke dumper which will iterate over records */ | ||
| 2344 | dumper->dump(dumper, reason); | ||
| 2345 | |||
| 2346 | /* reset iterator */ | ||
| 2347 | dumper->active = false; | ||
| 2348 | } | ||
| 2349 | rcu_read_unlock(); | ||
| 2350 | } | ||
| 2351 | |||
| 2352 | /** | ||
| 2353 | * kmsg_dump_get_line - retrieve one kmsg log line | ||
| 2354 | * @dumper: registered kmsg dumper | ||
| 2355 | * @syslog: include the "<4>" prefixes | ||
| 2356 | * @line: buffer to copy the line to | ||
| 2357 | * @size: maximum size of the buffer | ||
| 2358 | * @len: length of line placed into buffer | ||
| 2359 | * | ||
| 2360 | * Start at the beginning of the kmsg buffer, with the oldest kmsg | ||
| 2361 | * record, and copy one record into the provided buffer. | ||
| 2362 | * | ||
| 2363 | * Consecutive calls will return the next available record moving | ||
| 2364 | * towards the end of the buffer with the youngest messages. | ||
| 2365 | * | ||
| 2366 | * A return value of FALSE indicates that there are no more records to | ||
| 2367 | * read. | ||
| 2368 | */ | ||
| 2369 | bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog, | ||
| 2370 | char *line, size_t size, size_t *len) | ||
| 2371 | { | ||
| 2372 | unsigned long flags; | ||
| 2373 | struct log *msg; | ||
| 2374 | size_t l = 0; | ||
| 2375 | bool ret = false; | ||
| 2376 | |||
| 2377 | if (!dumper->active) | ||
| 2378 | goto out; | ||
| 2320 | 2379 | ||
| 2321 | raw_spin_lock_irqsave(&logbuf_lock, flags); | 2380 | raw_spin_lock_irqsave(&logbuf_lock, flags); |
| 2322 | if (syslog_seq < log_first_seq) | 2381 | if (dumper->cur_seq < log_first_seq) { |
| 2323 | idx = syslog_idx; | 2382 | /* messages are gone, move to first available one */ |
| 2324 | else | 2383 | dumper->cur_seq = log_first_seq; |
| 2325 | idx = log_first_idx; | 2384 | dumper->cur_idx = log_first_idx; |
| 2385 | } | ||
| 2386 | |||
| 2387 | /* last entry */ | ||
| 2388 | if (dumper->cur_seq >= log_next_seq) { | ||
| 2389 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); | ||
| 2390 | goto out; | ||
| 2391 | } | ||
| 2326 | 2392 | ||
| 2327 | if (idx > log_next_idx) { | 2393 | msg = log_from_idx(dumper->cur_idx); |
| 2328 | s1 = log_buf; | 2394 | l = msg_print_text(msg, syslog, |
| 2329 | l1 = log_next_idx; | 2395 | line, size); |
| 2330 | 2396 | ||
| 2331 | s2 = log_buf + idx; | 2397 | dumper->cur_idx = log_next(dumper->cur_idx); |
| 2332 | l2 = log_buf_len - idx; | 2398 | dumper->cur_seq++; |
| 2333 | } else { | 2399 | ret = true; |
| 2334 | s1 = ""; | 2400 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); |
| 2335 | l1 = 0; | 2401 | out: |
| 2402 | if (len) | ||
| 2403 | *len = l; | ||
| 2404 | return ret; | ||
| 2405 | } | ||
| 2406 | EXPORT_SYMBOL_GPL(kmsg_dump_get_line); | ||
| 2407 | |||
| 2408 | /** | ||
| 2409 | * kmsg_dump_get_buffer - copy kmsg log lines | ||
| 2410 | * @dumper: registered kmsg dumper | ||
| 2411 | * @syslog: include the "<4>" prefixes | ||
| 2412 | * @line: buffer to copy the line to | ||
| 2413 | * @size: maximum size of the buffer | ||
| 2414 | * @len: length of line placed into buffer | ||
| 2415 | * | ||
| 2416 | * Start at the end of the kmsg buffer and fill the provided buffer | ||
| 2417 | * with as many of the the *youngest* kmsg records that fit into it. | ||
| 2418 | * If the buffer is large enough, all available kmsg records will be | ||
| 2419 | * copied with a single call. | ||
| 2420 | * | ||
| 2421 | * Consecutive calls will fill the buffer with the next block of | ||
| 2422 | * available older records, not including the earlier retrieved ones. | ||
| 2423 | * | ||
| 2424 | * A return value of FALSE indicates that there are no more records to | ||
| 2425 | * read. | ||
| 2426 | */ | ||
| 2427 | bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog, | ||
| 2428 | char *buf, size_t size, size_t *len) | ||
| 2429 | { | ||
| 2430 | unsigned long flags; | ||
| 2431 | u64 seq; | ||
| 2432 | u32 idx; | ||
| 2433 | u64 next_seq; | ||
| 2434 | u32 next_idx; | ||
| 2435 | size_t l = 0; | ||
| 2436 | bool ret = false; | ||
| 2437 | |||
| 2438 | if (!dumper->active) | ||
| 2439 | goto out; | ||
| 2440 | |||
| 2441 | raw_spin_lock_irqsave(&logbuf_lock, flags); | ||
| 2442 | if (dumper->cur_seq < log_first_seq) { | ||
| 2443 | /* messages are gone, move to first available one */ | ||
| 2444 | dumper->cur_seq = log_first_seq; | ||
| 2445 | dumper->cur_idx = log_first_idx; | ||
| 2446 | } | ||
| 2447 | |||
| 2448 | /* last entry */ | ||
| 2449 | if (dumper->cur_seq >= dumper->next_seq) { | ||
| 2450 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); | ||
| 2451 | goto out; | ||
| 2452 | } | ||
| 2453 | |||
| 2454 | /* calculate length of entire buffer */ | ||
| 2455 | seq = dumper->cur_seq; | ||
| 2456 | idx = dumper->cur_idx; | ||
| 2457 | while (seq < dumper->next_seq) { | ||
| 2458 | struct log *msg = log_from_idx(idx); | ||
| 2459 | |||
| 2460 | l += msg_print_text(msg, true, NULL, 0); | ||
| 2461 | idx = log_next(idx); | ||
| 2462 | seq++; | ||
| 2463 | } | ||
| 2336 | 2464 | ||
| 2337 | s2 = log_buf + idx; | 2465 | /* move first record forward until length fits into the buffer */ |
| 2338 | l2 = log_next_idx - idx; | 2466 | seq = dumper->cur_seq; |
| 2467 | idx = dumper->cur_idx; | ||
| 2468 | while (l > size && seq < dumper->next_seq) { | ||
| 2469 | struct log *msg = log_from_idx(idx); | ||
| 2470 | |||
| 2471 | l -= msg_print_text(msg, true, NULL, 0); | ||
| 2472 | idx = log_next(idx); | ||
| 2473 | seq++; | ||
| 2474 | } | ||
| 2475 | |||
| 2476 | /* last message in next interation */ | ||
| 2477 | next_seq = seq; | ||
| 2478 | next_idx = idx; | ||
| 2479 | |||
| 2480 | l = 0; | ||
| 2481 | while (seq < dumper->next_seq) { | ||
| 2482 | struct log *msg = log_from_idx(idx); | ||
| 2483 | |||
| 2484 | l += msg_print_text(msg, syslog, | ||
| 2485 | buf + l, size - l); | ||
| 2486 | |||
| 2487 | idx = log_next(idx); | ||
| 2488 | seq++; | ||
| 2339 | } | 2489 | } |
| 2490 | |||
| 2491 | dumper->next_seq = next_seq; | ||
| 2492 | dumper->next_idx = next_idx; | ||
| 2493 | ret = true; | ||
| 2340 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); | 2494 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); |
| 2495 | out: | ||
| 2496 | if (len) | ||
| 2497 | *len = l; | ||
| 2498 | return ret; | ||
| 2499 | } | ||
| 2500 | EXPORT_SYMBOL_GPL(kmsg_dump_get_buffer); | ||
| 2341 | 2501 | ||
| 2342 | rcu_read_lock(); | 2502 | /** |
| 2343 | list_for_each_entry_rcu(dumper, &dump_list, list) | 2503 | * kmsg_dump_rewind - reset the interator |
| 2344 | dumper->dump(dumper, reason, s1, l1, s2, l2); | 2504 | * @dumper: registered kmsg dumper |
| 2345 | rcu_read_unlock(); | 2505 | * |
| 2506 | * Reset the dumper's iterator so that kmsg_dump_get_line() and | ||
| 2507 | * kmsg_dump_get_buffer() can be called again and used multiple | ||
| 2508 | * times within the same dumper.dump() callback. | ||
| 2509 | */ | ||
| 2510 | void kmsg_dump_rewind(struct kmsg_dumper *dumper) | ||
| 2511 | { | ||
| 2512 | unsigned long flags; | ||
| 2513 | |||
| 2514 | raw_spin_lock_irqsave(&logbuf_lock, flags); | ||
| 2515 | dumper->cur_seq = clear_seq; | ||
| 2516 | dumper->cur_idx = clear_idx; | ||
| 2517 | dumper->next_seq = log_next_seq; | ||
| 2518 | dumper->next_idx = log_next_idx; | ||
| 2519 | raw_spin_unlock_irqrestore(&logbuf_lock, flags); | ||
| 2346 | } | 2520 | } |
| 2521 | EXPORT_SYMBOL_GPL(kmsg_dump_rewind); | ||
| 2347 | #endif | 2522 | #endif |
diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 146fd6147e84..d9834b362943 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c | |||
| @@ -701,14 +701,18 @@ int main(void) | |||
| 701 | pfd.fd = fd; | 701 | pfd.fd = fd; |
| 702 | 702 | ||
| 703 | while (1) { | 703 | while (1) { |
| 704 | struct sockaddr *addr_p = (struct sockaddr *) &addr; | ||
| 705 | socklen_t addr_l = sizeof(addr); | ||
| 704 | pfd.events = POLLIN; | 706 | pfd.events = POLLIN; |
| 705 | pfd.revents = 0; | 707 | pfd.revents = 0; |
| 706 | poll(&pfd, 1, -1); | 708 | poll(&pfd, 1, -1); |
| 707 | 709 | ||
| 708 | len = recv(fd, kvp_recv_buffer, sizeof(kvp_recv_buffer), 0); | 710 | len = recvfrom(fd, kvp_recv_buffer, sizeof(kvp_recv_buffer), 0, |
| 711 | addr_p, &addr_l); | ||
| 709 | 712 | ||
| 710 | if (len < 0) { | 713 | if (len < 0 || addr.nl_pid) { |
| 711 | syslog(LOG_ERR, "recv failed; error:%d", len); | 714 | syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s", |
| 715 | addr.nl_pid, errno, strerror(errno)); | ||
| 712 | close(fd); | 716 | close(fd); |
| 713 | return -1; | 717 | return -1; |
| 714 | } | 718 | } |
