zephyr/arch/x86/core/acpi.c
Tomasz Bursztyka be4c893549 arch/x86: Expose function do get DRHDs from DMAR ACPI table
This is part of Intel VT-D and how to discover capabilities, base
addresses and so on in order to start taking advantage from it.

There is a lot to get from there, but currently we are interested only
by getting the remapping hardware base address. And more specifically
for interrupt remapping usage.

There might be more than one of such hardware so the exposed function is
made to retrieve all of them.

Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
2020-12-08 09:29:20 -05:00

281 lines
5.6 KiB
C

/*
* Copyright (c) 2020 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <arch/x86/acpi.h>
static struct acpi_rsdp *rsdp;
bool is_rdsp_searched = false;
static struct acpi_dmar *dmar;
bool is_dmar_searched;
static bool check_sum(struct acpi_sdt *t)
{
uint8_t sum = 0, *p = (uint8_t *)t;
for (int i = 0; i < t->length; i++) {
sum += p[i];
}
return sum == 0;
}
/* We never identity map the NULL page, but may need to read some BIOS data */
static uint8_t *zero_page_base;
static void find_rsdp(void)
{
uint8_t *bda_seg;
if (is_rdsp_searched) {
/* Looking up for RSDP has already been done */
return;
}
if (zero_page_base == NULL) {
z_mem_map(&zero_page_base, 0, 4096, K_MEM_PERM_RW);
}
/* Physical (real mode!) address 0000:040e stores a (real
* mode!!) segment descriptor pointing to the 1kb Extended
* BIOS Data Area. Look there first.
*
* We had to memory map this segment descriptor since it is in
* the NULL page. The remaining structures (EBDA etc) are identity
* mapped somewhere within the minefield of reserved regions in the
* first megabyte and are directly accessible.
*/
bda_seg = 0x040e + zero_page_base;
uint64_t *search = (void *)(long)(((int)*(uint16_t *)bda_seg) << 4);
/* Might be nothing there, check before we inspect */
if (search != NULL) {
for (int i = 0; i < 1024/8; i++) {
if (search[i] == ACPI_RSDP_SIGNATURE) {
rsdp = (void *)&search[i];
goto out;
}
}
}
/* If it's not there, then look for it in the last 128kb of
* real mode memory.
*/
search = (uint64_t *)0xe0000;
for (int i = 0; i < 128*1024/8; i++) {
if (search[i] == ACPI_RSDP_SIGNATURE) {
rsdp = (void *)&search[i];
goto out;
}
}
/* Now we're supposed to look in the UEFI system table, which
* is passed as a function argument to the bootloader and long
* forgotten by now...
*/
rsdp = NULL;
out:
is_rdsp_searched = true;
}
void *z_acpi_find_table(uint32_t signature)
{
find_rsdp();
if (!rsdp) {
return NULL;
}
struct acpi_rsdt *rsdt = (void *)(long)rsdp->rsdt_ptr;
if (rsdt && check_sum(&rsdt->sdt)) {
uint32_t *end = (uint32_t *)((char *)rsdt + rsdt->sdt.length);
for (uint32_t *tp = &rsdt->table_ptrs[0]; tp < end; tp++) {
struct acpi_sdt *t = (void *)(long)*tp;
if (t->signature == signature && check_sum(t)) {
return t;
}
}
}
if (rsdp->revision < 2) {
return NULL;
}
struct acpi_xsdt *xsdt = (void *)(long)rsdp->xsdt_ptr;
if (xsdt && check_sum(&xsdt->sdt)) {
uint64_t *end = (uint64_t *)((char *)xsdt + xsdt->sdt.length);
for (uint64_t *tp = &xsdt->table_ptrs[0]; tp < end; tp++) {
struct acpi_sdt *t = (void *)(long)*tp;
if (t->signature == signature && check_sum(t)) {
return t;
}
}
}
return NULL;
}
/*
* Return the 'n'th CPU entry from the ACPI MADT, or NULL if not available.
*/
struct acpi_cpu *z_acpi_get_cpu(int n)
{
struct acpi_madt *madt = z_acpi_find_table(ACPI_MADT_SIGNATURE);
uintptr_t base = POINTER_TO_UINT(madt);
uintptr_t offset;
if (!madt) {
return NULL;
}
offset = POINTER_TO_UINT(madt->entries) - base;
while (offset < madt->sdt.length) {
struct acpi_madt_entry *entry;
entry = (struct acpi_madt_entry *)(offset + base);
if (entry->type == ACPI_MADT_ENTRY_CPU) {
struct acpi_cpu *cpu = (struct acpi_cpu *)entry;
if (cpu->flags & ACPI_CPU_FLAGS_ENABLED) {
if (n == 0) {
return cpu;
}
--n;
}
}
offset += entry->length;
}
return NULL;
}
static void find_dmar(void)
{
if (is_dmar_searched) {
return;
}
dmar = z_acpi_find_table(ACPI_DMAR_SIGNATURE);
is_dmar_searched = true;
}
struct acpi_dmar *z_acpi_find_dmar(void)
{
find_dmar();
return dmar;
}
struct acpi_drhd *z_acpi_find_drhds(int *n)
{
struct acpi_drhd *drhds = NULL;
uintptr_t offset;
uintptr_t base;
find_dmar();
if (dmar == NULL) {
return NULL;
}
*n = 0;
base = POINTER_TO_UINT(dmar);
offset = POINTER_TO_UINT(dmar->remap_entries) - base;
while (offset < dmar->sdt.length) {
struct acpi_dmar_entry *entry;
entry = (struct acpi_dmar_entry *)(offset + base);
if (entry->type == ACPI_DMAR_TYPE_DRHD) {
if (*n == 0) {
drhds = (struct acpi_drhd *)entry;
}
(*n)++;
} else {
/* DMAR entries are found packed by type so
* if type is not DRHD, we will not encounter one,
* anymore.
*/
break;
}
offset += entry->length;
}
return drhds;
}
struct acpi_dmar_dev_scope *z_acpi_get_drhd_dev_scopes(struct acpi_drhd *drhd,
int *n)
{
uintptr_t offset;
uintptr_t base;
if (drhd->entry.length <= ACPI_DRHD_MIN_SIZE) {
return NULL;
}
*n = 0;
base = POINTER_TO_UINT(drhd);
offset = POINTER_TO_UINT(drhd->device_scope) - base;
while (offset < drhd->entry.length) {
struct acpi_dmar_dev_scope *dev_scope;
dev_scope = (struct acpi_dmar_dev_scope *)(offset + base);
(*n)++;
offset += dev_scope->length;
}
return (*n == 0) ? NULL : drhd->device_scope;
}
struct acpi_dmar_dev_path *
z_acpi_get_dev_scope_paths(struct acpi_dmar_dev_scope *dev_scope, int *n)
{
switch (dev_scope->type) {
case ACPI_DRHD_DEV_SCOPE_PCI_EPD:
/* Fall through */
case ACPI_DRHD_DEV_SCOPE_PCI_SUB_H:
/* Fall through */
case ACPI_DRHD_DEV_SCOPE_IOAPIC:
if (dev_scope->length < (ACPI_DMAR_DEV_SCOPE_MIN_SIZE +
ACPI_DMAR_DEV_PATH_SIZE)) {
return NULL;
}
break;
case ACPI_DRHD_DEV_SCOPE_MSI_CAP_HPET:
/* Fall through */
case ACPI_DRHD_DEV_SCOPE_NAMESPACE_DEV:
if (dev_scope->length != (ACPI_DMAR_DEV_SCOPE_MIN_SIZE +
ACPI_DMAR_DEV_PATH_SIZE)) {
return NULL;
}
break;
default:
return NULL;
}
*n = (dev_scope->length - ACPI_DMAR_DEV_SCOPE_MIN_SIZE) /
ACPI_DMAR_DEV_PATH_SIZE;
return dev_scope->path;
}