Firmware is evil:
 - ACPI was created to "try and make the 'ACPI' extensions somehow
 Windows specific" in order to "work well with NT and not the others
 even if they are open"
 - EFI was created to hide "secret" registers from the OS.
 - UEFI was created to allow compromising an otherwise secure OS.

Never has firmware been created to solve a problem or simplify an
otherwise cumbersome process. It is of no surprise then, that
firmware nowadays intentionally crashes an OS.

One simple way to do that is to mark GHES errors as fatal. Firmware
knows and even expects that an OS will crash in this case. And most
OSes do.

PCIe errors are notorious for having different definitions of "fatal".
In ACPI, and other firmware sandards, 'fatal' means the machine is
about to explode and needs to be reset. In PCIe, on the other hand,
fatal means that the link to a device has died. In the hotplug world
of PCIe, this is akin to a USB disconnect. From that view, the "fatal"
loss of a link is a normal event. To allow a machine to crash in this
case is downright idiotic.

To solve this, implement an IRQ safe handler for AER. This makes sure
we have enough information to invoke the full AER handler later down
the road, and tells ghes_notify_nmi that "It's all cool".
ghes_notify_nmi() then gets calmed down a little, and doesn't panic().

Signed-off-by: Alexandru Gagniuc <mr.nuke...@gmail.com>
---
 drivers/acpi/apei/ghes.c | 44 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 42 insertions(+), 2 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 2119c51b4a9e..e0528da4e8f8 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -481,12 +481,26 @@ static int ghes_handle_aer(struct acpi_hest_generic_data 
*gdata, int sev)
        return ghes_severity(gdata->error_severity);
 }
 
+static int ghes_handle_aer_irqsafe(struct acpi_hest_generic_data *gdata,
+                                  int sev)
+{
+       struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata);
+
+       /* The system can always recover from AER errors. */
+       if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID &&
+               pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO)
+               return CPER_SEV_RECOVERABLE;
+
+       return ghes_severity(gdata->error_severity);
+}
+
 /**
  * ghes_handler - handler for ACPI APEI errors
  * @error_uuid: UUID describing the error entry (See ACPI/EFI CPER for details)
  * @handle: Handler for the GHES entry of type 'error_uuid'. The handler
  *     returns the severity of the error after handling. A handler is allowed
  *     to demote errors to correctable or corrected, as appropriate.
+ * @handle_irqsafe: (optional) Non-blocking handler for GHES entry.
  */
 static const struct ghes_handler {
        const guid_t *error_uuid;
@@ -498,6 +512,7 @@ static const struct ghes_handler {
                .handle = ghes_handle_mem,
        }, {
                .error_uuid = &CPER_SEC_PCIE,
+               .handle_irqsafe = ghes_handle_aer_irqsafe,
                .handle = ghes_handle_aer,
        }, {
                .error_uuid = &CPER_SEC_PROC_ARM,
@@ -551,6 +566,30 @@ static void ghes_do_proc(struct ghes *ghes,
        }
 }
 
+/* How severe is the error if handling is deferred outside IRQ/NMI context? */
+static int ghes_deferrable_severity(struct ghes *ghes)
+{
+       int deferrable_sev, sev, sec_sev;
+       struct acpi_hest_generic_data *gdata;
+       const struct ghes_handler *handler;
+       const guid_t *section_type;
+       const struct acpi_hest_generic_status *estatus = ghes->estatus;
+
+       deferrable_sev = GHES_SEV_NO;
+       sev = ghes_severity(estatus->error_severity);
+       apei_estatus_for_each_section(estatus, gdata) {
+               section_type = (guid_t *)gdata->section_type;
+               handler = get_handler(section_type);
+               if (handler && handler->handle_irqsafe)
+                       sec_sev = handler->handle_irqsafe(gdata, sev);
+               else
+                       sec_sev = ghes_severity(gdata->error_severity);
+               deferrable_sev = max(deferrable_sev, sec_sev);
+       }
+
+       return deferrable_sev;
+}
+
 static void __ghes_print_estatus(const char *pfx,
                                 const struct acpi_hest_generic *generic,
                                 const struct acpi_hest_generic_status *estatus)
@@ -980,7 +1019,7 @@ static void __process_error(struct ghes *ghes)
 static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
 {
        struct ghes *ghes;
-       int sev, ret = NMI_DONE;
+       int sev, dsev, ret = NMI_DONE;
 
        if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
                return ret;
@@ -993,8 +1032,9 @@ static int ghes_notify_nmi(unsigned int cmd, struct 
pt_regs *regs)
                        ret = NMI_HANDLED;
                }
 
+               dsev = ghes_deferrable_severity(ghes);
                sev = ghes_severity(ghes->estatus->error_severity);
-               if (sev >= GHES_SEV_PANIC) {
+               if ((sev >= GHES_SEV_PANIC) && (dsev >= GHES_SEV_PANIC)) {
                        oops_begin();
                        ghes_print_queued_estatus();
                        __ghes_panic(ghes);
-- 
2.14.3

Reply via email to