#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>
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;
if (device_find_child(parent, "apei", DEVICE_UNIT_ANY) != NULL)
return;
found = 0;
AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
100, apei_find, NULL, NULL, (void *)&found);
if (found)
return;
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);
prev = NULL;
TAILQ_FOREACH(reg, list, link) {
if (start > rman_get_end(reg->res) ||
end < rman_get_start(reg->res)) {
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;
}
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);
}
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[] = {
DEVMETHOD(device_identify, apei_identify),
DEVMETHOD(device_probe, apei_probe),
DEVMETHOD(device_attach, apei_attach),
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),
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);