Author: mav
Date: Mon Jul 27 21:19:41 2020
New Revision: 363624
URL: https://svnweb.freebsd.org/changeset/base/363624

Log:
  Add initial driver for ACPI Platform Error Interfaces.
  
  APEI allows platform to report different kinds of errors to OS in several
  ways.  We've found that Supermicro X10/X11 motherboards report PCIe errors
  appearing on hot-unplug via this interface using NMI.  Without respective
  driver it ended up in kernel panic without any additional information.
  
  This driver introduces support for the APEI Generic Hardware Error Source
  reporting via NMI, SCI or polling.  It decodes the reported errors and
  either pass them to pci(4) for processing or just logs otherwise.  Errors
  marked as fatal still end up in kernel panic, but some more informative.
  
  When somebody get to native PCIe AER support implementation both of the
  reporting mechanisms should get common error recovery code.  Since in our
  case errors happen when the device is already gone, there is nothing to
  recover, so the code just clears the error statuses, practically ignoring
  the otherwise destructive NMIs in nicer way.
  
  MFC after:    2 weeks
  Relnotes:     yes
  Sponsored by: iXsystems, Inc.

Added:
  head/sys/dev/acpica/acpi_apei.c   (contents, props changed)
Modified:
  head/sys/arm64/arm64/machdep.c
  head/sys/arm64/include/acpica_machdep.h
  head/sys/conf/files
  head/sys/dev/acpica/acpi.c
  head/sys/dev/pci/pci.c
  head/sys/dev/pci/pcivar.h
  head/sys/x86/include/acpica_machdep.h
  head/sys/x86/x86/cpu_machdep.c

Modified: head/sys/arm64/arm64/machdep.c
==============================================================================
--- head/sys/arm64/arm64/machdep.c      Mon Jul 27 19:05:53 2020        
(r363623)
+++ head/sys/arm64/arm64/machdep.c      Mon Jul 27 21:19:41 2020        
(r363624)
@@ -132,6 +132,8 @@ void pagezero_cache(void *);
 /* pagezero_simple is default pagezero */
 void (*pagezero)(void *p) = pagezero_simple;
 
+int (*apei_nmi)(void);
+
 static void
 pan_setup(void)
 {

Modified: head/sys/arm64/include/acpica_machdep.h
==============================================================================
--- head/sys/arm64/include/acpica_machdep.h     Mon Jul 27 19:05:53 2020        
(r363623)
+++ head/sys/arm64/include/acpica_machdep.h     Mon Jul 27 21:19:41 2020        
(r363624)
@@ -57,6 +57,8 @@ struct acpi_generic_address;
 int    acpi_map_addr(struct acpi_generic_address  *, bus_space_tag_t *,
     bus_space_handle_t *, bus_size_t);
 
+extern int (*apei_nmi)(void);
+
 #endif /* _KERNEL */
 
 #endif /* __ACPICA_MACHDEP_H__ */

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files Mon Jul 27 19:05:53 2020        (r363623)
+++ head/sys/conf/files Mon Jul 27 21:19:41 2020        (r363624)
@@ -754,6 +754,7 @@ dev/acpica/Osd/OsdSynch.c   optional acpi
 dev/acpica/Osd/OsdTable.c      optional acpi
 dev/acpica/acpi.c              optional acpi
 dev/acpica/acpi_acad.c         optional acpi
+dev/acpica/acpi_apei.c         optional acpi
 dev/acpica/acpi_battery.c      optional acpi
 dev/acpica/acpi_button.c       optional acpi
 dev/acpica/acpi_cmbat.c                optional acpi

Modified: head/sys/dev/acpica/acpi.c
==============================================================================
--- head/sys/dev/acpica/acpi.c  Mon Jul 27 19:05:53 2020        (r363623)
+++ head/sys/dev/acpica/acpi.c  Mon Jul 27 21:19:41 2020        (r363624)
@@ -152,6 +152,7 @@ static ACPI_STATUS acpi_device_scan_children(device_t 
                    int max_depth, acpi_scan_cb_t user_fn, void *arg);
 static int     acpi_isa_pnp_probe(device_t bus, device_t child,
                    struct isa_pnp_id *ids);
+static void    acpi_platform_osc(device_t dev);
 static void    acpi_probe_children(device_t bus);
 static void    acpi_probe_order(ACPI_HANDLE handle, int *order);
 static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level,
@@ -683,6 +684,8 @@ acpi_attach(device_t dev)
     /* Register ACPI again to pass the correct argument of pm_func. */
     power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, sc);
 
+    acpi_platform_osc(dev);
+
     if (!acpi_disabled("bus")) {
        EVENTHANDLER_REGISTER(dev_lookup, acpi_lookup, NULL, 1000);
        acpi_probe_children(dev);
@@ -1941,6 +1944,34 @@ acpi_enable_pcie(void)
                alloc++;
        }
 #endif
+}
+
+static void
+acpi_platform_osc(device_t dev)
+{
+       ACPI_HANDLE sb_handle;
+       ACPI_STATUS status;
+       uint32_t cap_set[2];
+
+       /* 0811B06E-4A27-44F9-8D60-3CBBC22E7B48 */
+       static uint8_t acpi_platform_uuid[ACPI_UUID_LENGTH] = {
+               0x6e, 0xb0, 0x11, 0x08, 0x27, 0x4a, 0xf9, 0x44,
+               0x8d, 0x60, 0x3c, 0xbb, 0xc2, 0x2e, 0x7b, 0x48
+       };
+
+       if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle)))
+               return;
+
+       cap_set[1] = 0x10;      /* APEI Support */
+       status = acpi_EvaluateOSC(sb_handle, acpi_platform_uuid, 1,
+           nitems(cap_set), cap_set, cap_set, false);
+       if (ACPI_FAILURE(status)) {
+               if (status == AE_NOT_FOUND)
+                       return;
+               device_printf(dev, "_OSC failed: %s\n",
+                   AcpiFormatException(status));
+               return;
+       }
 }
 
 /*

Added: head/sys/dev/acpica/acpi_apei.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/dev/acpica/acpi_apei.c     Mon Jul 27 21:19:41 2020        
(r363624)
@@ -0,0 +1,684 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Alexander Motin <m...@freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/interrupt.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/queue.h>
+#include <sys/rman.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <contrib/dev/acpica/include/aclocal.h>
+#include <contrib/dev/acpica/include/actables.h>
+
+#include <dev/acpica/acpivar.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+struct apei_ge {
+       union {
+               ACPI_HEST_GENERIC v1;
+               ACPI_HEST_GENERIC_V2 v2;
+       };
+       int              res_type;
+       int              res_rid;
+       struct resource *res;
+       int              res2_type;
+       int              res2_rid;
+       struct resource *res2;
+       uint8_t         *buf, *copybuf;
+       TAILQ_ENTRY(apei_ge) link;
+       struct callout   poll;
+       void            *swi_ih;
+} *apei_nmi_ge;
+
+struct apei_softc {
+       ACPI_TABLE_HEST *hest;
+       TAILQ_HEAD(, apei_ge) ges;
+};
+
+struct apei_mem_error {
+       uint64_t        ValidationBits;
+       uint64_t        ErrorStatus;
+       uint64_t        PhysicalAddress;
+       uint64_t        PhysicalAddressMask;
+       uint16_t        Node;
+       uint16_t        Card;
+       uint16_t        Module;
+       uint16_t        Bank;
+       uint16_t        Device;
+       uint16_t        Row;
+       uint16_t        Column;
+       uint16_t        BitPosition;
+       uint64_t        RequesterID;
+       uint64_t        ResponderID;
+       uint64_t        TargetID;
+       uint8_t         MemoryErrorType;
+       uint8_t         Extended;
+       uint16_t        RankNumber;
+       uint16_t        CardHandle;
+       uint16_t        ModuleHandle;
+};
+
+struct apei_pcie_error {
+       uint64_t        ValidationBits;
+       uint32_t        PortType;
+       uint32_t        Version;
+       uint32_t        CommandStatus;
+       uint32_t        Reserved;
+       uint8_t         DeviceID[16];
+       uint8_t         DeviceSerialNumber[8];
+       uint8_t         BridgeControlStatus[4];
+       uint8_t         CapabilityStructure[60];
+       uint8_t         AERInfo[96];
+};
+
+#ifdef __i386__
+static __inline uint64_t
+apei_bus_read_8(struct resource *res, bus_size_t offset)
+{
+       return (bus_read_4(res, offset) |
+           ((uint64_t)bus_read_4(res, offset + 4)) << 32);
+}
+static __inline void
+apei_bus_write_8(struct resource *res, bus_size_t offset, uint64_t val)
+{
+       bus_write_4(res, offset, val);
+       bus_write_4(res, offset + 4, val >> 32);
+}
+#define        READ8(r, o)     apei_bus_read_8((r), (o))
+#define        WRITE8(r, o, v) apei_bus_write_8((r), (o), (v))
+#else
+#define        READ8(r, o)     bus_read_8((r), (o))
+#define        WRITE8(r, o, v) bus_write_8((r), (o), (v))
+#endif
+
+int apei_nmi_handler(void);
+
+static const char *
+apei_severity(uint32_t s)
+{
+       switch (s) {
+       case ACPI_HEST_GEN_ERROR_RECOVERABLE:
+           return ("Recoverable");
+       case ACPI_HEST_GEN_ERROR_FATAL:
+           return ("Fatal");
+       case ACPI_HEST_GEN_ERROR_CORRECTED:
+           return ("Corrected");
+       case ACPI_HEST_GEN_ERROR_NONE:
+           return ("Informational");
+       }
+       return ("???");
+}
+
+static int
+apei_mem_handler(ACPI_HEST_GENERIC_DATA *ged)
+{
+       struct apei_mem_error *p = (struct apei_mem_error *)(ged + 1);
+
+       printf("APEI %s Memory Error:\n", apei_severity(ged->ErrorSeverity));
+       if (p->ValidationBits & 0x01)
+               printf(" Error Status: 0x%jx\n", p->ErrorStatus);
+       if (p->ValidationBits & 0x02)
+               printf(" Physical Address: 0x%jx\n", p->PhysicalAddress);
+       if (p->ValidationBits & 0x04)
+               printf(" Physical Address Mask: 0x%jx\n", 
p->PhysicalAddressMask);
+       if (p->ValidationBits & 0x08)
+               printf(" Node: %u\n", p->Node);
+       if (p->ValidationBits & 0x10)
+               printf(" Card: %u\n", p->Card);
+       if (p->ValidationBits & 0x20)
+               printf(" Module: %u\n", p->Module);
+       if (p->ValidationBits & 0x40)
+               printf(" Bank: %u\n", p->Bank);
+       if (p->ValidationBits & 0x80)
+               printf(" Device: %u\n", p->Device);
+       if (p->ValidationBits & 0x100)
+               printf(" Row: %u\n", p->Row);
+       if (p->ValidationBits & 0x200)
+               printf(" Column: %u\n", p->Column);
+       if (p->ValidationBits & 0x400)
+               printf(" Bit Position: %u\n", p->BitPosition);
+       if (p->ValidationBits & 0x800)
+               printf(" Requester ID: 0x%jx\n", p->RequesterID);
+       if (p->ValidationBits & 0x1000)
+               printf(" Responder ID: 0x%jx\n", p->ResponderID);
+       if (p->ValidationBits & 0x2000)
+               printf(" Target ID: 0x%jx\n", p->TargetID);
+       if (p->ValidationBits & 0x4000)
+               printf(" Memory Error Type: %u\n", p->MemoryErrorType);
+       if (p->ValidationBits & 0x8000)
+               printf(" Rank Number: %u\n", p->RankNumber);
+       if (p->ValidationBits & 0x10000)
+               printf(" Card Handle: 0x%x\n", p->CardHandle);
+       if (p->ValidationBits & 0x20000)
+               printf(" Module Handle: 0x%x\n", p->ModuleHandle);
+       if (p->ValidationBits & 0x40000)
+               printf(" Extended Row: %u\n",
+                   (uint32_t)(p->Extended & 0x3) << 16 | p->Row);
+       if (p->ValidationBits & 0x80000)
+               printf(" Bank Group: %u\n", p->Bank >> 8);
+       if (p->ValidationBits & 0x100000)
+               printf(" Bank Address: %u\n", p->Bank & 0xff);
+       if (p->ValidationBits & 0x200000)
+               printf(" Chip Identification: %u\n", (p->Extended >> 5) & 0x7);
+
+       return (0);
+}
+
+static int
+apei_pcie_handler(ACPI_HEST_GENERIC_DATA *ged)
+{
+       struct apei_pcie_error *p = (struct apei_pcie_error *)(ged + 1);
+       device_t dev;
+       int h = 0, off, sev;
+
+       if ((p->ValidationBits & 0x8) == 0x8) {
+               mtx_lock(&Giant);
+               dev = pci_find_dbsf((uint32_t)p->DeviceID[10] << 8 |
+                   p->DeviceID[9], p->DeviceID[11], p->DeviceID[8],
+                   p->DeviceID[7]);
+               if (dev != NULL) {
+                       switch (ged->ErrorSeverity) {
+                       case ACPI_HEST_GEN_ERROR_FATAL:
+                               sev = PCIEM_STA_FATAL_ERROR;
+                               break;
+                       case ACPI_HEST_GEN_ERROR_RECOVERABLE:
+                               sev = PCIEM_STA_NON_FATAL_ERROR;
+                               break;
+                       default:
+                               sev = PCIEM_STA_CORRECTABLE_ERROR;
+                               break;
+                       }
+                       pcie_apei_error(dev, sev,
+                           (p->ValidationBits & 0x80) ? p->AERInfo : NULL);
+                       h = 1;
+               }
+               mtx_unlock(&Giant);
+       }
+       if (h)
+               return (h);
+
+       printf("APEI %s PCIe Error:\n", apei_severity(ged->ErrorSeverity));
+       if (p->ValidationBits & 0x01)
+               printf(" Port Type: %u\n", p->PortType);
+       if (p->ValidationBits & 0x02)
+               printf(" Version: %x\n", p->Version);
+       if (p->ValidationBits & 0x04)
+               printf(" Command Status: 0x%08x\n", p->CommandStatus);
+       if (p->ValidationBits & 0x08) {
+               printf(" DeviceID:");
+               for (off = 0; off < sizeof(p->DeviceID); off++)
+                       printf(" %02x", p->DeviceID[off]);
+               printf("\n");
+       }
+       if (p->ValidationBits & 0x10) {
+               printf(" Device Serial Number:");
+               for (off = 0; off < sizeof(p->DeviceSerialNumber); off++)
+                       printf(" %02x", p->DeviceSerialNumber[off]);
+               printf("\n");
+       }
+       if (p->ValidationBits & 0x20) {
+               printf(" Bridge Control Status:");
+               for (off = 0; off < sizeof(p->BridgeControlStatus); off++)
+                       printf(" %02x", p->BridgeControlStatus[off]);
+               printf("\n");
+       }
+       if (p->ValidationBits & 0x40) {
+               printf(" Capability Structure:\n");
+               for (off = 0; off < sizeof(p->CapabilityStructure); off++) {
+                       printf(" %02x", p->CapabilityStructure[off]);
+                       if ((off % 16) == 15 ||
+                           off + 1 == sizeof(p->CapabilityStructure))
+                               printf("\n");
+               }
+       }
+       if (p->ValidationBits & 0x80) {
+               printf(" AER Info:\n");
+               for (off = 0; off < sizeof(p->AERInfo); off++) {
+                       printf(" %02x", p->AERInfo[off]);
+                       if ((off % 16) == 15 || off + 1 == sizeof(p->AERInfo))
+                               printf("\n");
+               }
+       }
+       return (h);
+}
+
+static void
+apei_ged_handler(ACPI_HEST_GENERIC_DATA *ged)
+{
+       ACPI_HEST_GENERIC_DATA_V300 *ged3 = (ACPI_HEST_GENERIC_DATA_V300 *)ged;
+       /* A5BC1114-6F64-4EDE-B863-3E83ED7C83B1 */
+       static uint8_t mem_uuid[ACPI_UUID_LENGTH] = {
+               0x14, 0x11, 0xBC, 0xA5, 0x64, 0x6F, 0xDE, 0x4E,
+               0xB8, 0x63, 0x3E, 0x83, 0xED, 0x7C, 0x83, 0xB1
+       };
+       /* D995E954-BBC1-430F-AD91-B44DCB3C6F35 */
+       static uint8_t pcie_uuid[ACPI_UUID_LENGTH] = {
+               0x54, 0xE9, 0x95, 0xD9, 0xC1, 0xBB, 0x0F, 0x43,
+               0xAD, 0x91, 0xB4, 0x4D, 0xCB, 0x3C, 0x6F, 0x35
+       };
+       uint8_t *t;
+       int h = 0, off;
+
+       if (memcmp(mem_uuid, ged->SectionType, ACPI_UUID_LENGTH) == 0) {
+               h = apei_mem_handler(ged);
+       } else if (memcmp(pcie_uuid, ged->SectionType, ACPI_UUID_LENGTH) == 0) {
+               h = apei_pcie_handler(ged);
+       } else {
+               t = ged->SectionType;
+               printf("APEI %s Error %02x%02x%02x%02x-%02x%02x-"
+                   "%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x:\n",
+                   apei_severity(ged->ErrorSeverity),
+                   t[3], t[2], t[1], t[0], t[5], t[4], t[7], t[6],
+                   t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]);
+               printf(" Error Data:\n");
+               t = (uint8_t *)(ged + 1);
+               for (off = 0; off < ged->ErrorDataLength; off++) {
+                       printf(" %02x", t[off]);
+                       if ((off % 16) == 15 || off + 1 == ged->ErrorDataLength)
+                               printf("\n");
+               }
+       }
+       if (h)
+               return;
+
+       printf(" Flags: 0x%x\n", ged->Flags);
+       if (ged->ValidationBits & ACPI_HEST_GEN_VALID_FRU_ID) {
+               t = ged->FruId;
+               printf(" FRU Id: %02x%02x%02x%02x-%02x%02x-%02x%02x-"
+                   "%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+                   t[3], t[2], t[1], t[0], t[5], t[4], t[7], t[6],
+                   t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]);
+       }
+       if (ged->ValidationBits & ACPI_HEST_GEN_VALID_FRU_STRING)
+               printf(" FRU Text: %.20s", ged->FruText);
+       if (ged->Revision == 0x300 &&
+           ged->ValidationBits & ACPI_HEST_GEN_VALID_TIMESTAMP)
+               printf(" Timestamp: %016jx", ged3->TimeStamp);
+}
+
+static int
+apei_ge_handler(struct apei_ge *ge, bool copy)
+{
+       uint8_t *buf = copy ? ge->copybuf : ge->buf;
+       ACPI_HEST_GENERIC_STATUS *ges = (ACPI_HEST_GENERIC_STATUS *)buf;
+       ACPI_HEST_GENERIC_DATA *ged;
+       uint32_t sev;
+       int i, c, off;
+
+       if (ges->BlockStatus == 0)
+               return (0);
+
+       c = (ges->BlockStatus >> 4) & 0x3ff;
+       sev = ges->ErrorSeverity;
+
+       /* Process error entries. */
+       for (off = i = 0; i < c && off + sizeof(*ged) <= ges->DataLength; i++) {
+               ged = (ACPI_HEST_GENERIC_DATA *)&buf[sizeof(*ges) + off];
+               apei_ged_handler(ged);
+               off += sizeof(*ged) + ged->ErrorDataLength;
+       }
+
+       /* Acknowledge the error has been processed. */
+       ges->BlockStatus = 0;
+       if (!copy && ge->v1.Header.Type == ACPI_HEST_TYPE_GENERIC_ERROR_V2) {
+               uint64_t val = READ8(ge->res2, 0);
+               val &= ge->v2.ReadAckPreserve;
+               val |= ge->v2.ReadAckWrite;
+               WRITE8(ge->res2, 0, val);
+       }
+
+       /* If ACPI told the error is fatal -- make it so. */
+       if (sev == ACPI_HEST_GEN_ERROR_FATAL)
+               panic("APEI Fatal Hardware Error!");
+
+       return (1);
+}
+
+static void
+apei_nmi_swi(void *arg)
+{
+       struct apei_ge *ge = arg;
+
+       apei_ge_handler(ge, true);
+}
+
+int
+apei_nmi_handler(void)
+{
+       struct apei_ge *ge = apei_nmi_ge;
+       ACPI_HEST_GENERIC_STATUS *ges, *gesc;
+
+       if (ge == NULL)
+               return (0);
+
+       ges = (ACPI_HEST_GENERIC_STATUS *)ge->buf;
+       if (ges->BlockStatus == 0)
+               return (0);
+
+       /* If ACPI told the error is fatal -- make it so. */
+       if (ges->ErrorSeverity == ACPI_HEST_GEN_ERROR_FATAL)
+               panic("APEI Fatal Hardware Error!");
+
+       /* Copy the buffer for later processing. */
+       gesc = (ACPI_HEST_GENERIC_STATUS *)ge->copybuf;
+       if (gesc->BlockStatus == 0)
+               memcpy(ge->copybuf, ge->buf, ge->v1.ErrorBlockLength);
+
+       /* Acknowledge the error has been processed. */
+       ges->BlockStatus = 0;
+       if (ge->v1.Header.Type == ACPI_HEST_TYPE_GENERIC_ERROR_V2) {
+               uint64_t val = READ8(ge->res2, 0);
+               val &= ge->v2.ReadAckPreserve;
+               val |= ge->v2.ReadAckWrite;
+               WRITE8(ge->res2, 0, val);
+       }
+
+       /* Schedule SWI for real handling. */
+       swi_sched(ge->swi_ih, SWI_FROMNMI);
+
+       return (1);
+}
+
+static void
+apei_callout_handler(void *context)
+{
+       struct apei_ge *ge = context;
+
+       apei_ge_handler(ge, false);
+       callout_schedule(&ge->poll, ge->v1.Notify.PollInterval * hz / 1000);
+}
+
+static void
+apei_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
+{
+       device_t dev = context;
+       struct apei_softc *sc = device_get_softc(dev);
+       struct apei_ge *ge;
+
+       TAILQ_FOREACH(ge, &sc->ges, link) {
+               if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_SCI ||
+                   ge->v1.Notify.Type == ACPI_HEST_NOTIFY_GPIO ||
+                   ge->v1.Notify.Type == ACPI_HEST_NOTIFY_GSIV)
+                       apei_ge_handler(ge, false);
+       }
+}
+
+static int
+hest_parse_structure(struct apei_softc *sc, void *addr, int remaining)
+{
+       ACPI_HEST_HEADER *hdr = addr;
+       struct apei_ge *ge;
+
+       if (remaining < (int)sizeof(ACPI_HEST_HEADER))
+               return (-1);
+
+       switch (hdr->Type) {
+       case ACPI_HEST_TYPE_IA32_CHECK: {
+               ACPI_HEST_IA_MACHINE_CHECK *s = addr;
+               return (sizeof(*s) + s->NumHardwareBanks *
+                   sizeof(ACPI_HEST_IA_ERROR_BANK));
+       }
+       case ACPI_HEST_TYPE_IA32_CORRECTED_CHECK: {
+               ACPI_HEST_IA_CORRECTED *s = addr;
+               return (sizeof(*s) + s->NumHardwareBanks *
+                   sizeof(ACPI_HEST_IA_ERROR_BANK));
+       }
+       case ACPI_HEST_TYPE_IA32_NMI: {
+               ACPI_HEST_IA_NMI *s = addr;
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_AER_ROOT_PORT: {
+               ACPI_HEST_AER_ROOT *s = addr;
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_AER_ENDPOINT: {
+               ACPI_HEST_AER *s = addr;
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_AER_BRIDGE: {
+               ACPI_HEST_AER_BRIDGE *s = addr;
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_GENERIC_ERROR: {
+               ACPI_HEST_GENERIC *s = addr;
+               ge = malloc(sizeof(*ge), M_DEVBUF, M_WAITOK | M_ZERO);
+               ge->v1 = *s;
+               TAILQ_INSERT_TAIL(&sc->ges, ge, link);
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_GENERIC_ERROR_V2: {
+               ACPI_HEST_GENERIC_V2 *s = addr;
+               ge = malloc(sizeof(*ge), M_DEVBUF, M_WAITOK | M_ZERO);
+               ge->v2 = *s;
+               TAILQ_INSERT_TAIL(&sc->ges, ge, link);
+               return (sizeof(*s));
+       }
+       case ACPI_HEST_TYPE_IA32_DEFERRED_CHECK: {
+               ACPI_HEST_IA_DEFERRED_CHECK *s = addr;
+               return (sizeof(*s) + s->NumHardwareBanks *
+                   sizeof(ACPI_HEST_IA_ERROR_BANK));
+       }
+       default:
+               return (-1);
+       }
+}
+
+static void
+hest_parse_table(struct apei_softc *sc)
+{
+       ACPI_TABLE_HEST *hest = sc->hest;
+       char *cp;
+       int remaining, consumed;
+
+       remaining = hest->Header.Length - sizeof(ACPI_TABLE_HEST);
+       while (remaining > 0) {
+               cp = (char *)hest + hest->Header.Length - remaining;
+               consumed = hest_parse_structure(sc, cp, remaining);
+               if (consumed <= 0)
+                       break;
+               else
+                       remaining -= consumed;
+       }
+}
+
+static char *apei_ids[] = { "PNP0C33", NULL };
+static devclass_t apei_devclass;
+
+static ACPI_STATUS
+apei_find(ACPI_HANDLE handle, UINT32 level, void *context,
+    void **status)
+{
+       int *found = (int *)status;
+       char **ids;
+
+       for (ids = apei_ids; *ids != NULL; ids++) {
+               if (acpi_MatchHid(handle, *ids)) {
+                       *found = 1;
+                       break;
+               }
+       }
+       return (AE_OK);
+}
+
+static void
+apei_identify(driver_t *driver, device_t parent)
+{
+       device_t        child;
+       int             found;
+
+       if (acpi_disabled("apei"))
+               return;
+       if (acpi_find_table(ACPI_SIG_HEST) == 0)
+               return;
+       /* Only one APEI device can exist. */
+       if (devclass_get_device(apei_devclass, 0))
+               return;
+       /* Search for ACPI error device to be used. */
+       found = 0;
+       AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+           100, apei_find, NULL, NULL, (void *)&found);
+       if (found)
+               return;
+       /* If not found - create a fake one. */
+       child = BUS_ADD_CHILD(parent, 2, "apei", 0);
+       if (child == NULL)
+               printf("%s: can't add child\n", __func__);
+}
+
+static int
+apei_probe(device_t dev)
+{
+       int rv;
+
+       if (acpi_disabled("apei"))
+               return (ENXIO);
+       if (acpi_find_table(ACPI_SIG_HEST) == 0)
+               return (ENXIO);
+       if (acpi_get_handle(dev) != NULL)
+               rv = ACPI_ID_PROBE(device_get_parent(dev), dev, apei_ids, NULL);
+       else
+               rv = 0;
+       if (rv <= 0)
+               device_set_desc(dev, "Platform Error Interface");
+       return (rv);
+}
+
+static int
+apei_attach(device_t dev)
+{
+       struct apei_softc *sc = device_get_softc(dev);
+       struct apei_ge *ge;
+       ACPI_STATUS status;
+       int rid;
+
+       TAILQ_INIT(&sc->ges);
+
+       /* Search and parse HEST table. */
+       status = AcpiGetTable(ACPI_SIG_HEST, 0, (ACPI_TABLE_HEADER 
**)&sc->hest);
+       if (ACPI_FAILURE(status))
+               return (ENXIO);
+       hest_parse_table(sc);
+       AcpiPutTable((ACPI_TABLE_HEADER *)sc->hest);
+
+       rid = 0;
+       TAILQ_FOREACH(ge, &sc->ges, link) {
+               ge->res_rid = rid++;
+               acpi_bus_alloc_gas(dev, &ge->res_type, &ge->res_rid,
+                   &ge->v1.ErrorStatusAddress, &ge->res, 0);
+               if (ge->v1.Header.Type == ACPI_HEST_TYPE_GENERIC_ERROR_V2) {
+                       ge->res2_rid = rid++;
+                       acpi_bus_alloc_gas(dev, &ge->res2_type, &ge->res2_rid,
+                           &ge->v2.ReadAckRegister, &ge->res2, 0);
+               }
+               ge->buf = pmap_mapdev_attr(READ8(ge->res, 0),
+                   ge->v1.ErrorBlockLength, VM_MEMATTR_WRITE_COMBINING);
+               if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_POLLED) {
+                       callout_init(&ge->poll, 1);
+                       callout_reset(&ge->poll,
+                           ge->v1.Notify.PollInterval * hz / 1000,
+                           apei_callout_handler, ge);
+               } else if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_NMI) {
+                       ge->copybuf = malloc(ge->v1.ErrorBlockLength,
+                           M_DEVBUF, M_WAITOK | M_ZERO);
+                       swi_add(&clk_intr_event, "apei", apei_nmi_swi, ge,
+                           SWI_CLOCK, INTR_MPSAFE, &ge->swi_ih);
+                       apei_nmi_ge = ge;
+                       apei_nmi = apei_nmi_handler;
+               }
+       }
+
+       if (acpi_get_handle(dev) != NULL) {
+               AcpiInstallNotifyHandler(acpi_get_handle(dev),
+                   ACPI_DEVICE_NOTIFY, apei_notify_handler, dev);
+       }
+       return (0);
+}
+
+static int
+apei_detach(device_t dev)
+{
+       struct apei_softc *sc = device_get_softc(dev);
+       struct apei_ge *ge;
+
+       apei_nmi = NULL;
+       apei_nmi_ge = NULL;
+       if (acpi_get_handle(dev) != NULL) {
+               AcpiRemoveNotifyHandler(acpi_get_handle(dev),
+                   ACPI_DEVICE_NOTIFY, apei_notify_handler);
+       }
+
+       while ((ge = TAILQ_FIRST(&sc->ges)) != NULL) {
+               TAILQ_REMOVE(&sc->ges, ge, link);
+               bus_release_resource(dev, ge->res_type, ge->res_rid, ge->res);
+               if (ge->res2) {
+                       bus_release_resource(dev, ge->res2_type,
+                           ge->res2_rid, ge->res2);
+               }
+               if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_POLLED) {
+                       callout_drain(&ge->poll);
+               } else if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_NMI) {
+                       swi_remove(&ge->swi_ih);
+                       free(ge->copybuf, M_DEVBUF);
+               }
+               pmap_unmapdev((vm_offset_t)ge->buf, ge->v1.ErrorBlockLength);
+               free(ge, M_DEVBUF);
+       }
+       return (0);
+}
+
+static device_method_t apei_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_identify, apei_identify),
+       DEVMETHOD(device_probe, apei_probe),
+       DEVMETHOD(device_attach, apei_attach),
+       DEVMETHOD(device_detach, apei_detach),
+       DEVMETHOD_END
+};
+
+static driver_t        apei_driver = {
+       "apei",
+       apei_methods,
+       sizeof(struct apei_softc),
+};
+
+DRIVER_MODULE(apei, acpi, apei_driver, apei_devclass, 0, 0);
+MODULE_DEPEND(apei, acpi, 1, 1, 1);

Modified: head/sys/dev/pci/pci.c
==============================================================================
--- head/sys/dev/pci/pci.c      Mon Jul 27 19:05:53 2020        (r363623)
+++ head/sys/dev/pci/pci.c      Mon Jul 27 21:19:41 2020        (r363624)
@@ -6306,6 +6306,67 @@ pcie_get_max_completion_timeout(device_t dev)
        }
 }
 
+void
+pcie_apei_error(device_t dev, int sev, uint8_t *aerp)
+{
+       struct pci_devinfo *dinfo = device_get_ivars(dev);
+       const char *s;
+       int aer;
+       uint32_t r, r1;
+       uint16_t rs;
+
+       if (sev == PCIEM_STA_CORRECTABLE_ERROR)
+               s = "Correctable";
+       else if (sev == PCIEM_STA_NON_FATAL_ERROR)
+               s = "Uncorrectable (Non-Fatal)";
+       else
+               s = "Uncorrectable (Fatal)";
+       device_printf(dev, "%s PCIe error reported by APEI\n", s);
+       if (aerp) {
+               if (sev == PCIEM_STA_CORRECTABLE_ERROR) {
+                       r = le32dec(aerp + PCIR_AER_COR_STATUS);
+                       r1 = le32dec(aerp + PCIR_AER_COR_MASK);
+               } else {
+                       r = le32dec(aerp + PCIR_AER_UC_STATUS);
+                       r1 = le32dec(aerp + PCIR_AER_UC_MASK);
+               }
+               device_printf(dev, "status 0x%08x mask 0x%08x", r, r1);
+               if (sev != PCIEM_STA_CORRECTABLE_ERROR) {
+                       r = le32dec(aerp + PCIR_AER_UC_SEVERITY);
+                       rs = le16dec(aerp + PCIR_AER_CAP_CONTROL);
+                       printf(" severity 0x%08x first %d\n",
+                           r, rs & 0x1f);
+               } else
+                       printf("\n");
+       }
+
+       /* As kind of recovery just report and clear the error statuses. */
+       if (pci_find_extcap(dev, PCIZ_AER, &aer) == 0) {
+               r = pci_read_config(dev, aer + PCIR_AER_UC_STATUS, 4);
+               if (r != 0) {
+                       pci_write_config(dev, aer + PCIR_AER_UC_STATUS, r, 4);
+                       device_printf(dev, "Clearing UC AER errors 0x%08x\n", 
r);
+               }
+
+               r = pci_read_config(dev, aer + PCIR_AER_COR_STATUS, 4);
+               if (r != 0) {
+                       pci_write_config(dev, aer + PCIR_AER_COR_STATUS, r, 4);
+                       device_printf(dev, "Clearing COR AER errors 0x%08x\n", 
r);
+               }
+       }
+       if (dinfo->cfg.pcie.pcie_location != 0) {
+               rs = pci_read_config(dev, dinfo->cfg.pcie.pcie_location +
+                   PCIER_DEVICE_STA, 2);
+               if ((rs & (PCIEM_STA_CORRECTABLE_ERROR |
+                   PCIEM_STA_NON_FATAL_ERROR | PCIEM_STA_FATAL_ERROR |
+                   PCIEM_STA_UNSUPPORTED_REQ)) != 0) {
+                       pci_write_config(dev, dinfo->cfg.pcie.pcie_location +
+                           PCIER_DEVICE_STA, rs, 2);
+                       device_printf(dev, "Clearing PCIe errors 0x%04x\n", rs);
+               }
+       }
+}
+
 /*
  * Perform a Function Level Reset (FLR) on a device.
  *

Modified: head/sys/dev/pci/pcivar.h
==============================================================================
--- head/sys/dev/pci/pcivar.h   Mon Jul 27 19:05:53 2020        (r363623)
+++ head/sys/dev/pci/pcivar.h   Mon Jul 27 21:19:41 2020        (r363624)
@@ -686,6 +686,7 @@ uint32_t pcie_read_config(device_t dev, int reg, int w
 void   pcie_write_config(device_t dev, int reg, uint32_t value, int width);
 uint32_t pcie_adjust_config(device_t dev, int reg, uint32_t mask,
            uint32_t value, int width);
+void   pcie_apei_error(device_t dev, int sev, uint8_t *aer);
 bool   pcie_flr(device_t dev, u_int max_delay, bool force);
 int    pcie_get_max_completion_timeout(device_t dev);
 bool   pcie_wait_for_pending_transactions(device_t dev, u_int max_delay);

Modified: head/sys/x86/include/acpica_machdep.h
==============================================================================
--- head/sys/x86/include/acpica_machdep.h       Mon Jul 27 19:05:53 2020        
(r363623)
+++ head/sys/x86/include/acpica_machdep.h       Mon Jul 27 21:19:41 2020        
(r363624)
@@ -84,6 +84,7 @@ void  madt_parse_interrupt_values(void *entry,
            enum intr_trigger *trig, enum intr_polarity *pol);
 
 extern int madt_found_sci_override;
+extern int (*apei_nmi)(void);
 
 #endif /* _KERNEL */
 

Modified: head/sys/x86/x86/cpu_machdep.c
==============================================================================
--- head/sys/x86/x86/cpu_machdep.c      Mon Jul 27 19:05:53 2020        
(r363623)
+++ head/sys/x86/x86/cpu_machdep.c      Mon Jul 27 21:19:41 2020        
(r363624)
@@ -831,6 +831,7 @@ int nmi_is_broadcast = 1;
 SYSCTL_INT(_machdep, OID_AUTO, nmi_is_broadcast, CTLFLAG_RWTUN,
     &nmi_is_broadcast, 0,
     "Chipset NMI is broadcast");
+int (*apei_nmi)(void);
 
 void
 nmi_call_kdb(u_int cpu, u_int type, struct trapframe *frame)
@@ -845,6 +846,10 @@ nmi_call_kdb(u_int cpu, u_int type, struct trapframe *
                        panic("NMI indicates hardware failure");
        }
 #endif /* DEV_ISA */
+
+       /* ACPI Platform Error Interfaces callback. */
+       if (apei_nmi != NULL && (*apei_nmi)())
+               claimed = true;
 
        /*
         * NMIs can be useful for debugging.  They can be hooked up to a
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to