memtest86plus/system/acpi.c

345 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020-2022 Martin Whitaker.
// Copyright (C) 2004-2022 Sam Demeulemeester.
#include "boot.h"
#include "bootparams.h"
#include "efi.h"
#include "pmem.h"
#include "string.h"
#include "unistd.h"
#include "vmem.h"
#include "acpi.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// Table signatures
#define RSDPSignature1 ('R' | ('S' << 8) | ('D' << 16) | (' ' << 24))
#define RSDPSignature2 ('P' | ('T' << 8) | ('R' << 16) | (' ' << 24))
#define RSDTSignature ('R' | ('S' << 8) | ('D' << 16) | ('T' << 24)) // Root System Description Table
#define XSDTSignature ('X' | ('S' << 8) | ('D' << 16) | ('T' << 24)) // Extended System Description Table
#define MADTSignature ('A' | ('P' << 8) | ('I' << 16) | ('C' << 24)) // Multiple APIC Description Table
#define FADTSignature ('F' | ('A' << 8) | ('C' << 16) | ('P' << 24)) // Fixed ACPI Description Table
#define HPETSignature ('H' | ('P' << 8) | ('E' << 16) | ('T' << 24)) // High Precision Event Timer
#define EINJSignature ('E' | ('I' << 8) | ('N' << 16) | ('J' << 24)) // Error Injection Table
#define ERSTSignature ('E' | ('R' << 8) | ('S' << 16) | ('T' << 24)) // Error Record Serialization Table
#define CPEPSignature ('C' | ('P' << 8) | ('E' << 16) | ('P' << 24)) // Corrected Platform Error Polling Table
#define HESTSignature ('H' | ('E' << 8) | ('S' << 16) | ('T' << 24)) // Hardware Error Source Table
#define SLITSignature ('S' | ('L' << 8) | ('I' << 16) | ('T' << 24)) // System Locality Information Table (NUMA)
#define SRATSignature ('S' | ('R' << 8) | ('A' << 16) | ('T' << 24)) // System Resource Affinity Table (NUMA)
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef struct __attribute__ ((packed)) {
uint8_t address_space;
uint8_t bit_width;
uint8_t bit_offset;
uint8_t access_size;
uint64_t address;
} acpi_gen_addr_struct;
typedef struct {
char signature[8]; // "RSD PTR "
uint8_t checksum;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_addr;
uint32_t length;
uint64_t xsdt_addr;
uint8_t xchecksum;
uint8_t reserved[3];
} rsdp_t;
typedef struct {
char signature[4]; // "RSDT" or "XSDT"
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oem_id[6];
char oem_table_id[8];
char oem_revision[4];
char creator_id[4];
char creator_revision[4];
} rsdt_header_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static const efi_guid_t EFI_ACPI_1_RDSP_GUID = { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d} };
static const efi_guid_t EFI_ACPI_2_RDSP_GUID = { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81} };
//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------
const char *rsdp_source = "";
acpi_t acpi_config = {0, 0, 0, 0, 0, 0, 0, false};
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static rsdp_t *scan_for_rsdp(uintptr_t addr, int length)
{
uint32_t *ptr = (uint32_t *)addr;
uint32_t *end = ptr + length / sizeof(uint32_t);
while (ptr < end) {
rsdp_t *rp = (rsdp_t *)ptr;
if (*ptr == RSDPSignature1 && *(ptr+1) == RSDPSignature2 && acpi_checksum(ptr, 20) == 0) {
if (rp->revision < 2 || (rp->length < 1024 && acpi_checksum(ptr, rp->length) == 0)) {
return rp;
}
}
ptr += 4;
}
return NULL;
}
#ifdef __x86_64__
static rsdp_t *find_rsdp_in_efi64_system_table(efi64_system_table_t *system_table)
{
efi64_config_table_t *config_tables = (efi64_config_table_t *)map_region(system_table->config_tables,
system_table->num_config_tables * sizeof(efi64_config_table_t),
true);
if (config_tables == NULL) return NULL;
uintptr_t table_addr = 0;
for (uint32_t i = 0; i < system_table->num_config_tables; i++) {
if (memcmp(&config_tables[i].guid, &EFI_ACPI_2_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
break;
}
if (memcmp(&config_tables[i].guid, &EFI_ACPI_1_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
}
}
return (rsdp_t *)table_addr;
}
#else
static rsdp_t *find_rsdp_in_efi32_system_table(efi32_system_table_t *system_table)
{
efi32_config_table_t *config_tables = (efi32_config_table_t *)map_region(system_table->config_tables,
system_table->num_config_tables * sizeof(efi32_config_table_t),
true);
if (config_tables == NULL) return NULL;
uintptr_t table_addr = 0;
for (uint32_t i = 0; i < system_table->num_config_tables; i++) {
if (memcmp(&config_tables[i].guid, &EFI_ACPI_2_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
break;
}
if (memcmp(&config_tables[i].guid, &EFI_ACPI_1_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
}
}
return (rsdp_t *)table_addr;
}
#endif
static uintptr_t find_rsdp(void)
{
const boot_params_t *boot_params = (boot_params_t *)boot_params_addr;
const efi_info_t *efi_info = &boot_params->efi_info;
// Search for the RSDP
rsdp_t *rp = NULL;
#ifdef __x86_64__
if (efi_info->loader_signature == EFI64_LOADER_SIGNATURE) {
uintptr_t system_table_addr = (uintptr_t)efi_info->sys_tab_hi << 32 | (uintptr_t)efi_info->sys_tab;
system_table_addr = map_region(system_table_addr, sizeof(efi64_system_table_t), true);
if (system_table_addr != 0) {
rp = find_rsdp_in_efi64_system_table((efi64_system_table_t *)system_table_addr);
if (rp) rsdp_source = "EFI64 system table";
}
}
#else
if (efi_info->loader_signature == EFI32_LOADER_SIGNATURE) {
uintptr_t system_table_addr = map_region(efi_info->sys_tab, sizeof(efi32_system_table_t), true);
if (system_table_addr != 0) {
rp = find_rsdp_in_efi32_system_table((efi32_system_table_t *)system_table_addr);
if (rp) rsdp_source = "EFI32 system table";
}
}
#endif
if (rp == NULL) {
// Search the BIOS EBDA area.
uintptr_t address = *(uint16_t *)0x40E << 4;
if (address) {
rp = scan_for_rsdp(address, 0x400);
if (rp) rsdp_source = "BIOS EBDA";
}
}
if (rp == NULL) {
// Search the BIOS reserved area.
rp = scan_for_rsdp(0xE0000, 0x20000);
if (rp) rsdp_source = "BIOS reserved area";
}
if (rp == NULL) {
// RSDP not found, give up.
return 0;
}
return (uintptr_t)rp;
}
static uintptr_t find_acpi_table(uint32_t table_signature)
{
rsdp_t *rp = (rsdp_t *)acpi_config.rsdp_addr;
// Found the RSDP, now get either the RSDT or XSDT
// and scan it for a pointer to the table we're looking for
rsdt_header_t *rt;
if (acpi_config.ver_maj < rp->revision) {
acpi_config.ver_maj = rp->revision;
}
if (rp->revision >= 2) {
rt = (rsdt_header_t *)map_region(rp->xsdt_addr, sizeof(rsdt_header_t), true);
if (rt == NULL) {
return 0;
}
// Validate the XSDT.
if (*(uint32_t *)rt != XSDTSignature) {
return 0;
}
rt = (rsdt_header_t *)map_region(rp->xsdt_addr, rt->length, true);
if (rt == NULL || acpi_checksum(rt, rt->length) != 0) {
return 0;
}
// Scan the XSDT for a pointer to the table we're looking for.
uint64_t *tab_ptr = (uint64_t *)((uint8_t *)rt + sizeof(rsdt_header_t));
uint64_t *tab_end = (uint64_t *)((uint8_t *)rt + rt->length);
while (tab_ptr < tab_end) {
uintptr_t addr = *tab_ptr++; // read the next table entry
uint32_t *ptr = (uint32_t *)map_region(addr, sizeof(uint32_t), true);
if (ptr && *ptr == table_signature) {
return addr;
}
}
} else {
rt = (rsdt_header_t *)map_region(rp->rsdt_addr, sizeof(rsdt_header_t), true);
if (rt == NULL) {
return 0;
}
// Validate the RSDT.
if (*(uint32_t *)rt != RSDTSignature) {
return 0;
}
rt = (rsdt_header_t *)map_region(rp->rsdt_addr, rt->length, true);
if (rt == NULL || acpi_checksum(rt, rt->length) != 0) {
return 0;
}
// Scan the RSDT for a pointer to the table we're looking for.
uint32_t *tab_ptr = (uint32_t *)((uint8_t *)rt + sizeof(rsdt_header_t));
uint32_t *tab_end = (uint32_t *)((uint8_t *)rt + rt->length);
while (tab_ptr < tab_end) {
uintptr_t addr = *tab_ptr++; // read the next table entry
uint32_t *ptr = (uint32_t *)map_region(addr, sizeof(uint32_t), true);
if (ptr && *ptr == table_signature) {
return addr;
}
}
}
return 0;
}
static bool parse_fadt(uintptr_t fadt_addr)
{
// FADT is a very big & complex table and we only need a few data.
// We use byte offset instead of a complete struct.
// FADT Header is identical to RSDP Header
rsdt_header_t *fadt = (rsdt_header_t *)fadt_addr;
// Validate FADT
if (fadt == NULL || acpi_checksum(fadt, fadt->length) != 0) {
return false;
}
// Get ACPI Version
acpi_config.ver_maj = fadt->revision;
if (fadt->length > FADT_MINOR_REV_OFFSET) {
acpi_config.ver_min = *(uint8_t *)(fadt_addr+FADT_MINOR_REV_OFFSET) & 0xF;
}
// Get Old PM Base Address (32bit IO)
acpi_config.pm_addr = *(uint32_t *)(fadt_addr+FADT_PM_TMR_BLK_OFFSET);
acpi_config.pm_is_io = true;
#ifdef __x86_64__
acpi_gen_addr_struct *rt;
// Get APIC Timer Address
if (fadt->length > FADT_X_PM_TMR_BLK_OFFSET) {
rt = (acpi_gen_addr_struct *)map_region(fadt_addr+FADT_X_PM_TMR_BLK_OFFSET, sizeof(acpi_gen_addr_struct), true);
acpi_config.pm_is_io = (rt->address_space == 1) ? true : false;
if (rt->address != 0) {
acpi_config.pm_addr = rt->address;
}
}
#endif
return true;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int acpi_checksum(const void *data, int length)
{
uint8_t sum = 0;
uint8_t *ptr = (uint8_t *)data;
while (length--) {
sum += *ptr++;
}
return sum;
}
void acpi_init(void)
{
acpi_config.rsdp_addr = find_rsdp();
if (acpi_config.rsdp_addr == 0) {
return;
}
acpi_config.madt_addr = find_acpi_table(MADTSignature);
acpi_config.fadt_addr = find_acpi_table(FADTSignature);
if (acpi_config.fadt_addr) {
parse_fadt(acpi_config.fadt_addr);
}
acpi_config.hpet_addr = find_acpi_table(HPETSignature);
}