318 lines
7.1 KiB
C
318 lines
7.1 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#ifdef CONFIG_DMAR_PARSE_ENABLED
|
|
#include <hypervisor.h>
|
|
#include "pci.h"
|
|
#include "vtd.h"
|
|
#include "acpi_priv.h"
|
|
|
|
enum acpi_dmar_type {
|
|
ACPI_DMAR_TYPE_HARDWARE_UNIT = 0,
|
|
ACPI_DMAR_TYPE_RESERVED_MEMORY = 1,
|
|
ACPI_DMAR_TYPE_ROOT_ATS = 2,
|
|
ACPI_DMAR_TYPE_HARDWARE_AFFINITY = 3,
|
|
ACPI_DMAR_TYPE_NAMESPACE = 4,
|
|
ACPI_DMAR_TYPE_RESERVED = 5
|
|
};
|
|
|
|
struct acpi_table_dmar {
|
|
/* Common ACPI table header */
|
|
struct acpi_table_header header;
|
|
/* Host address Width */
|
|
uint8_t width;
|
|
uint8_t flags;
|
|
uint8_t reserved[10];
|
|
};
|
|
|
|
/* DMAR subtable header */
|
|
struct acpi_dmar_header {
|
|
uint16_t type;
|
|
uint16_t length;
|
|
};
|
|
|
|
struct acpi_dmar_hardware_unit {
|
|
struct acpi_dmar_header header;
|
|
uint8_t flags;
|
|
uint8_t reserved;
|
|
uint16_t segment;
|
|
/* register base address */
|
|
uint64_t address;
|
|
};
|
|
|
|
struct find_iter_args {
|
|
int32_t i;
|
|
struct acpi_dmar_hardware_unit *res;
|
|
};
|
|
|
|
struct acpi_dmar_pci_path {
|
|
uint8_t device;
|
|
uint8_t function;
|
|
};
|
|
|
|
struct acpi_dmar_device_scope {
|
|
uint8_t entry_type;
|
|
uint8_t length;
|
|
uint16_t reserved;
|
|
uint8_t enumeration_id;
|
|
uint8_t bus;
|
|
};
|
|
|
|
typedef int32_t (*dmar_iter_t)(struct acpi_dmar_header*, void*);
|
|
|
|
static struct dmar_info dmar_info_parsed;
|
|
static int32_t dmar_unit_cnt;
|
|
|
|
static void
|
|
dmar_iterate_tbl(dmar_iter_t iter, void *arg)
|
|
{
|
|
struct acpi_table_dmar *dmar_tbl;
|
|
struct acpi_dmar_header *dmar_header;
|
|
char *ptr, *ptr_end;
|
|
|
|
dmar_tbl = (struct acpi_table_dmar *)get_dmar_table();
|
|
ASSERT(dmar_tbl != NULL, "");
|
|
|
|
ptr = (char *)dmar_tbl + sizeof(*dmar_tbl);
|
|
ptr_end = (char *)dmar_tbl + dmar_tbl->header.length;
|
|
|
|
for (;;) {
|
|
if (ptr >= ptr_end)
|
|
break;
|
|
dmar_header = (struct acpi_dmar_header *)ptr;
|
|
if (dmar_header->length <= 0) {
|
|
pr_err("drhd: corrupted DMAR table, l %d\n",
|
|
dmar_header->length);
|
|
break;
|
|
}
|
|
ptr += dmar_header->length;
|
|
if (!iter(dmar_header, arg))
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int32_t
|
|
drhd_count_iter(struct acpi_dmar_header *dmar_header, __unused void *arg)
|
|
{
|
|
if (dmar_header->type == ACPI_DMAR_TYPE_HARDWARE_UNIT)
|
|
dmar_unit_cnt++;
|
|
return 1;
|
|
}
|
|
|
|
static int32_t
|
|
drhd_find_iter(struct acpi_dmar_header *dmar_header, void *arg)
|
|
{
|
|
struct find_iter_args *args;
|
|
|
|
if (dmar_header->type != ACPI_DMAR_TYPE_HARDWARE_UNIT)
|
|
return 1;
|
|
|
|
args = arg;
|
|
if (args->i == 0) {
|
|
args->res = (struct acpi_dmar_hardware_unit *)dmar_header;
|
|
return 0;
|
|
}
|
|
args->i--;
|
|
return 1;
|
|
}
|
|
|
|
static struct acpi_dmar_hardware_unit *
|
|
drhd_find_by_index(int32_t idx)
|
|
{
|
|
struct find_iter_args args;
|
|
|
|
args.i = idx;
|
|
args.res = NULL;
|
|
dmar_iterate_tbl(drhd_find_iter, &args);
|
|
return args.res;
|
|
}
|
|
|
|
static uint8_t get_secondary_bus(uint8_t bus, uint8_t dev, uint8_t func)
|
|
{
|
|
uint32_t data;
|
|
|
|
pio_write32(PCI_CFG_ENABLE | (bus << 16U) | (dev << 11U) |
|
|
(func << 8U) | 0x18U, PCI_CONFIG_ADDR);
|
|
|
|
data = pio_read32(PCI_CONFIG_DATA);
|
|
|
|
return (data >> 8U) & 0xffU;
|
|
}
|
|
|
|
static uint16_t
|
|
dmar_path_bdf(int32_t path_len, int32_t busno,
|
|
const struct acpi_dmar_pci_path *path)
|
|
{
|
|
int32_t i;
|
|
uint8_t bus;
|
|
uint8_t dev;
|
|
uint8_t fun;
|
|
|
|
|
|
bus = (uint8_t)busno;
|
|
dev = path->device;
|
|
fun = path->function;
|
|
|
|
|
|
for (i = 1; i < path_len; i++) {
|
|
bus = get_secondary_bus(bus, dev, fun);
|
|
dev = path[i].device;
|
|
fun = path[i].function;
|
|
}
|
|
return (bus << 8U | DEVFUN(dev, fun));
|
|
}
|
|
|
|
|
|
static int32_t
|
|
handle_dmar_devscope(struct dmar_dev_scope *dev_scope,
|
|
void *addr, int32_t remaining)
|
|
{
|
|
int32_t path_len;
|
|
uint16_t bdf;
|
|
struct acpi_dmar_pci_path *path;
|
|
struct acpi_dmar_device_scope *apci_devscope = addr;
|
|
|
|
if (remaining < (int32_t)sizeof(struct acpi_dmar_device_scope))
|
|
return -1;
|
|
|
|
if (remaining < apci_devscope->length)
|
|
return -1;
|
|
|
|
path = (struct acpi_dmar_pci_path *)(apci_devscope + 1);
|
|
path_len = (apci_devscope->length -
|
|
sizeof(struct acpi_dmar_device_scope)) /
|
|
sizeof(struct acpi_dmar_pci_path);
|
|
|
|
bdf = dmar_path_bdf(path_len, apci_devscope->bus, path);
|
|
dev_scope->id = apci_devscope->enumeration_id;
|
|
dev_scope->type = apci_devscope->entry_type;
|
|
dev_scope->bus = (bdf >> 8U) & 0xffU;
|
|
dev_scope->devfun = bdf & 0xffU;
|
|
|
|
return apci_devscope->length;
|
|
}
|
|
|
|
static uint32_t
|
|
get_drhd_dev_scope_cnt(struct acpi_dmar_hardware_unit *drhd)
|
|
{
|
|
struct acpi_dmar_device_scope *scope;
|
|
char *start;
|
|
char *end;
|
|
uint32_t count = 0;
|
|
|
|
start = (char *)drhd + sizeof(struct acpi_dmar_hardware_unit);
|
|
end = (char *)drhd + drhd->header.length;
|
|
|
|
while (start < end) {
|
|
scope = (struct acpi_dmar_device_scope *)start;
|
|
if ((scope->entry_type != ACPI_DMAR_SCOPE_TYPE_NOT_USED) &&
|
|
(scope->entry_type < ACPI_DMAR_SCOPE_TYPE_RESERVED))
|
|
count++;
|
|
start += scope->length;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int32_t
|
|
handle_one_drhd(struct acpi_dmar_hardware_unit *acpi_drhd,
|
|
struct dmar_drhd *drhd)
|
|
{
|
|
struct dmar_dev_scope *dev_scope;
|
|
struct acpi_dmar_device_scope *ads;
|
|
int32_t remaining, consumed;
|
|
char *cp;
|
|
uint32_t dev_count;
|
|
|
|
drhd->segment = acpi_drhd->segment;
|
|
drhd->flags = acpi_drhd->flags;
|
|
drhd->reg_base_addr = acpi_drhd->address;
|
|
|
|
dev_count = get_drhd_dev_scope_cnt(acpi_drhd);
|
|
drhd->dev_cnt = dev_count;
|
|
if (dev_count) {
|
|
drhd->devices =
|
|
calloc(dev_count, sizeof(struct dmar_dev_scope));
|
|
ASSERT(drhd->devices, "");
|
|
} else {
|
|
drhd->devices = NULL;
|
|
return 0;
|
|
}
|
|
|
|
remaining = acpi_drhd->header.length -
|
|
sizeof(struct acpi_dmar_hardware_unit);
|
|
|
|
dev_scope = drhd->devices;
|
|
|
|
while (remaining > 0) {
|
|
cp = (char *)acpi_drhd + acpi_drhd->header.length - remaining;
|
|
|
|
consumed = handle_dmar_devscope(dev_scope, cp, remaining);
|
|
|
|
if (((drhd->segment << 16U) |
|
|
(dev_scope->bus << 8U) |
|
|
dev_scope->devfun) == CONFIG_GPU_SBDF) {
|
|
ASSERT(dev_count == 1, "no dedicated iommu for gpu");
|
|
drhd->ignore = true;
|
|
}
|
|
|
|
if (consumed <= 0)
|
|
break;
|
|
|
|
remaining -= consumed;
|
|
/* skip IOAPIC & HPET */
|
|
ads = (struct acpi_dmar_device_scope *)cp;
|
|
if ((ads->entry_type != ACPI_DMAR_SCOPE_TYPE_NOT_USED) &&
|
|
(ads->entry_type < ACPI_DMAR_SCOPE_TYPE_RESERVED)) {
|
|
dev_scope++;
|
|
}
|
|
else
|
|
pr_dbg("drhd: skip dev_scope type %d",
|
|
ads->entry_type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t parse_dmar_table(void)
|
|
{
|
|
int32_t i;
|
|
struct acpi_dmar_hardware_unit *acpi_drhd;
|
|
|
|
/* find out how many dmar units */
|
|
dmar_iterate_tbl(drhd_count_iter, NULL);
|
|
|
|
/* alloc memory for dmar uint */
|
|
dmar_info_parsed.drhd_units =
|
|
calloc(dmar_unit_cnt, sizeof(struct dmar_drhd));
|
|
ASSERT(dmar_info_parsed.drhd_units, "");
|
|
|
|
dmar_info_parsed.drhd_count = dmar_unit_cnt;
|
|
|
|
for (i = 0; i < dmar_unit_cnt; i++) {
|
|
acpi_drhd = drhd_find_by_index(i);
|
|
if (acpi_drhd == NULL)
|
|
continue;
|
|
if (acpi_drhd->flags & DRHD_FLAG_INCLUDE_PCI_ALL_MASK)
|
|
ASSERT((i+1) == dmar_unit_cnt,
|
|
"drhd with flags set should be the last one");
|
|
handle_one_drhd(acpi_drhd, &dmar_info_parsed.drhd_units[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @post return != NULL
|
|
*/
|
|
struct dmar_info *get_dmar_info(void)
|
|
{
|
|
if (dmar_info_parsed.drhd_count == 0) {
|
|
parse_dmar_table();
|
|
}
|
|
return &dmar_info_parsed;
|
|
}
|
|
#endif
|