diff options
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/apei/ghes.c | 184 |
1 files changed, 177 insertions, 7 deletions
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index d1a40218e17e..931410d31a96 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c | |||
@@ -60,6 +60,21 @@ | |||
60 | 60 | ||
61 | #define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3 | 61 | #define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3 |
62 | 62 | ||
63 | /* This is just an estimation for memory pool allocation */ | ||
64 | #define GHES_ESTATUS_CACHE_AVG_SIZE 512 | ||
65 | |||
66 | #define GHES_ESTATUS_CACHES_SIZE 4 | ||
67 | |||
68 | #define GHES_ESTATUS_IN_CACHE_MAX_NSEC (10 * NSEC_PER_SEC) | ||
69 | /* Prevent too many caches are allocated because of RCU */ | ||
70 | #define GHES_ESTATUS_CACHE_ALLOCED_MAX (GHES_ESTATUS_CACHES_SIZE * 3 / 2) | ||
71 | |||
72 | #define GHES_ESTATUS_CACHE_LEN(estatus_len) \ | ||
73 | (sizeof(struct ghes_estatus_cache) + (estatus_len)) | ||
74 | #define GHES_ESTATUS_FROM_CACHE(estatus_cache) \ | ||
75 | ((struct acpi_hest_generic_status *) \ | ||
76 | ((struct ghes_estatus_cache *)(estatus_cache) + 1)) | ||
77 | |||
63 | #define GHES_ESTATUS_NODE_LEN(estatus_len) \ | 78 | #define GHES_ESTATUS_NODE_LEN(estatus_len) \ |
64 | (sizeof(struct ghes_estatus_node) + (estatus_len)) | 79 | (sizeof(struct ghes_estatus_node) + (estatus_len)) |
65 | #define GHES_ESTATUS_FROM_NODE(estatus_node) \ | 80 | #define GHES_ESTATUS_FROM_NODE(estatus_node) \ |
@@ -94,6 +109,14 @@ struct ghes_estatus_node { | |||
94 | struct acpi_hest_generic *generic; | 109 | struct acpi_hest_generic *generic; |
95 | }; | 110 | }; |
96 | 111 | ||
112 | struct ghes_estatus_cache { | ||
113 | u32 estatus_len; | ||
114 | atomic_t count; | ||
115 | struct acpi_hest_generic *generic; | ||
116 | unsigned long long time_in; | ||
117 | struct rcu_head rcu; | ||
118 | }; | ||
119 | |||
97 | int ghes_disable; | 120 | int ghes_disable; |
98 | module_param_named(disable, ghes_disable, bool, 0); | 121 | module_param_named(disable, ghes_disable, bool, 0); |
99 | 122 | ||
@@ -154,6 +177,9 @@ static unsigned long ghes_estatus_pool_size_request; | |||
154 | static struct llist_head ghes_estatus_llist; | 177 | static struct llist_head ghes_estatus_llist; |
155 | static struct irq_work ghes_proc_irq_work; | 178 | static struct irq_work ghes_proc_irq_work; |
156 | 179 | ||
180 | struct ghes_estatus_cache *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE]; | ||
181 | static atomic_t ghes_estatus_cache_alloced; | ||
182 | |||
157 | static int ghes_ioremap_init(void) | 183 | static int ghes_ioremap_init(void) |
158 | { | 184 | { |
159 | ghes_ioremap_area = __get_vm_area(PAGE_SIZE * GHES_IOREMAP_PAGES, | 185 | ghes_ioremap_area = __get_vm_area(PAGE_SIZE * GHES_IOREMAP_PAGES, |
@@ -458,9 +484,9 @@ static void __ghes_print_estatus(const char *pfx, | |||
458 | apei_estatus_print(pfx, estatus); | 484 | apei_estatus_print(pfx, estatus); |
459 | } | 485 | } |
460 | 486 | ||
461 | static void ghes_print_estatus(const char *pfx, | 487 | static int ghes_print_estatus(const char *pfx, |
462 | const struct acpi_hest_generic *generic, | 488 | const struct acpi_hest_generic *generic, |
463 | const struct acpi_hest_generic_status *estatus) | 489 | const struct acpi_hest_generic_status *estatus) |
464 | { | 490 | { |
465 | /* Not more than 2 messages every 5 seconds */ | 491 | /* Not more than 2 messages every 5 seconds */ |
466 | static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2); | 492 | static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2); |
@@ -471,8 +497,137 @@ static void ghes_print_estatus(const char *pfx, | |||
471 | ratelimit = &ratelimit_corrected; | 497 | ratelimit = &ratelimit_corrected; |
472 | else | 498 | else |
473 | ratelimit = &ratelimit_uncorrected; | 499 | ratelimit = &ratelimit_uncorrected; |
474 | if (__ratelimit(ratelimit)) | 500 | if (__ratelimit(ratelimit)) { |
475 | __ghes_print_estatus(pfx, generic, estatus); | 501 | __ghes_print_estatus(pfx, generic, estatus); |
502 | return 1; | ||
503 | } | ||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | /* | ||
508 | * GHES error status reporting throttle, to report more kinds of | ||
509 | * errors, instead of just most frequently occurred errors. | ||
510 | */ | ||
511 | static int ghes_estatus_cached(struct acpi_hest_generic_status *estatus) | ||
512 | { | ||
513 | u32 len; | ||
514 | int i, cached = 0; | ||
515 | unsigned long long now; | ||
516 | struct ghes_estatus_cache *cache; | ||
517 | struct acpi_hest_generic_status *cache_estatus; | ||
518 | |||
519 | len = apei_estatus_len(estatus); | ||
520 | rcu_read_lock(); | ||
521 | for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { | ||
522 | cache = rcu_dereference(ghes_estatus_caches[i]); | ||
523 | if (cache == NULL) | ||
524 | continue; | ||
525 | if (len != cache->estatus_len) | ||
526 | continue; | ||
527 | cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); | ||
528 | if (memcmp(estatus, cache_estatus, len)) | ||
529 | continue; | ||
530 | atomic_inc(&cache->count); | ||
531 | now = sched_clock(); | ||
532 | if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC) | ||
533 | cached = 1; | ||
534 | break; | ||
535 | } | ||
536 | rcu_read_unlock(); | ||
537 | return cached; | ||
538 | } | ||
539 | |||
540 | static struct ghes_estatus_cache *ghes_estatus_cache_alloc( | ||
541 | struct acpi_hest_generic *generic, | ||
542 | struct acpi_hest_generic_status *estatus) | ||
543 | { | ||
544 | int alloced; | ||
545 | u32 len, cache_len; | ||
546 | struct ghes_estatus_cache *cache; | ||
547 | struct acpi_hest_generic_status *cache_estatus; | ||
548 | |||
549 | alloced = atomic_add_return(1, &ghes_estatus_cache_alloced); | ||
550 | if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) { | ||
551 | atomic_dec(&ghes_estatus_cache_alloced); | ||
552 | return NULL; | ||
553 | } | ||
554 | len = apei_estatus_len(estatus); | ||
555 | cache_len = GHES_ESTATUS_CACHE_LEN(len); | ||
556 | cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len); | ||
557 | if (!cache) { | ||
558 | atomic_dec(&ghes_estatus_cache_alloced); | ||
559 | return NULL; | ||
560 | } | ||
561 | cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); | ||
562 | memcpy(cache_estatus, estatus, len); | ||
563 | cache->estatus_len = len; | ||
564 | atomic_set(&cache->count, 0); | ||
565 | cache->generic = generic; | ||
566 | cache->time_in = sched_clock(); | ||
567 | return cache; | ||
568 | } | ||
569 | |||
570 | static void ghes_estatus_cache_free(struct ghes_estatus_cache *cache) | ||
571 | { | ||
572 | u32 len; | ||
573 | |||
574 | len = apei_estatus_len(GHES_ESTATUS_FROM_CACHE(cache)); | ||
575 | len = GHES_ESTATUS_CACHE_LEN(len); | ||
576 | gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len); | ||
577 | atomic_dec(&ghes_estatus_cache_alloced); | ||
578 | } | ||
579 | |||
580 | static void ghes_estatus_cache_rcu_free(struct rcu_head *head) | ||
581 | { | ||
582 | struct ghes_estatus_cache *cache; | ||
583 | |||
584 | cache = container_of(head, struct ghes_estatus_cache, rcu); | ||
585 | ghes_estatus_cache_free(cache); | ||
586 | } | ||
587 | |||
588 | static void ghes_estatus_cache_add( | ||
589 | struct acpi_hest_generic *generic, | ||
590 | struct acpi_hest_generic_status *estatus) | ||
591 | { | ||
592 | int i, slot = -1, count; | ||
593 | unsigned long long now, duration, period, max_period = 0; | ||
594 | struct ghes_estatus_cache *cache, *slot_cache = NULL, *new_cache; | ||
595 | |||
596 | new_cache = ghes_estatus_cache_alloc(generic, estatus); | ||
597 | if (new_cache == NULL) | ||
598 | return; | ||
599 | rcu_read_lock(); | ||
600 | now = sched_clock(); | ||
601 | for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { | ||
602 | cache = rcu_dereference(ghes_estatus_caches[i]); | ||
603 | if (cache == NULL) { | ||
604 | slot = i; | ||
605 | slot_cache = NULL; | ||
606 | break; | ||
607 | } | ||
608 | duration = now - cache->time_in; | ||
609 | if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) { | ||
610 | slot = i; | ||
611 | slot_cache = cache; | ||
612 | break; | ||
613 | } | ||
614 | count = atomic_read(&cache->count); | ||
615 | period = duration / (count + 1); | ||
616 | if (period > max_period) { | ||
617 | max_period = period; | ||
618 | slot = i; | ||
619 | slot_cache = cache; | ||
620 | } | ||
621 | } | ||
622 | /* new_cache must be put into array after its contents are written */ | ||
623 | smp_wmb(); | ||
624 | if (slot != -1 && cmpxchg(ghes_estatus_caches + slot, | ||
625 | slot_cache, new_cache) == slot_cache) { | ||
626 | if (slot_cache) | ||
627 | call_rcu(&slot_cache->rcu, ghes_estatus_cache_rcu_free); | ||
628 | } else | ||
629 | ghes_estatus_cache_free(new_cache); | ||
630 | rcu_read_unlock(); | ||
476 | } | 631 | } |
477 | 632 | ||
478 | static int ghes_proc(struct ghes *ghes) | 633 | static int ghes_proc(struct ghes *ghes) |
@@ -482,9 +637,11 @@ static int ghes_proc(struct ghes *ghes) | |||
482 | rc = ghes_read_estatus(ghes, 0); | 637 | rc = ghes_read_estatus(ghes, 0); |
483 | if (rc) | 638 | if (rc) |
484 | goto out; | 639 | goto out; |
485 | ghes_print_estatus(NULL, ghes->generic, ghes->estatus); | 640 | if (!ghes_estatus_cached(ghes->estatus)) { |
641 | if (ghes_print_estatus(NULL, ghes->generic, ghes->estatus)) | ||
642 | ghes_estatus_cache_add(ghes->generic, ghes->estatus); | ||
643 | } | ||
486 | ghes_do_proc(ghes->estatus); | 644 | ghes_do_proc(ghes->estatus); |
487 | |||
488 | out: | 645 | out: |
489 | ghes_clear_estatus(ghes); | 646 | ghes_clear_estatus(ghes); |
490 | return 0; | 647 | return 0; |
@@ -546,6 +703,7 @@ static void ghes_proc_in_irq(struct irq_work *irq_work) | |||
546 | { | 703 | { |
547 | struct llist_node *llnode, *next, *tail = NULL; | 704 | struct llist_node *llnode, *next, *tail = NULL; |
548 | struct ghes_estatus_node *estatus_node; | 705 | struct ghes_estatus_node *estatus_node; |
706 | struct acpi_hest_generic *generic; | ||
549 | struct acpi_hest_generic_status *estatus; | 707 | struct acpi_hest_generic_status *estatus; |
550 | u32 len, node_len; | 708 | u32 len, node_len; |
551 | 709 | ||
@@ -569,7 +727,11 @@ static void ghes_proc_in_irq(struct irq_work *irq_work) | |||
569 | len = apei_estatus_len(estatus); | 727 | len = apei_estatus_len(estatus); |
570 | node_len = GHES_ESTATUS_NODE_LEN(len); | 728 | node_len = GHES_ESTATUS_NODE_LEN(len); |
571 | ghes_do_proc(estatus); | 729 | ghes_do_proc(estatus); |
572 | ghes_print_estatus(NULL, estatus_node->generic, estatus); | 730 | if (!ghes_estatus_cached(estatus)) { |
731 | generic = estatus_node->generic; | ||
732 | if (ghes_print_estatus(NULL, generic, estatus)) | ||
733 | ghes_estatus_cache_add(generic, estatus); | ||
734 | } | ||
573 | gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, | 735 | gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, |
574 | node_len); | 736 | node_len); |
575 | llnode = next; | 737 | llnode = next; |
@@ -622,6 +784,8 @@ static int ghes_notify_nmi(struct notifier_block *this, | |||
622 | if (!(ghes->flags & GHES_TO_CLEAR)) | 784 | if (!(ghes->flags & GHES_TO_CLEAR)) |
623 | continue; | 785 | continue; |
624 | #ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG | 786 | #ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG |
787 | if (ghes_estatus_cached(ghes->estatus)) | ||
788 | goto next; | ||
625 | /* Save estatus for further processing in IRQ context */ | 789 | /* Save estatus for further processing in IRQ context */ |
626 | len = apei_estatus_len(ghes->estatus); | 790 | len = apei_estatus_len(ghes->estatus); |
627 | node_len = GHES_ESTATUS_NODE_LEN(len); | 791 | node_len = GHES_ESTATUS_NODE_LEN(len); |
@@ -633,6 +797,7 @@ static int ghes_notify_nmi(struct notifier_block *this, | |||
633 | memcpy(estatus, ghes->estatus, len); | 797 | memcpy(estatus, ghes->estatus, len); |
634 | llist_add(&estatus_node->llnode, &ghes_estatus_llist); | 798 | llist_add(&estatus_node->llnode, &ghes_estatus_llist); |
635 | } | 799 | } |
800 | next: | ||
636 | #endif | 801 | #endif |
637 | ghes_clear_estatus(ghes); | 802 | ghes_clear_estatus(ghes); |
638 | } | 803 | } |
@@ -847,6 +1012,11 @@ static int __init ghes_init(void) | |||
847 | if (rc) | 1012 | if (rc) |
848 | goto err_ioremap_exit; | 1013 | goto err_ioremap_exit; |
849 | 1014 | ||
1015 | rc = ghes_estatus_pool_expand(GHES_ESTATUS_CACHE_AVG_SIZE * | ||
1016 | GHES_ESTATUS_CACHE_ALLOCED_MAX); | ||
1017 | if (rc) | ||
1018 | goto err_pool_exit; | ||
1019 | |||
850 | rc = platform_driver_register(&ghes_platform_driver); | 1020 | rc = platform_driver_register(&ghes_platform_driver); |
851 | if (rc) | 1021 | if (rc) |
852 | goto err_pool_exit; | 1022 | goto err_pool_exit; |