mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
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
This commit is contained in:
1 parent
e1f4a8cb86
commit
9313f6b014
5 files changed
+579
-120
No files matched your search
@@ -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
|
||||
|
||||
+39
-120
@@ -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);
|
||||
@@ -0,0 +1,435 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2020 Alexander Motin <mav@FreeBSD.org>
|
||||
* 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);
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2020 Alexander Motin <mav@FreeBSD.org>
|
||||
* 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__ */
|
||||
Reference in new issue
Block a user