Relocate the estatus cache allocation and lookup helpers from ghes.c into
ghes_cper.c. This code move keeps the logic intact while making the cache
implementation available to forthcoming users.

Fix ghes_estatus_cache_add() by freeing new_cache and decrementing
ghes_estatus_cache_alloced. This avoids leaks and counter exhaustion
that can disable estatus deduplication.

Reviewed-by: Jonathan Cameron <[email protected]>
Signed-off-by: Ahmed Tiba <[email protected]>
---
 drivers/acpi/apei/ghes.c      | 138 +---------------------------------------
 drivers/acpi/apei/ghes_cper.c | 143 ++++++++++++++++++++++++++++++++++++++++++
 include/acpi/ghes_cper.h      |   5 ++
 3 files changed, 149 insertions(+), 137 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index bda535224422..e9cb576eefcf 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -113,10 +113,7 @@ static DEFINE_MUTEX(ghes_devs_mutex);
  */
 static DEFINE_SPINLOCK(ghes_notify_lock_irq);
 
-static struct gen_pool *ghes_estatus_pool;
-
-static struct ghes_estatus_cache __rcu 
*ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
-static atomic_t ghes_estatus_cache_alloced;
+struct gen_pool *ghes_estatus_pool;
 
 int ghes_estatus_pool_init(unsigned int num_ghes)
 {
@@ -733,139 +730,6 @@ static int ghes_print_estatus(const char *pfx,
        return 0;
 }
 
-/*
- * GHES error status reporting throttle, to report more kinds of
- * errors, instead of just most frequently occurred errors.
- */
-static int ghes_estatus_cached(struct acpi_hest_generic_status *estatus)
-{
-       u32 len;
-       int i, cached = 0;
-       unsigned long long now;
-       struct ghes_estatus_cache *cache;
-       struct acpi_hest_generic_status *cache_estatus;
-
-       len = cper_estatus_len(estatus);
-       rcu_read_lock();
-       for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
-               cache = rcu_dereference(ghes_estatus_caches[i]);
-               if (cache == NULL)
-                       continue;
-               if (len != cache->estatus_len)
-                       continue;
-               cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
-               if (memcmp(estatus, cache_estatus, len))
-                       continue;
-               atomic_inc(&cache->count);
-               now = sched_clock();
-               if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC)
-                       cached = 1;
-               break;
-       }
-       rcu_read_unlock();
-       return cached;
-}
-
-static struct ghes_estatus_cache *ghes_estatus_cache_alloc(
-       struct acpi_hest_generic *generic,
-       struct acpi_hest_generic_status *estatus)
-{
-       int alloced;
-       u32 len, cache_len;
-       struct ghes_estatus_cache *cache;
-       struct acpi_hest_generic_status *cache_estatus;
-
-       alloced = atomic_add_return(1, &ghes_estatus_cache_alloced);
-       if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) {
-               atomic_dec(&ghes_estatus_cache_alloced);
-               return NULL;
-       }
-       len = cper_estatus_len(estatus);
-       cache_len = GHES_ESTATUS_CACHE_LEN(len);
-       cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len);
-       if (!cache) {
-               atomic_dec(&ghes_estatus_cache_alloced);
-               return NULL;
-       }
-       cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
-       memcpy(cache_estatus, estatus, len);
-       cache->estatus_len = len;
-       atomic_set(&cache->count, 0);
-       cache->generic = generic;
-       cache->time_in = sched_clock();
-       return cache;
-}
-
-static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
-{
-       struct ghes_estatus_cache *cache;
-       u32 len;
-
-       cache = container_of(head, struct ghes_estatus_cache, rcu);
-       len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache));
-       len = GHES_ESTATUS_CACHE_LEN(len);
-       gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len);
-       atomic_dec(&ghes_estatus_cache_alloced);
-}
-
-static void
-ghes_estatus_cache_add(struct acpi_hest_generic *generic,
-                      struct acpi_hest_generic_status *estatus)
-{
-       unsigned long long now, duration, period, max_period = 0;
-       struct ghes_estatus_cache *cache, *new_cache;
-       struct ghes_estatus_cache __rcu *victim;
-       int i, slot = -1, count;
-
-       new_cache = ghes_estatus_cache_alloc(generic, estatus);
-       if (!new_cache)
-               return;
-
-       rcu_read_lock();
-       now = sched_clock();
-       for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
-               cache = rcu_dereference(ghes_estatus_caches[i]);
-               if (cache == NULL) {
-                       slot = i;
-                       break;
-               }
-               duration = now - cache->time_in;
-               if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) {
-                       slot = i;
-                       break;
-               }
-               count = atomic_read(&cache->count);
-               period = duration;
-               do_div(period, (count + 1));
-               if (period > max_period) {
-                       max_period = period;
-                       slot = i;
-               }
-       }
-       rcu_read_unlock();
-
-       if (slot != -1) {
-               /*
-                * Use release semantics to ensure that ghes_estatus_cached()
-                * running on another CPU will see the updated cache fields if
-                * it can see the new value of the pointer.
-                */
-               victim = xchg_release(&ghes_estatus_caches[slot],
-                                     RCU_INITIALIZER(new_cache));
-
-               /*
-                * At this point, victim may point to a cached item different
-                * from the one based on which we selected the slot. Instead of
-                * going to the loop again to pick another slot, let's just
-                * drop the other item anyway: this may cause a false cache
-                * miss later on, but that won't cause any problems.
-                */
-               if (victim)
-                       call_rcu(&unrcu_pointer(victim)->rcu,
-                                ghes_estatus_cache_rcu_free);
-       }
-}
-
 static void __ghes_panic(struct ghes *ghes,
                         struct acpi_hest_generic_status *estatus,
                         u64 buf_paddr, enum fixed_addresses fixmap_idx)
diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
index 9c668725e08a..44b644584703 100644
--- a/drivers/acpi/apei/ghes_cper.c
+++ b/drivers/acpi/apei/ghes_cper.c
@@ -13,10 +13,14 @@
  */
 
 #include <linux/err.h>
+#include <linux/genalloc.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/math64.h>
 #include <linux/mm.h>
 #include <linux/ratelimit.h>
+#include <linux/rcupdate.h>
+#include <linux/sched/clock.h>
 #include <linux/slab.h>
 
 #include <acpi/apei.h>
@@ -28,6 +32,9 @@
 
 #include "apei-internal.h"
 
+static struct ghes_estatus_cache __rcu 
*ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
+static atomic_t ghes_estatus_cache_alloced;
+
 static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
 {
        phys_addr_t paddr;
@@ -273,3 +280,139 @@ void ghes_clear_estatus(struct ghes *ghes,
        if (is_hest_type_generic_v2(ghes))
                ghes_ack_error(ghes->generic_v2);
 }
+
+/*
+ * GHES error status reporting throttle, to report more kinds of
+ * errors, instead of just most frequently occurred errors.
+ */
+int ghes_estatus_cached(struct acpi_hest_generic_status *estatus)
+{
+       u32 len;
+       int i, cached = 0;
+       unsigned long long now;
+       struct ghes_estatus_cache *cache;
+       struct acpi_hest_generic_status *cache_estatus;
+
+       len = cper_estatus_len(estatus);
+       rcu_read_lock();
+       for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
+               cache = rcu_dereference(ghes_estatus_caches[i]);
+               if (cache == NULL)
+                       continue;
+               if (len != cache->estatus_len)
+                       continue;
+               cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
+               if (memcmp(estatus, cache_estatus, len))
+                       continue;
+               atomic_inc(&cache->count);
+               now = sched_clock();
+               if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC)
+                       cached = 1;
+               break;
+       }
+       rcu_read_unlock();
+       return cached;
+}
+
+static struct ghes_estatus_cache *ghes_estatus_cache_alloc(
+       struct acpi_hest_generic *generic,
+       struct acpi_hest_generic_status *estatus)
+{
+       int alloced;
+       u32 len, cache_len;
+       struct ghes_estatus_cache *cache;
+       struct acpi_hest_generic_status *cache_estatus;
+
+       alloced = atomic_add_return(1, &ghes_estatus_cache_alloced);
+       if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) {
+               atomic_dec(&ghes_estatus_cache_alloced);
+               return NULL;
+       }
+       len = cper_estatus_len(estatus);
+       cache_len = GHES_ESTATUS_CACHE_LEN(len);
+       cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len);
+       if (cache == NULL) {
+               atomic_dec(&ghes_estatus_cache_alloced);
+               return NULL;
+       }
+       cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
+       memcpy(cache_estatus, estatus, len);
+       cache->estatus_len = len;
+       atomic_set(&cache->count, 0);
+       cache->generic = generic;
+       cache->time_in = sched_clock();
+       return cache;
+}
+
+static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
+{
+       struct ghes_estatus_cache *cache;
+       u32 len;
+
+       cache = container_of(head, struct ghes_estatus_cache, rcu);
+       len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache));
+       len = GHES_ESTATUS_CACHE_LEN(len);
+       gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len);
+       atomic_dec(&ghes_estatus_cache_alloced);
+}
+
+void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
+                           struct acpi_hest_generic_status *estatus)
+{
+       unsigned long long now, duration, period, max_period = 0;
+       struct ghes_estatus_cache *cache, *new_cache;
+       struct ghes_estatus_cache __rcu *victim;
+       int i, slot = -1, count;
+
+       new_cache = ghes_estatus_cache_alloc(generic, estatus);
+       if (!new_cache)
+               return;
+
+       rcu_read_lock();
+       now = sched_clock();
+       for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
+               cache = rcu_dereference(ghes_estatus_caches[i]);
+               if (cache == NULL) {
+                       slot = i;
+                       break;
+               }
+               duration = now - cache->time_in;
+               if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) {
+                       slot = i;
+                       break;
+               }
+               count = atomic_read(&cache->count);
+               period = duration;
+               do_div(period, (count + 1));
+               if (period > max_period) {
+                       max_period = period;
+                       slot = i;
+               }
+       }
+       rcu_read_unlock();
+
+       if (slot != -1) {
+               /*
+                * Use release semantics to ensure that ghes_estatus_cached()
+                * running on another CPU will see the updated cache fields if
+                * it can see the new value of the pointer.
+                */
+               victim = xchg_release(&ghes_estatus_caches[slot],
+                                     RCU_INITIALIZER(new_cache));
+
+               /*
+                * At this point, victim may point to a cached item different
+                * from the one based on which we selected the slot. Instead of
+                * going to the loop again to pick another slot, let's just
+                * drop the other item anyway: this may cause a false cache
+                * miss later on, but that won't cause any problems.
+                */
+               if (victim)
+                       call_rcu(&unrcu_pointer(victim)->rcu,
+                                ghes_estatus_cache_rcu_free);
+       } else {
+               gen_pool_free(ghes_estatus_pool, (unsigned long)new_cache,
+                            GHES_ESTATUS_CACHE_LEN(new_cache->estatus_len));
+               atomic_dec(&ghes_estatus_cache_alloced);
+       }
+}
diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
index 4649e3140888..15305c8be9a7 100644
--- a/include/acpi/ghes_cper.h
+++ b/include/acpi/ghes_cper.h
@@ -55,6 +55,8 @@
        ((struct acpi_hest_generic_data *)                              \
        ((struct ghes_vendor_record_entry *)(vendor_entry) + 1))
 
+extern struct gen_pool *ghes_estatus_pool;
+
 static inline bool is_hest_type_generic_v2(struct ghes *ghes)
 {
        return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
@@ -99,5 +101,8 @@ int __ghes_read_estatus(struct acpi_hest_generic_status 
*estatus,
                        u64 buf_paddr, enum fixed_addresses fixmap_idx,
                        size_t buf_len);
 #endif
+int ghes_estatus_cached(struct acpi_hest_generic_status *estatus);
+void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
+                           struct acpi_hest_generic_status *estatus);
 
 #endif /* ACPI_APEI_GHES_CPER_H */

-- 
2.43.0


Reply via email to