Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * 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 = 0U, *p = (uint8_t *)t;

	for (int i = 0; i < t->length; i++) {
		sum += p[i];
	}

	return sum == 0U;
}


/* 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_phys_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) != 0) {
				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;
}