| /* |
| * Copyright (c) 2020 Intel Corporation |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <zephyr/kernel.h> |
| #include <zephyr/arch/x86/acpi.h> |
| #include <zephyr/arch/x86/efi.h> |
| |
| static struct acpi_rsdp *rsdp; |
| static bool is_rsdp_searched; |
| |
| static struct acpi_dmar *dmar; |
| static 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; |
| } |
| |
| static void find_rsdp(void) |
| { |
| uint8_t *bda_seg, *zero_page_base; |
| uint64_t *search; |
| uintptr_t search_phys, rsdp_phys = 0U; |
| size_t search_length = 0U, rsdp_length; |
| |
| if (is_rsdp_searched) { |
| /* Looking up for RSDP has already been done */ |
| return; |
| } |
| |
| /* Let's first get it from EFI, if enabled */ |
| if (IS_ENABLED(CONFIG_X86_EFI)) { |
| rsdp_phys = (uintptr_t)efi_get_acpi_rsdp(); |
| if (rsdp_phys != 0UL) { |
| /* See at found label why this is required */ |
| search_length = sizeof(struct acpi_rsdp); |
| z_phys_map((uint8_t **)&search, rsdp_phys, search_length, 0); |
| rsdp = (struct acpi_rsdp *)search; |
| |
| goto found; |
| } |
| } |
| |
| /* We never identity map the NULL page, so need to map it before |
| * it can be accessed. |
| */ |
| z_phys_map(&zero_page_base, 0, 4096, 0); |
| |
| /* Physical (real mode!) address 0000:040e stores a (real |
| * mode!!) segment descriptor pointing to the 1kb Extended |
| * BIOS Data Area. |
| * |
| * 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; |
| search_phys = (long)(((int)*(uint16_t *)bda_seg) << 4); |
| |
| /* Unmap after use */ |
| z_phys_unmap(zero_page_base, 4096); |
| |
| /* Might be nothing there, check before we inspect. |
| * Note that EBDA usually is in 0x80000 to 0x100000. |
| */ |
| if ((POINTER_TO_UINT(search_phys) >= 0x80000UL) && |
| (POINTER_TO_UINT(search_phys) < 0x100000UL)) { |
| search_length = 1024; |
| z_phys_map((uint8_t **)&search, search_phys, search_length, 0); |
| |
| for (int i = 0; i < 1024/8; i++) { |
| if (search[i] == ACPI_RSDP_SIGNATURE) { |
| rsdp_phys = search_phys + i * 8; |
| rsdp = (void *)&search[i]; |
| goto found; |
| } |
| } |
| |
| z_phys_unmap((uint8_t *)search, search_length); |
| } |
| |
| /* If it's not there, then look for it in the last 128kb of |
| * real mode memory. |
| */ |
| search_phys = 0xe0000; |
| search_length = 128 * 1024; |
| z_phys_map((uint8_t **)&search, search_phys, search_length, 0); |
| |
| rsdp_phys = 0U; |
| for (int i = 0; i < 128*1024/8; i++) { |
| if (search[i] == ACPI_RSDP_SIGNATURE) { |
| rsdp_phys = search_phys + i * 8; |
| rsdp = (void *)&search[i]; |
| goto found; |
| } |
| } |
| |
| z_phys_unmap((uint8_t *)search, search_length); |
| |
| rsdp = NULL; |
| |
| is_rsdp_searched = true; |
| |
| return; |
| |
| found: |
| /* Determine length of RSDP table. |
| * ACPI v2 and above uses the length field. |
| * Otherwise, just the size of struct itself. |
| */ |
| if (rsdp->revision < 2) { |
| rsdp_length = sizeof(*rsdp); |
| } else { |
| rsdp_length = rsdp->length; |
| } |
| |
| /* Need to unmap search since it is still mapped */ |
| if (search_length != 0U) { |
| z_phys_unmap((uint8_t *)search, search_length); |
| } |
| |
| /* Now map the RSDP */ |
| z_phys_map((uint8_t **)&rsdp, rsdp_phys, rsdp_length, 0); |
| |
| is_rsdp_searched = true; |
| } |
| |
| void *z_acpi_find_table(uint32_t signature) |
| { |
| uint8_t *mapped_tbl; |
| uint32_t length; |
| struct acpi_rsdt *rsdt; |
| struct acpi_xsdt *xsdt; |
| struct acpi_sdt *t; |
| uintptr_t t_phys; |
| bool tbl_found; |
| |
| find_rsdp(); |
| |
| if (!rsdp) { |
| return NULL; |
| } |
| |
| if (rsdp->rsdt_ptr) { |
| z_phys_map((uint8_t **)&rsdt, rsdp->rsdt_ptr, sizeof(*rsdt), 0); |
| tbl_found = false; |
| |
| if (check_sum(&rsdt->sdt)) { |
| /* Remap the memory to the indicated length of RSDT */ |
| length = rsdt->sdt.length; |
| z_phys_unmap((uint8_t *)rsdt, sizeof(*rsdt)); |
| z_phys_map((uint8_t **)&rsdt, rsdp->rsdt_ptr, length, 0); |
| |
| uint32_t *end = (uint32_t *)((char *)rsdt + rsdt->sdt.length); |
| |
| /* Extra indirection required to avoid |
| * -Waddress-of-packed-member |
| */ |
| void *table_ptrs = &rsdt->table_ptrs[0]; |
| |
| for (uint32_t *tp = table_ptrs; tp < end; tp++) { |
| t_phys = (long)*tp; |
| z_phys_map(&mapped_tbl, t_phys, sizeof(*t), 0); |
| t = (void *)mapped_tbl; |
| |
| if (t->signature == signature && check_sum(t)) { |
| tbl_found = true; |
| break; |
| } |
| |
| z_phys_unmap(mapped_tbl, sizeof(*t)); |
| } |
| } |
| |
| z_phys_unmap((uint8_t *)rsdt, sizeof(*rsdt)); |
| |
| if (tbl_found) { |
| goto found; |
| } |
| } |
| |
| if (rsdp->revision < 2) { |
| return NULL; |
| } |
| |
| if (rsdp->xsdt_ptr) { |
| z_phys_map((uint8_t **)&xsdt, rsdp->xsdt_ptr, sizeof(*xsdt), 0); |
| |
| tbl_found = false; |
| if (check_sum(&xsdt->sdt)) { |
| /* Remap the memory to the indicated length of RSDT */ |
| length = xsdt->sdt.length; |
| z_phys_unmap((uint8_t *)xsdt, sizeof(*xsdt)); |
| z_phys_map((uint8_t **)&xsdt, rsdp->xsdt_ptr, length, 0); |
| |
| uint64_t *end = (uint64_t *)((char *)xsdt + xsdt->sdt.length); |
| |
| /* Extra indirection required to avoid |
| * -Waddress-of-packed-member |
| */ |
| void *table_ptrs = &xsdt->table_ptrs[0]; |
| |
| for (uint64_t *tp = table_ptrs; tp < end; tp++) { |
| t_phys = (long)*tp; |
| z_phys_map(&mapped_tbl, t_phys, sizeof(*t), 0); |
| t = (void *)mapped_tbl; |
| |
| if (t->signature == signature && check_sum(t)) { |
| tbl_found = true; |
| break; |
| } |
| |
| z_phys_unmap(mapped_tbl, sizeof(*t)); |
| } |
| } |
| |
| z_phys_unmap((uint8_t *)xsdt, sizeof(*xsdt)); |
| |
| if (tbl_found) { |
| goto found; |
| } |
| } |
| |
| return NULL; |
| |
| found: |
| /* Remap to indicated length of the table */ |
| length = t->length; |
| z_phys_unmap(mapped_tbl, sizeof(*t)); |
| z_phys_map(&mapped_tbl, t_phys, length, 0); |
| t = (void *)mapped_tbl; |
| |
| return t; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| uint16_t z_acpi_get_dev_id_from_dmar(uint8_t dev_scope_type) |
| { |
| struct acpi_drhd *drhd; |
| int n_drhd; |
| |
| find_dmar(); |
| |
| if (dmar == NULL) { |
| return USHRT_MAX; |
| } |
| |
| drhd = z_acpi_find_drhds(&n_drhd); |
| |
| for (; n_drhd > 0; n_drhd--) { |
| struct acpi_dmar_dev_scope *dev_scope; |
| int n_ds; |
| |
| dev_scope = z_acpi_get_drhd_dev_scopes(drhd, &n_ds); |
| for (; n_ds > 0; n_ds--) { |
| if (dev_scope->type == dev_scope_type) { |
| struct acpi_dmar_dev_path *path; |
| int n_path; |
| |
| path = z_acpi_get_dev_scope_paths(dev_scope, |
| &n_path); |
| if (n_path > 0) { |
| union acpi_dmar_id id; |
| |
| /* Let's over simplify for now: |
| * we don't look for secondary bus |
| * and extra paths. We just stop here. |
| */ |
| |
| id.bits.bus = dev_scope->start_bus_num; |
| id.bits.device = path->device; |
| id.bits.function = path->function; |
| |
| return id.raw; |
| } |
| } |
| |
| dev_scope = (struct acpi_dmar_dev_scope *)( |
| POINTER_TO_UINT(dev_scope) + dev_scope->length); |
| } |
| |
| drhd = (struct acpi_drhd *)(POINTER_TO_UINT(drhd) + |
| drhd->entry.length); |
| } |
| |
| return USHRT_MAX; |
| } |