The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=9313f6b01485ad9a0b7cc59b459f5714533587c3
commit 9313f6b01485ad9a0b7cc59b459f5714533587c3 Author: John Baldwin <[email protected]> AuthorDate: 2026-07-07 18:13:51 +0000 Commit: John Baldwin <[email protected]> CommitDate: 2026-07-07 18:13:51 +0000 acpi: Add a pseudo-bus for APEI devices to manage resources Different APEI tables can reuse the same registers (and sometimes different views of the same register, e.g. 32- vs 64-bit mappings of the same register). To enable this sharing, apei0 now acts as a bus device managing a pool of allocated resources and handing out mappings to child devices which handle individual tables. Most of the previous apei(4) driver has been moved into a new hest0 device that is a child of apei0. Reviewed by: gallatin Sponsored by: Netflix Differential Revision: https://reviews.freebsd.org/D58024 --- sys/conf/files | 2 + sys/dev/acpica/acpi_apei.c | 159 ++++----------- sys/dev/acpica/acpi_apei_bus.c | 435 +++++++++++++++++++++++++++++++++++++++++ sys/dev/acpica/apei_if.m | 49 +++++ sys/dev/acpica/apeivar.h | 54 +++++ 5 files changed, 579 insertions(+), 120 deletions(-) diff --git a/sys/conf/files b/sys/conf/files index cc18ecbb9301..3635e6474708 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -759,6 +759,7 @@ 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_apei_bus.c optional acpi dev/acpica/acpi_battery.c optional acpi dev/acpica/acpi_button.c optional acpi dev/acpica/acpi_cmbat.c optional acpi @@ -779,6 +780,7 @@ dev/acpica/acpi_throttle.c optional acpi dev/acpica/acpi_video.c optional acpi_video acpi dev/acpica/acpi_dock.c optional acpi_dock acpi dev/acpica/acpi_spmc.c optional acpi +dev/acpica/apei_if.m optional acpi dev/adlink/adlink.c optional adlink dev/ae/if_ae.c optional ae pci dev/age/if_age.c optional age pci diff --git a/sys/dev/acpica/acpi_apei.c b/sys/dev/acpica/acpi_apei.c index 925558d585bf..09073207c06e 100644 --- a/sys/dev/acpica/acpi_apei.c +++ b/sys/dev/acpica/acpi_apei.c @@ -48,6 +48,7 @@ #include <contrib/dev/acpica/include/actables.h> #include <dev/acpica/acpivar.h> +#include <dev/acpica/apeivar.h> #include <dev/pci/pcireg.h> #include <dev/pci/pcivar.h> @@ -56,12 +57,8 @@ struct apei_ge { 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; + struct resource_map *res; + struct resource_map *res2; uint8_t *buf, *copybuf; TAILQ_ENTRY(apei_ge) link; TAILQ_ENTRY(apei_ge) nlink; @@ -85,7 +82,7 @@ struct apei_pges { TAILQ_HEAD(, apei_ge) ges; }; -struct apei_softc { +struct hest_softc { TAILQ_HEAD(, apei_ge) ges; struct apei_nges nges; struct apei_iges iges; @@ -128,26 +125,6 @@ struct apei_pcie_error { 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 - #define GED_SIZE(ged) ((ged)->Revision >= 0x300 ? \ sizeof(ACPI_HEST_GENERIC_DATA_V300) : sizeof(ACPI_HEST_GENERIC_DATA)) #define GED_DATA(ged) ((uint8_t *)(ged) + GED_SIZE(ged)) @@ -431,10 +408,10 @@ apei_ge_handler(struct apei_ge *ge, bool copy) ges->BlockStatus = 0; if (!copy && ge->v1.Header.Type == ACPI_HEST_TYPE_GENERIC_ERROR_V2 && ge->res2) { - uint64_t val = READ8(ge->res2, 0); + uint64_t val = bus_read_8(ge->res2, 0); val &= ge->v2.ReadAckPreserve; val |= ge->v2.ReadAckWrite; - WRITE8(ge->res2, 0, val); + bus_write_8(ge->res2, 0, val); } /* If ACPI told the error is fatal -- make it so. */ @@ -484,10 +461,10 @@ apei_nmi_handler(void) ges->BlockStatus = 0; if (ge->v1.Header.Type == ACPI_HEST_TYPE_GENERIC_ERROR_V2 && ge->res2) { - uint64_t val = READ8(ge->res2, 0); + uint64_t val = bus_read_8(ge->res2, 0); val &= ge->v2.ReadAckPreserve; val |= ge->v2.ReadAckWrite; - WRITE8(ge->res2, 0, val); + bus_write_8(ge->res2, 0, val); } handled = 1; } @@ -514,7 +491,7 @@ 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 hest_softc *sc = device_get_softc(dev); struct apei_ge *ge; TAILQ_FOREACH(ge, &sc->iges.ges, nlink) @@ -522,7 +499,7 @@ apei_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) } static int -hest_parse_structure(struct apei_softc *sc, void *addr, int remaining) +hest_parse_structure(struct hest_softc *sc, void *addr, int remaining) { ACPI_HEST_HEADER *hdr = addr; struct apei_ge *ge; @@ -582,7 +559,7 @@ hest_parse_structure(struct apei_softc *sc, void *addr, int remaining) } static void -hest_parse_table(ACPI_TABLE_HEST *hest, struct apei_softc *sc) +hest_parse_table(ACPI_TABLE_HEST *hest, struct hest_softc *sc) { char *cp; int remaining, consumed; @@ -598,95 +575,43 @@ hest_parse_table(ACPI_TABLE_HEST *hest, struct apei_softc *sc) } } -static char *apei_ids[] = { "PNP0C33", NULL }; - -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) +hest_identify(driver_t *driver, device_t parent) { device_t child; - int found; ACPI_TABLE_HEADER *hest; ACPI_STATUS status; - if (acpi_disabled("apei")) - return; - /* Without HEST table we have nothing to do. */ status = AcpiGetTable(ACPI_SIG_HEST, 0, &hest); if (ACPI_FAILURE(status)) return; AcpiPutTable(hest); - /* Only one APEI device can exist. */ - if (devclass_get_device(devclass_find("apei"), 0)) + if (device_find_child(parent, "hest", DEVICE_UNIT_ANY) != NULL) 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); + child = BUS_ADD_CHILD(parent, 0, "hest", DEVICE_UNIT_ANY); if (child == NULL) printf("%s: can't add child\n", __func__); } static int -apei_probe(device_t dev) +hest_probe(device_t dev) { - ACPI_TABLE_HEADER *hest; - ACPI_STATUS status; - int rv; - - if (acpi_disabled("apei")) - return (ENXIO); - - if (acpi_get_handle(dev) != NULL) { - rv = ACPI_ID_PROBE(device_get_parent(dev), dev, apei_ids, NULL); - if (rv > 0) - return (rv); - } else - rv = 0; - - /* Without HEST table we have nothing to do. */ - status = AcpiGetTable(ACPI_SIG_HEST, 0, &hest); - if (ACPI_FAILURE(status)) - return (ENXIO); - AcpiPutTable(hest); - - device_set_desc(dev, "ACPI Platform Error Interface"); - return (rv); + device_set_desc(dev, "APEI Hardware Errors"); + return (BUS_PROBE_NOWILDCARD); } static int -apei_attach(device_t dev) +hest_attach(device_t dev) { - struct apei_softc *sc = device_get_softc(dev); + struct hest_softc *sc = device_get_softc(dev); ACPI_TABLE_HEADER *hest; struct acpi_softc *acpi_sc; struct apei_pges *pges; struct apei_ge *ge; ACPI_STATUS status; - int rid; if (!apei_sysctl_tree) { /* Install hw.acpi.apei sysctl tree */ @@ -717,21 +642,17 @@ apei_attach(device_t dev) hest_parse_table((ACPI_TABLE_HEST *)hest, sc); AcpiPutTable(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); + ge->res = apei_map_register(dev, &ge->v1.ErrorStatusAddress); if (ge->res) { - ge->buf = pmap_mapdev_attr(READ8(ge->res, 0), + ge->buf = pmap_mapdev_attr(bus_read_8(ge->res, 0), ge->v1.ErrorBlockLength, VM_MEMATTR_WRITE_COMBINING); } else { device_printf(dev, "Can't allocate status resource.\n"); } 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, RF_SHAREABLE); + ge->res2 = apei_map_register(dev, + &ge->v2.ReadAckRegister); if (ge->res2 == NULL) device_printf(dev, "Can't allocate ack resource.\n"); } @@ -766,9 +687,9 @@ apei_attach(device_t dev) } static int -apei_detach(device_t dev) +hest_detach(device_t dev) { - struct apei_softc *sc = device_get_softc(dev); + struct hest_softc *sc = device_get_softc(dev); struct apei_ge *ge; apei_nmi = NULL; @@ -787,12 +708,10 @@ apei_detach(device_t dev) while ((ge = TAILQ_FIRST(&sc->ges)) != NULL) { TAILQ_REMOVE(&sc->ges, ge, link); if (ge->res) { - bus_release_resource(dev, ge->res_type, - ge->res_rid, ge->res); + apei_unmap_register(dev, ge->res); } if (ge->res2) { - bus_release_resource(dev, ge->res2_type, - ge->res2_rid, ge->res2); + apei_unmap_register(dev, ge->res2); } if (ge->v1.Notify.Type == ACPI_HEST_NOTIFY_POLLED) { TAILQ_REMOVE(&sc->pges[PGE_ID(ge)].ges, ge, nlink); @@ -812,23 +731,23 @@ apei_detach(device_t dev) return (0); } -static device_method_t apei_methods[] = { +static device_method_t hest_methods[] = { /* Device interface */ - DEVMETHOD(device_identify, apei_identify), - DEVMETHOD(device_probe, apei_probe), - DEVMETHOD(device_attach, apei_attach), - DEVMETHOD(device_detach, apei_detach), + DEVMETHOD(device_identify, hest_identify), + DEVMETHOD(device_probe, hest_probe), + DEVMETHOD(device_attach, hest_attach), + DEVMETHOD(device_detach, hest_detach), DEVMETHOD_END }; -static driver_t apei_driver = { - "apei", - apei_methods, - sizeof(struct apei_softc), +static driver_t hest_driver = { + "hest", + hest_methods, + sizeof(struct hest_softc), }; static int -apei_modevent(struct module *mod __unused, int evt, void *cookie __unused) +hest_modevent(struct module *mod __unused, int evt, void *cookie __unused) { int err = 0; @@ -845,5 +764,5 @@ apei_modevent(struct module *mod __unused, int evt, void *cookie __unused) return (err); } -DRIVER_MODULE(apei, acpi, apei_driver, apei_modevent, 0); -MODULE_DEPEND(apei, acpi, 1, 1, 1); +DRIVER_MODULE(hest, apei, hest_driver, hest_modevent, 0); +MODULE_DEPEND(hest, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/acpi_apei_bus.c b/sys/dev/acpica/acpi_apei_bus.c new file mode 100644 index 000000000000..c6c3a4f11a12 --- /dev/null +++ b/sys/dev/acpica/acpi_apei_bus.c @@ -0,0 +1,435 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Alexander Motin <[email protected]> + * Copyright (c) 2025-2026 Netflix, Inc. + * + * 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/types.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/rman.h> + +#include <contrib/dev/acpica/include/acpi.h> + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/apeivar.h> + +/* + * Different APEI tables can reuse the same registers (and sometimes + * different views of the same register, e.g. 32- vs 64-bit mappings + * of the same register). To enable this sharing, apei0 acts as a bus + * device managing a pool of allocated resources and hands out + * mappings to child devices. Child devices handle individual tables. + */ + +struct apei_register { + struct resource *res; + TAILQ_ENTRY(apei_register) link; +}; + +struct apei_register_mapping { + struct resource_map map; + struct apei_register *reg; + TAILQ_ENTRY(apei_register_mapping) link; +}; + +struct apei_ivars { + TAILQ_HEAD(, apei_register_mapping) mappings; +}; + +TAILQ_HEAD(apei_register_list, apei_register); + +struct apei_softc { + device_t dev; + struct apei_register_list mem; + struct apei_register_list io; +}; + +static char *apei_ids[] = { "PNP0C33", NULL }; + +static MALLOC_DEFINE(M_APEI, "apei", "ACPI platform error interface"); + +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; + + /* Only one APEI device can exist. */ + if (device_find_child(parent, "apei", DEVICE_UNIT_ANY) != NULL) + 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_get_handle(dev) != NULL) { + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, apei_ids, NULL); + if (rv > 0) + return (rv); + } + + device_set_desc(dev, "ACPI Platform Error Interface"); + return (0); +} + +static int +apei_attach(device_t dev) +{ + struct apei_softc *sc = device_get_softc(dev); + + sc->dev = dev; + TAILQ_INIT(&sc->mem); + TAILQ_INIT(&sc->io); + bus_identify_children(dev); + bus_attach_children(dev); + return (0); +} + +static device_t +apei_add_child(device_t dev, u_int order, const char *name, int unit) +{ + struct apei_ivars *ai; + device_t child; + + ai = malloc(sizeof(*ai), M_APEI, M_WAITOK); + TAILQ_INIT(&ai->mappings); + child = device_add_child_ordered(dev, order, name, unit); + if (child != NULL) + device_set_ivars(child, ai); + else + free(ai, M_APEI); + return (child); +} + +static void +apei_child_deleted(device_t dev, device_t child) +{ + struct apei_ivars *ai = device_get_ivars(child); + + MPASS(TAILQ_EMPTY(&ai->mappings)); + free(ai, M_APEI); +} + +static void +apei_child_detached(device_t dev, device_t child) +{ + struct apei_ivars *ai = device_get_ivars(child); + + while (!TAILQ_EMPTY(&ai->mappings)) { + struct apei_register_mapping *m = TAILQ_FIRST(&ai->mappings); + + device_printf(dev, "child leaked mapping of %s %#jx-%#jx\n", + rman_get_type(m->reg->res) == SYS_RES_MEMORY ? "iomem" : + "port", rman_get_start(m->reg->res), + rman_get_end(m->reg->res)); + + (void) apei_unmap_register(child, &m->map); + } +} + +static int +apei_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) +{ + switch (index) { + case ACPI_IVAR_HANDLE: + *(ACPI_HANDLE *)result = acpi_get_handle(dev); + break; + default: + return (ENOENT); + } + + return (0); +} + +static struct apei_register * +apei_alloc_register(struct apei_softc *sc, int type, rman_res_t start, + rman_res_t count) +{ + struct apei_register_list *list; + struct apei_register *reg, *prev; + rman_res_t end; + const char *descr; + int error, next_rid; + + switch (type) { + case SYS_RES_MEMORY: + list = &sc->mem; + descr = "iomem"; + break; + case SYS_RES_IOPORT: + list = &sc->io; + descr = "port"; + break; + default: + return (NULL); + } + + end = start + (count - 1); + + /* First, look for an existing resource. */ + prev = NULL; + TAILQ_FOREACH(reg, list, link) { + /* Does the existing register overlap with this request? */ + if (start > rman_get_end(reg->res) || + end < rman_get_start(reg->res)) { + + /* prev will be the location of the first free rid */ + if (prev == NULL) + next_rid = 0; + else + next_rid = rman_get_rid(prev->res) + 1; + if (rman_get_rid(reg->res) == next_rid) + prev = reg; + continue; + } + + /* Do we need to extend the existing register? */ + if (start < rman_get_start(reg->res) || + end > rman_get_end(reg->res)) { + rman_res_t new_end, new_start; + + new_start = rman_get_start(reg->res); + if (start < new_start) + new_start = start; + new_end = rman_get_end(reg->res); + if (end > new_end) + new_end = end; + error = bus_adjust_resource(sc->dev, reg->res, + new_start, new_end); + if (error != 0) { + if (bootverbose) + device_printf(sc->dev, + "failed to grow %s %#jx-%#jx for %#jx-%#jx: %d\n", + descr, rman_get_start(reg->res), + rman_get_end(reg->res), start, end, + error); + return (NULL); + } + } + + return (reg); + } + + /* Allocate a new resource. */ + if (prev == NULL) + next_rid = 0; + else + next_rid = rman_get_rid(prev->res) + 1; + error = bus_set_resource(sc->dev, type, next_rid, start, count); + if (error != 0) { + if (bootverbose) + device_printf(sc->dev, + "failed to add %s %#jx-%#jx: %d\n", descr, start, + end, error); + return (NULL); + } + + reg = malloc(sizeof(*reg), M_APEI, M_WAITOK); + reg->res = bus_alloc_resource_any(sc->dev, type, next_rid, RF_ACTIVE | + RF_UNMAPPED); + if (reg->res == NULL) { + if (bootverbose) + device_printf(sc->dev, + "failed to allocate %s %#jx-%#jx\n", descr, start, + end); + free(reg, M_APEI); + bus_delete_resource(sc->dev, type, next_rid); + return (NULL); + } + + if (prev == NULL) + TAILQ_INSERT_HEAD(list, reg, link); + else + TAILQ_INSERT_AFTER(list, prev, reg, link); + return (reg); +} + +static struct resource_map * +apei_map_register_method(device_t dev, device_t child, int type, + rman_res_t start, rman_res_t count) +{ + struct apei_softc *sc = device_get_softc(dev); + struct apei_ivars *ai = device_get_ivars(child); + struct apei_register_mapping *m; + struct apei_register *reg; + struct resource_map_request req; + int error; + + reg = apei_alloc_register(sc, type, start, count); + if (reg == NULL) + return (NULL); + + m = malloc(sizeof(*m), M_APEI, M_WAITOK | M_ZERO); + m->reg = reg; + resource_init_map_request(&req); + req.offset = start - rman_get_start(reg->res); + req.length = count; + error = bus_map_resource(dev, reg->res, &req, &m->map); + if (error != 0) { + if (bootverbose) + device_printf(dev, "failed to map %s %#jx-%#jx: %d\n", + type == SYS_RES_MEMORY ? "iomem" : "port", + start, + start + (count - 1), error); + free(m, M_APEI); + return (NULL); + } + TAILQ_INSERT_TAIL(&ai->mappings, m, link); + return (&m->map); +} + +static int +apei_unmap_register_method(device_t dev, device_t child, + struct resource_map *map) +{ + struct apei_ivars *ai = device_get_ivars(child); + struct apei_register_mapping *m; + int error; + + if (map == NULL) + return (0); + + TAILQ_FOREACH(m, &ai->mappings, link) { + if (&m->map == map) + break; + } + + if (m == NULL) + return (EINVAL); + + TAILQ_REMOVE(&ai->mappings, m, link); + error = bus_unmap_resource(dev, m->reg->res, &m->map); + if (error != 0 && bootverbose) + device_printf(dev, "failed to unmap %s %#jx-%#jx: %d\n", + rman_get_type(m->reg->res) == SYS_RES_MEMORY ? "iomem" : + "port", rman_get_start(m->reg->res), + rman_get_end(m->reg->res), error); + free(m, M_APEI); + return (0); +} + +struct resource_map * +apei_map_register(device_t dev, ACPI_GENERIC_ADDRESS *gas) +{ + int type; + + if (gas->Address == 0 || gas->BitWidth == 0 || gas->BitWidth % 8 != 0) + return (NULL); + + switch (gas->SpaceId) { + case ACPI_ADR_SPACE_SYSTEM_MEMORY: + type = SYS_RES_MEMORY; + break; + case ACPI_ADR_SPACE_SYSTEM_IO: + type = SYS_RES_IOPORT; + break; + default: + return (NULL); + } + + return (APEI_MAP_REGISTER(device_get_parent(dev), dev, type, + gas->Address, gas->BitWidth / 8)); +} + +struct resource_map * +apei_map_memory(device_t dev, rman_res_t start, rman_res_t count) +{ + return (APEI_MAP_REGISTER(device_get_parent(dev), dev, SYS_RES_MEMORY, + start, count)); +} + +int +apei_unmap_register(device_t dev, struct resource_map *map) +{ + return (APEI_UNMAP_REGISTER(device_get_parent(dev), dev, map)); +} + +static device_method_t apei_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, apei_identify), + DEVMETHOD(device_probe, apei_probe), + DEVMETHOD(device_attach, apei_attach), + + /* Bus interface */ + DEVMETHOD(bus_add_child, apei_add_child), + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_child_deleted, apei_child_deleted), + DEVMETHOD(bus_child_detached, apei_child_detached), + DEVMETHOD(bus_read_ivar, apei_read_ivar), + + /* APEI interface */ + DEVMETHOD(apei_map_register, apei_map_register_method), + DEVMETHOD(apei_unmap_register, apei_unmap_register_method), + + DEVMETHOD_END +}; + +static driver_t apei_driver = { + "apei", + apei_methods, + sizeof(struct apei_softc), +}; + +DRIVER_MODULE(apei, acpi, apei_driver, NULL, NULL); +MODULE_DEPEND(apei, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/apei_if.m b/sys/dev/acpica/apei_if.m new file mode 100644 index 000000000000..1975fbde962f --- /dev/null +++ b/sys/dev/acpica/apei_if.m @@ -0,0 +1,49 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Netflix, Inc. +# Written by: John Baldwin <[email protected]> +# + +#include <sys/bus.h> + +#include <contrib/dev/acpica/include/acpi.h> + +/** + * @defgroup APEI apei - KObj methods for ACPI platform error interface drivers + * @brief A pair of methods to allocate/deallocate registers for APEI + * @{ + */ +INTERFACE apei; + +/** + * @brief Allocate a mapping for a resource. + * + * @param _bus parent device + * @param _dev device requesting the mapping + * @param _type resource type + * @param _start start address + * @param _count length + * + * @returns resource mapping on success, or @c NULL on failure + */ +METHOD struct resource_map * map_register { + device_t _bus; + device_t _child; + int _type; + rman_res_t _start; + rman_res_t _count; +}; + +/** + * @brief Deallocate a mapping for a resource. + * + * @param _bus parent device + * @param _dev device releasing the mapping + * @param _map resource map + */ +METHOD int unmap_register { + device_t _bus; + device_t _child; + struct resource_map *_map; +}; diff --git a/sys/dev/acpica/apeivar.h b/sys/dev/acpica/apeivar.h new file mode 100644 index 000000000000..7820641aef1f --- /dev/null +++ b/sys/dev/acpica/apeivar.h @@ -0,0 +1,54 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Alexander Motin <[email protected]> + * Copyright (c) 2025 Netflix, Inc. + * + * 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. + */ + +#ifndef __APEIVAR_H__ +#define __APEIVAR_H__ + +#include "apei_if.h" + +struct resource_map *apei_map_register(device_t dev, ACPI_GENERIC_ADDRESS *gas); +struct resource_map *apei_map_memory(device_t dev, rman_res_t start, + rman_res_t len); +int apei_unmap_register(device_t dev, struct resource_map *map); + +#ifdef __i386__ +static __inline uint64_t +bus_read_8(struct resource_map *res, bus_size_t offset) +{ + return (bus_read_4(res, offset) | + ((uint64_t)bus_read_4(res, offset + 4)) << 32); +} +static __inline void +bus_write_8(struct resource_map *res, bus_size_t offset, uint64_t val) +{ + bus_write_4(res, offset, val); + bus_write_4(res, offset + 4, val >> 32); +} +#endif + +#endif /* !__APEIVAR_H__ */
