Relocate the CPER buffer mapping, peek, and clear helpers from ghes.c into
ghes_cper.c so they can be shared with other firmware-first providers.
This commit only shuffles code; behavior stays the same.

While here, fix ghes_ack_error() mask handling and add second-copy length
validation in __ghes_read_estatus().

Reviewed-by: Jonathan Cameron <[email protected]>
Signed-off-by: Ahmed Tiba <[email protected]>
---
 drivers/acpi/apei/Makefile    |   2 +-
 drivers/acpi/apei/ghes.c      | 166 ---------------------------------
 drivers/acpi/apei/ghes_cper.c | 210 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 211 insertions(+), 167 deletions(-)

diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile
index 66588d6be56f..f57f3b009d8e 100644
--- a/drivers/acpi/apei/Makefile
+++ b/drivers/acpi/apei/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_ACPI_APEI)                += apei.o
-obj-$(CONFIG_ACPI_APEI_GHES)   += ghes.o
+obj-$(CONFIG_ACPI_APEI_GHES)   += ghes.o ghes_cper.o
 # clang versions prior to 18 may blow out the stack with KASAN
 ifeq ($(CONFIG_COMPILE_TEST)_$(CONFIG_CC_IS_CLANG)_$(call clang-min-version, 
180000),y_y_)
 KASAN_SANITIZE_ghes.o := n
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index c73a316644e8..f74fd5de34cc 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -118,26 +118,6 @@ 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;
 
-static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
-{
-       phys_addr_t paddr;
-       pgprot_t prot;
-
-       paddr = PFN_PHYS(pfn);
-       prot = arch_apei_get_mem_attribute(paddr);
-       __set_fixmap(fixmap_idx, paddr, prot);
-
-       return (void __iomem *) __fix_to_virt(fixmap_idx);
-}
-
-static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx)
-{
-       int _idx = virt_to_fix((unsigned long)vaddr);
-
-       WARN_ON_ONCE(fixmap_idx != _idx);
-       clear_fixmap(fixmap_idx);
-}
-
 int ghes_estatus_pool_init(unsigned int num_ghes)
 {
        unsigned long addr, len;
@@ -193,21 +173,6 @@ static void unmap_gen_v2(struct ghes *ghes)
        apei_unmap_generic_address(&ghes->generic_v2->read_ack_register);
 }
 
-static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
-{
-       int rc;
-       u64 val = 0;
-
-       rc = apei_read(&val, &gv2->read_ack_register);
-       if (rc)
-               return;
-
-       val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset;
-       val |= gv2->read_ack_write    << gv2->read_ack_register.bit_offset;
-
-       apei_write(val, &gv2->read_ack_register);
-}
-
 struct ghes *ghes_new(struct acpi_hest_generic *generic)
 {
        struct ghes *ghes;
@@ -280,137 +245,6 @@ static inline int ghes_severity(int severity)
        }
 }
 
-static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
-                                 int from_phys,
-                                 enum fixed_addresses fixmap_idx)
-{
-       void __iomem *vaddr;
-       u64 offset;
-       u32 trunk;
-
-       while (len > 0) {
-               offset = paddr - (paddr & PAGE_MASK);
-               vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx);
-               trunk = PAGE_SIZE - offset;
-               trunk = min(trunk, len);
-               if (from_phys)
-                       memcpy_fromio(buffer, vaddr + offset, trunk);
-               else
-                       memcpy_toio(vaddr + offset, buffer, trunk);
-               len -= trunk;
-               paddr += trunk;
-               buffer += trunk;
-               ghes_unmap(vaddr, fixmap_idx);
-       }
-}
-
-/* Check the top-level record header has an appropriate size. */
-int __ghes_check_estatus(struct ghes *ghes,
-                        struct acpi_hest_generic_status *estatus)
-{
-       u32 len = cper_estatus_len(estatus);
-       u32 max_len = min(ghes->generic->error_block_length,
-                         ghes->estatus_length);
-
-       if (len < sizeof(*estatus)) {
-               pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status 
block!\n");
-               return -EIO;
-       }
-
-       if (!len || len > max_len) {
-               pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status 
block length!\n");
-               return -EIO;
-       }
-
-       if (cper_estatus_check_header(estatus)) {
-               pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n");
-               return -EIO;
-       }
-
-       return 0;
-}
-
-/* Read the CPER block, returning its address, and header in estatus. */
-int __ghes_peek_estatus(struct ghes *ghes,
-                       struct acpi_hest_generic_status *estatus,
-                       u64 *buf_paddr, enum fixed_addresses fixmap_idx)
-{
-       struct acpi_hest_generic *g = ghes->generic;
-       int rc;
-
-       rc = apei_read(buf_paddr, &g->error_status_address);
-       if (rc) {
-               *buf_paddr = 0;
-               pr_warn_ratelimited(FW_WARN GHES_PFX
-                                   "Failed to read error status block address 
for hardware error source: %d.\n",
-                                  g->header.source_id);
-               return -EIO;
-       }
-       if (!*buf_paddr)
-               return -ENOENT;
-
-       ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1,
-                             fixmap_idx);
-       if (!estatus->block_status) {
-               *buf_paddr = 0;
-               return -ENOENT;
-       }
-
-       return 0;
-}
-
-int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
-                       u64 buf_paddr, enum fixed_addresses fixmap_idx,
-                       size_t buf_len)
-{
-       ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx);
-       if (cper_estatus_check(estatus)) {
-               pr_warn_ratelimited(FW_WARN GHES_PFX
-                                   "Failed to read error status block!\n");
-               return -EIO;
-       }
-
-       return 0;
-}
-
-int ghes_read_estatus(struct ghes *ghes,
-                     struct acpi_hest_generic_status *estatus,
-                     u64 *buf_paddr, enum fixed_addresses fixmap_idx)
-{
-       int rc;
-
-       rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx);
-       if (rc)
-               return rc;
-
-       rc = __ghes_check_estatus(ghes, estatus);
-       if (rc)
-               return rc;
-
-       return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx,
-                                  cper_estatus_len(estatus));
-}
-
-void ghes_clear_estatus(struct ghes *ghes,
-                       struct acpi_hest_generic_status *estatus,
-                       u64 buf_paddr, enum fixed_addresses fixmap_idx)
-{
-       estatus->block_status = 0;
-
-       if (!buf_paddr)
-               return;
-
-       ghes_copy_tofrom_phys(estatus, buf_paddr,
-                             sizeof(estatus->block_status), 0,
-                             fixmap_idx);
-
-       /*
-        * GHESv2 type HEST entries introduce support for error acknowledgment,
-        * so only acknowledge the error if this support is present.
-        */
-       if (is_hest_type_generic_v2(ghes))
-               ghes_ack_error(ghes->generic_v2);
-}
 
 /**
  * struct ghes_task_work - for synchronous RAS event
diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
new file mode 100644
index 000000000000..0c3f52b2d7f1
--- /dev/null
+++ b/drivers/acpi/apei/ghes_cper.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Shared GHES helpers for firmware-first CPER error handling.
+ *
+ * This file holds the GHES helper code that is shared by the in-tree GHES
+ * driver and by other firmware-first error sources that reuse the same CPER
+ * handling flow.
+ *
+ * Derived from the ACPI APEI GHES driver.
+ *
+ * Copyright 2010,2011 Intel Corp.
+ *   Author: Huang Ying <[email protected]>
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+
+#include <acpi/apei.h>
+#include <acpi/acpi_io.h>
+#include <acpi/ghes_cper.h>
+
+#include <asm/fixmap.h>
+#include <asm/tlbflush.h>
+
+#include "apei-internal.h"
+
+static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
+{
+       phys_addr_t paddr;
+       pgprot_t prot;
+
+       paddr = PFN_PHYS(pfn);
+       prot = arch_apei_get_mem_attribute(paddr);
+       __set_fixmap(fixmap_idx, paddr, prot);
+
+       return (void __iomem *) __fix_to_virt(fixmap_idx);
+}
+
+static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx)
+{
+       int _idx = virt_to_fix((unsigned long)vaddr);
+
+       WARN_ON_ONCE(fixmap_idx != _idx);
+       clear_fixmap(fixmap_idx);
+}
+
+static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
+{
+       int rc;
+       u64 val = 0;
+
+       rc = apei_read(&val, &gv2->read_ack_register);
+       if (rc)
+               return;
+
+       val &= gv2->read_ack_preserve;
+       val |= gv2->read_ack_write;
+
+       apei_write(val, &gv2->read_ack_register);
+}
+
+static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
+                                 int from_phys,
+                                 enum fixed_addresses fixmap_idx)
+{
+       void __iomem *vaddr;
+       u64 offset;
+       u32 trunk;
+
+       while (len > 0) {
+               offset = paddr - (paddr & PAGE_MASK);
+               vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx);
+               trunk = PAGE_SIZE - offset;
+               trunk = min(trunk, len);
+               if (from_phys)
+                       memcpy_fromio(buffer, vaddr + offset, trunk);
+               else
+                       memcpy_toio(vaddr + offset, buffer, trunk);
+               len -= trunk;
+               paddr += trunk;
+               buffer += trunk;
+               ghes_unmap(vaddr, fixmap_idx);
+       }
+}
+
+/* Check the top-level record header has an appropriate size. */
+int __ghes_check_estatus(struct ghes *ghes,
+                        struct acpi_hest_generic_status *estatus)
+{
+       u32 len = cper_estatus_len(estatus);
+       u32 max_len = min(ghes->generic->error_block_length,
+                         ghes->estatus_length);
+
+       if (len < sizeof(*estatus)) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status 
block!\n");
+               return -EIO;
+       }
+
+       if (len > max_len) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status 
block length!\n");
+               return -EIO;
+       }
+
+       if (cper_estatus_check_header(estatus)) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+/* Read the CPER block, returning its address, and header in estatus. */
+int __ghes_peek_estatus(struct ghes *ghes,
+                       struct acpi_hest_generic_status *estatus,
+                       u64 *buf_paddr, enum fixed_addresses fixmap_idx)
+{
+       struct acpi_hest_generic *g = ghes->generic;
+       int rc;
+
+       rc = apei_read(buf_paddr, &g->error_status_address);
+       if (rc) {
+               *buf_paddr = 0;
+               pr_warn_ratelimited(FW_WARN GHES_PFX
+                                   "Failed to read error status block address 
for hardware error source: %d.\n",
+                                   g->header.source_id);
+               return -EIO;
+       }
+       if (!*buf_paddr)
+               return -ENOENT;
+
+       ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1,
+                             fixmap_idx);
+       if (!estatus->block_status) {
+               *buf_paddr = 0;
+               return -ENOENT;
+       }
+
+       return 0;
+}
+
+int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
+                       u64 buf_paddr, enum fixed_addresses fixmap_idx,
+                       size_t buf_len)
+{
+       u32 len;
+
+       ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx);
+
+       len = cper_estatus_len(estatus);
+       if (len < sizeof(*estatus)) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status 
block!\n");
+               return -EIO;
+       }
+
+       if (len > buf_len) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status 
block length!\n");
+               return -EIO;
+       }
+
+       if (cper_estatus_check(estatus)) {
+               pr_warn_ratelimited(FW_WARN GHES_PFX
+                                   "Failed to read error status block!\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+int ghes_read_estatus(struct ghes *ghes,
+                     struct acpi_hest_generic_status *estatus,
+                     u64 *buf_paddr, enum fixed_addresses fixmap_idx)
+{
+       int rc;
+
+       rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx);
+       if (rc)
+               return rc;
+
+       rc = __ghes_check_estatus(ghes, estatus);
+       if (rc)
+               return rc;
+
+       return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx,
+                                  cper_estatus_len(estatus));
+}
+
+void ghes_clear_estatus(struct ghes *ghes,
+                       struct acpi_hest_generic_status *estatus,
+                       u64 buf_paddr, enum fixed_addresses fixmap_idx)
+{
+       estatus->block_status = 0;
+
+       if (!buf_paddr)
+               return;
+
+       ghes_copy_tofrom_phys(estatus, buf_paddr,
+                             sizeof(estatus->block_status), 0,
+                             fixmap_idx);
+
+       /*
+        * GHESv2 type HEST entries introduce support for error acknowledgment,
+        * so only acknowledge the error if this support is present.
+        */
+       if (is_hest_type_generic_v2(ghes))
+               ghes_ack_error(ghes->generic_v2);
+}

-- 
2.43.0


Reply via email to