#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <machine/intr.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <contrib/dev/acpica/include/actables.h>
#include <dev/acpica/acpivar.h>
static u_int acpi_its_xref = ACPI_MSI_XREF;
struct iort_map_entry {
u_int base;
u_int end;
u_int outbase;
u_int flags;
u_int out_node_offset;
struct iort_node *out_node;
};
struct iort_its_entry {
u_int its_id;
u_int xref;
int pxm;
};
struct iort_named_component
{
UINT32 NodeFlags;
UINT64 MemoryProperties;
UINT8 MemoryAddressLimit;
char DeviceName[32];
};
struct iort_node {
TAILQ_ENTRY(iort_node) next;
enum AcpiIortNodeType type;
u_int node_offset;
u_int nentries;
u_int usecount;
u_int revision;
union {
struct iort_map_entry *mappings;
struct iort_its_entry *its;
} entries;
union {
ACPI_IORT_ROOT_COMPLEX pci_rc;
ACPI_IORT_SMMU smmu;
ACPI_IORT_SMMU_V3 smmu_v3;
struct iort_named_component named_comp;
} data;
};
static TAILQ_HEAD(, iort_node) pci_nodes = TAILQ_HEAD_INITIALIZER(pci_nodes);
static TAILQ_HEAD(, iort_node) smmu_nodes = TAILQ_HEAD_INITIALIZER(smmu_nodes);
static TAILQ_HEAD(, iort_node) its_groups = TAILQ_HEAD_INITIALIZER(its_groups);
static TAILQ_HEAD(, iort_node) named_nodes = TAILQ_HEAD_INITIALIZER(named_nodes);
static int
iort_entry_get_id_mapping_index(struct iort_node *node)
{
switch(node->type) {
case ACPI_IORT_NODE_SMMU_V3:
if (node->revision < 1)
return (-1);
if (node->data.smmu_v3.EventGsiv != 0 &&
node->data.smmu_v3.PriGsiv != 0 &&
node->data.smmu_v3.GerrGsiv != 0 &&
node->data.smmu_v3.SyncGsiv != 0)
return (-1);
if (node->data.smmu_v3.IdMappingIndex >= node->nentries)
return (-1);
return (node->data.smmu_v3.IdMappingIndex);
case ACPI_IORT_NODE_PMCG:
return (0);
default:
break;
}
return (-1);
}
static struct iort_node *
iort_entry_lookup(struct iort_node *node, u_int id, u_int *outid)
{
struct iort_map_entry *entry;
int i, id_map;
id_map = iort_entry_get_id_mapping_index(node);
entry = node->entries.mappings;
for (i = 0; i < node->nentries; i++, entry++) {
if (i == id_map)
continue;
if (entry->base <= id && id <= entry->end)
break;
}
if (i == node->nentries)
return (NULL);
if ((entry->flags & ACPI_IORT_ID_SINGLE_MAPPING) == 0)
*outid = entry->outbase + (id - entry->base);
else
*outid = entry->outbase;
return (entry->out_node);
}
static struct iort_node *
iort_smmu_trymap(struct iort_node *node, u_int outtype, u_int *outid)
{
if (!node)
return (NULL);
if (outtype == ACPI_IORT_NODE_ITS_GROUP &&
(node->type == ACPI_IORT_NODE_SMMU_V3 ||
node->type == ACPI_IORT_NODE_SMMU)) {
node = iort_entry_lookup(node, *outid, outid);
if (node == NULL)
return (NULL);
}
KASSERT(node->type == outtype, ("mapping fail"));
return (node);
}
static struct iort_node *
iort_pci_rc_map(u_int seg, u_int rid, u_int outtype, u_int *outid)
{
struct iort_node *node, *out_node;
u_int nxtid;
out_node = NULL;
TAILQ_FOREACH(node, &pci_nodes, next) {
if (node->data.pci_rc.PciSegmentNumber != seg)
continue;
out_node = iort_entry_lookup(node, rid, &nxtid);
if (out_node != NULL)
break;
}
out_node = iort_smmu_trymap(out_node, outtype, &nxtid);
if (out_node)
*outid = nxtid;
return (out_node);
}
static struct iort_node *
iort_named_comp_map(const char *devname, u_int rid, u_int outtype, u_int *outid)
{
struct iort_node *node, *out_node;
u_int nxtid;
out_node = NULL;
TAILQ_FOREACH(node, &named_nodes, next) {
if (strstr(node->data.named_comp.DeviceName, devname) == NULL)
continue;
out_node = iort_entry_lookup(node, rid, &nxtid);
if (out_node != NULL)
break;
}
out_node = iort_smmu_trymap(out_node, outtype, &nxtid);
if (out_node)
*outid = nxtid;
return (out_node);
}
#ifdef notyet
int
acpi_iort_map_smmu(u_int seg, u_int devid, void **smmu, u_int *sid)
{
return (ENXIO);
}
#endif
static void
iort_copy_data(struct iort_node *node, ACPI_IORT_NODE *node_entry)
{
ACPI_IORT_ID_MAPPING *map_entry;
struct iort_map_entry *mapping;
int i;
map_entry = ACPI_ADD_PTR(ACPI_IORT_ID_MAPPING, node_entry,
node_entry->MappingOffset);
node->nentries = node_entry->MappingCount;
node->usecount = 0;
mapping = malloc(sizeof(*mapping) * node->nentries, M_DEVBUF,
M_WAITOK | M_ZERO);
node->entries.mappings = mapping;
for (i = 0; i < node->nentries; i++, mapping++, map_entry++) {
mapping->base = map_entry->InputBase;
mapping->end = map_entry->InputBase + map_entry->IdCount;
mapping->outbase = map_entry->OutputBase;
mapping->out_node_offset = map_entry->OutputReference;
mapping->flags = map_entry->Flags;
mapping->out_node = NULL;
}
}
static void
iort_copy_its(struct iort_node *node, ACPI_IORT_NODE *node_entry)
{
struct iort_its_entry *its;
ACPI_IORT_ITS_GROUP *itsg_entry;
UINT32 *id;
int i;
itsg_entry = (ACPI_IORT_ITS_GROUP *)node_entry->NodeData;
node->nentries = itsg_entry->ItsCount;
node->usecount = 0;
its = malloc(sizeof(*its) * node->nentries, M_DEVBUF, M_WAITOK | M_ZERO);
node->entries.its = its;
id = &itsg_entry->Identifiers[0];
for (i = 0; i < node->nentries; i++, its++, id++) {
its->its_id = *id;
its->pxm = -1;
its->xref = 0;
}
}
static void
iort_add_nodes(ACPI_IORT_NODE *node_entry, u_int node_offset)
{
ACPI_IORT_ROOT_COMPLEX *pci_rc;
ACPI_IORT_SMMU *smmu;
ACPI_IORT_SMMU_V3 *smmu_v3;
ACPI_IORT_NAMED_COMPONENT *named_comp;
struct iort_node *node;
node = malloc(sizeof(*node), M_DEVBUF, M_WAITOK | M_ZERO);
node->type = node_entry->Type;
node->node_offset = node_offset;
node->revision = node_entry->Revision;
switch(node_entry->Type) {
case ACPI_IORT_NODE_PCI_ROOT_COMPLEX:
pci_rc = (ACPI_IORT_ROOT_COMPLEX *)node_entry->NodeData;
memcpy(&node->data.pci_rc, pci_rc, sizeof(*pci_rc));
iort_copy_data(node, node_entry);
TAILQ_INSERT_TAIL(&pci_nodes, node, next);
break;
case ACPI_IORT_NODE_SMMU:
smmu = (ACPI_IORT_SMMU *)node_entry->NodeData;
memcpy(&node->data.smmu, smmu, sizeof(*smmu));
iort_copy_data(node, node_entry);
TAILQ_INSERT_TAIL(&smmu_nodes, node, next);
break;
case ACPI_IORT_NODE_SMMU_V3:
smmu_v3 = (ACPI_IORT_SMMU_V3 *)node_entry->NodeData;
memcpy(&node->data.smmu_v3, smmu_v3, sizeof(*smmu_v3));
iort_copy_data(node, node_entry);
TAILQ_INSERT_TAIL(&smmu_nodes, node, next);
break;
case ACPI_IORT_NODE_ITS_GROUP:
iort_copy_its(node, node_entry);
TAILQ_INSERT_TAIL(&its_groups, node, next);
break;
case ACPI_IORT_NODE_NAMED_COMPONENT:
named_comp = (ACPI_IORT_NAMED_COMPONENT *)node_entry->NodeData;
memcpy(&node->data.named_comp, named_comp, sizeof(*named_comp));
strncpy(node->data.named_comp.DeviceName,
named_comp->DeviceName,
sizeof(node->data.named_comp.DeviceName));
node->data.named_comp.DeviceName[31] = 0;
iort_copy_data(node, node_entry);
TAILQ_INSERT_TAIL(&named_nodes, node, next);
break;
default:
printf("ACPI: IORT: Dropping unhandled type %u\n",
node_entry->Type);
free(node, M_DEVBUF);
break;
}
}
static void
iort_resolve_node(struct iort_map_entry *entry, int check_smmu)
{
struct iort_node *node, *np;
node = NULL;
if (check_smmu) {
TAILQ_FOREACH(np, &smmu_nodes, next) {
if (entry->out_node_offset == np->node_offset) {
node = np;
break;
}
}
}
if (node == NULL) {
TAILQ_FOREACH(np, &its_groups, next) {
if (entry->out_node_offset == np->node_offset) {
node = np;
break;
}
}
}
if (node != NULL) {
node->usecount++;
entry->out_node = node;
} else {
printf("ACPI: IORT: Firmware Bug: no mapping for node %u\n",
entry->out_node_offset);
}
}
static void
iort_post_process_mappings(void)
{
struct iort_node *node;
int i;
TAILQ_FOREACH(node, &pci_nodes, next)
for (i = 0; i < node->nentries; i++)
iort_resolve_node(&node->entries.mappings[i], TRUE);
TAILQ_FOREACH(node, &smmu_nodes, next)
for (i = 0; i < node->nentries; i++)
iort_resolve_node(&node->entries.mappings[i], FALSE);
TAILQ_FOREACH(node, &named_nodes, next)
for (i = 0; i < node->nentries; i++)
iort_resolve_node(&node->entries.mappings[i], TRUE);
}
static void
madt_resolve_its_xref(ACPI_SUBTABLE_HEADER *entry, void *arg)
{
ACPI_MADT_GENERIC_TRANSLATOR *gict;
struct iort_node *its_node;
struct iort_its_entry *its_entry;
u_int xref;
int i, matches;
if (entry->Type != ACPI_MADT_TYPE_GENERIC_TRANSLATOR)
return;
gict = (ACPI_MADT_GENERIC_TRANSLATOR *)entry;
matches = 0;
xref = acpi_its_xref++;
TAILQ_FOREACH(its_node, &its_groups, next) {
its_entry = its_node->entries.its;
for (i = 0; i < its_node->nentries; i++, its_entry++) {
if (its_entry->its_id == gict->TranslationId) {
its_entry->xref = xref;
matches++;
}
}
}
if (matches == 0)
printf("ACPI: IORT: Unused ITS block, ID %u\n",
gict->TranslationId);
}
static void
srat_resolve_its_pxm(ACPI_SUBTABLE_HEADER *entry, void *arg)
{
ACPI_SRAT_GIC_ITS_AFFINITY *gicits;
struct iort_node *its_node;
struct iort_its_entry *its_entry;
int *map_counts;
int i, matches, dom;
if (entry->Type != ACPI_SRAT_TYPE_GIC_ITS_AFFINITY)
return;
matches = 0;
map_counts = arg;
gicits = (ACPI_SRAT_GIC_ITS_AFFINITY *)entry;
dom = acpi_map_pxm_to_vm_domainid(gicits->ProximityDomain);
#if MAXMEMDOM > 1
if (dom == -1)
printf("Firmware Error: Proximity Domain %d could not be"
" mapped for GIC ITS ID %d!\n",
gicits->ProximityDomain, gicits->ItsId);
#endif
i = ++map_counts[dom + 1];
if (i > 1) {
#ifdef NUMA
if (dom != -1)
printf("ERROR: Multiple Proximity Domains map to the"
" same NUMA domain %d!\n", dom);
#else
printf("WARNING: multiple Proximity Domains in SRAT but NUMA"
" NOT enabled!\n");
#endif
}
TAILQ_FOREACH(its_node, &its_groups, next) {
its_entry = its_node->entries.its;
for (i = 0; i < its_node->nentries; i++, its_entry++) {
if (its_entry->its_id == gicits->ItsId) {
its_entry->pxm = dom;
matches++;
}
}
}
if (matches == 0)
printf("ACPI: IORT: ITS block %u in SRAT not found in IORT!\n",
gicits->ItsId);
}
static int
iort_post_process_its(void)
{
ACPI_TABLE_MADT *madt;
ACPI_TABLE_SRAT *srat;
vm_paddr_t madt_pa, srat_pa;
int map_counts[MAXMEMDOM + 1] = { 0 };
madt_pa = acpi_find_table(ACPI_SIG_MADT);
KASSERT(madt_pa != 0, ("no MADT!"));
madt = acpi_map_table(madt_pa, ACPI_SIG_MADT);
KASSERT(madt != NULL, ("can't map MADT!"));
acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
madt_resolve_its_xref, NULL);
acpi_unmap_table(madt);
srat_pa = acpi_find_table(ACPI_SIG_SRAT);
if (srat_pa != 0) {
srat = acpi_map_table(srat_pa, ACPI_SIG_SRAT);
KASSERT(srat != NULL, ("can't map SRAT!"));
acpi_walk_subtables(srat + 1, (char *)srat + srat->Header.Length,
srat_resolve_its_pxm, map_counts);
acpi_unmap_table(srat);
}
return (0);
}
static int
acpi_parse_iort(void *dummy __unused)
{
ACPI_TABLE_IORT *iort;
ACPI_IORT_NODE *node_entry;
vm_paddr_t iort_pa;
u_int node_offset;
iort_pa = acpi_find_table(ACPI_SIG_IORT);
if (iort_pa == 0)
return (ENXIO);
iort = acpi_map_table(iort_pa, ACPI_SIG_IORT);
if (iort == NULL) {
printf("ACPI: Unable to map the IORT table!\n");
return (ENXIO);
}
for (node_offset = iort->NodeOffset;
node_offset < iort->Header.Length;
node_offset += node_entry->Length) {
node_entry = ACPI_ADD_PTR(ACPI_IORT_NODE, iort, node_offset);
iort_add_nodes(node_entry, node_offset);
}
acpi_unmap_table(iort);
iort_post_process_mappings();
iort_post_process_its();
return (0);
}
SYSINIT(acpi_parse_iort, SI_SUB_DRIVERS, SI_ORDER_FIRST, acpi_parse_iort, NULL);
int
acpi_iort_its_lookup(u_int its_id, u_int *xref, int *pxm)
{
struct iort_node *its_node;
struct iort_its_entry *its_entry;
int i;
TAILQ_FOREACH(its_node, &its_groups, next) {
its_entry = its_node->entries.its;
for (i = 0; i < its_node->nentries; i++, its_entry++) {
if (its_entry->its_id == its_id) {
*xref = its_entry->xref;
*pxm = its_entry->pxm;
return (0);
}
}
}
return (ENOENT);
}
int
acpi_iort_map_pci_msi(u_int seg, u_int rid, u_int *xref, u_int *devid)
{
struct iort_node *node;
node = iort_pci_rc_map(seg, rid, ACPI_IORT_NODE_ITS_GROUP, devid);
if (node == NULL)
return (ENOENT);
KASSERT(node->type == ACPI_IORT_NODE_ITS_GROUP, ("bad group"));
*xref = node->entries.its[0].xref;
return (0);
}
int
acpi_iort_map_pci_smmuv3(u_int seg, u_int rid, uint64_t *xref, u_int *sid)
{
ACPI_IORT_SMMU_V3 *smmu;
struct iort_node *node;
node = iort_pci_rc_map(seg, rid, ACPI_IORT_NODE_SMMU_V3, sid);
if (node == NULL)
return (ENOENT);
KASSERT(node->type == ACPI_IORT_NODE_SMMU_V3, ("bad node"));
smmu = (ACPI_IORT_SMMU_V3 *)&node->data.smmu_v3;
*xref = smmu->BaseAddress;
return (0);
}
int
acpi_iort_map_named_msi(const char *devname, u_int rid, u_int *xref,
u_int *devid)
{
struct iort_node *node;
node = iort_named_comp_map(devname, rid, ACPI_IORT_NODE_ITS_GROUP,
devid);
if (node == NULL)
return (ENOENT);
KASSERT(node->type == ACPI_IORT_NODE_ITS_GROUP, ("bad group"));
*xref = node->entries.its[0].xref;
return (0);
}
int
acpi_iort_map_named_smmuv3(const char *devname, u_int rid, uint64_t *xref,
u_int *devid)
{
ACPI_IORT_SMMU_V3 *smmu;
struct iort_node *node;
node = iort_named_comp_map(devname, rid, ACPI_IORT_NODE_SMMU_V3, devid);
if (node == NULL)
return (ENOENT);
KASSERT(node->type == ACPI_IORT_NODE_SMMU_V3, ("bad node"));
smmu = (ACPI_IORT_SMMU_V3 *)&node->data.smmu_v3;
*xref = smmu->BaseAddress;
return (0);
}