If CONFIG(CRB_TPM) is enabled but the TPM is inactive, and no other TPM interface (SPI, I2C, Memory-Mapped) is configured, the function would incorrectly fallback to generate a TPM2 table for FIFO mode. This commit adds a check to ensure crb_tpm_is_active() is only called if CONFIG(CRB_TPM) is enabled and no other TPM interface is present. If the CRB TPM is inactive and no other TPMs are available, the function now exits early to prevent generating an invalid TPM2 table. Test=boot `starlabs/starlite_adl` and check Linux doesn't probe for a TPM when PTT is not active. Change-Id: I153779aa1f3d84ffeb694543f9da1d09b120f98f Signed-off-by: Sean Rhodes <sean@starlabs.systems> Reviewed-on: https://review.coreboot.org/c/coreboot/+/86513 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
1843 lines
50 KiB
C
1843 lines
50 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* coreboot ACPI Table support
|
|
*/
|
|
|
|
/*
|
|
* Each system port implementing ACPI has to provide two functions:
|
|
*
|
|
* write_acpi_tables()
|
|
* acpi_dump_apics()
|
|
*
|
|
* See Kontron 986LCD-M port for a good example of an ACPI implementation
|
|
* in coreboot.
|
|
*/
|
|
|
|
#include <acpi/acpi.h>
|
|
#include <acpi/acpi_iort.h>
|
|
#include <acpi/acpi_ivrs.h>
|
|
#include <acpi/acpigen.h>
|
|
#include <cbfs.h>
|
|
#include <cbmem.h>
|
|
#include <commonlib/helpers.h>
|
|
#include <console/console.h>
|
|
#include <cpu/cpu.h>
|
|
#include <device/device.h>
|
|
#include <device/mmio.h>
|
|
#include <device/pci.h>
|
|
#include <drivers/crb/tpm.h>
|
|
#include <drivers/uart/pl011.h>
|
|
#include <security/tpm/tss.h>
|
|
#include <string.h>
|
|
#include <types.h>
|
|
#include <version.h>
|
|
|
|
static acpi_rsdp_t *valid_rsdp(acpi_rsdp_t *rsdp);
|
|
|
|
u8 acpi_checksum(u8 *table, u32 length)
|
|
{
|
|
u8 ret = 0;
|
|
while (length--) {
|
|
ret += *table;
|
|
table++;
|
|
}
|
|
return -ret;
|
|
}
|
|
|
|
/**
|
|
* Add an ACPI table to the RSDT (and XSDT) structure, recalculate length
|
|
* and checksum.
|
|
*/
|
|
void acpi_add_table(acpi_rsdp_t *rsdp, void *table)
|
|
{
|
|
int i, entries_num;
|
|
acpi_rsdt_t *rsdt;
|
|
acpi_xsdt_t *xsdt;
|
|
|
|
/* The 32bit RSDT may not be valid if tables live above 4GiB */
|
|
rsdt = (acpi_rsdt_t *)(uintptr_t)rsdp->rsdt_address;
|
|
xsdt = (acpi_xsdt_t *)(uintptr_t)rsdp->xsdt_address;
|
|
|
|
/* This should always be MAX_ACPI_TABLES. */
|
|
entries_num = ARRAY_SIZE(xsdt->entry);
|
|
|
|
for (i = 0; i < entries_num; i++) {
|
|
if (xsdt->entry[i] == 0)
|
|
break;
|
|
}
|
|
|
|
if (i >= entries_num) {
|
|
printk(BIOS_ERR, "ACPI: Error: Could not add ACPI table, "
|
|
"too many tables.\n");
|
|
return;
|
|
}
|
|
|
|
/* Add table to the XSDT. */
|
|
xsdt->entry[i] = (u64)(uintptr_t)table;
|
|
|
|
/* Fix XSDT length or the kernel will assume invalid entries. */
|
|
xsdt->header.length = sizeof(acpi_header_t) + (sizeof(u64) * (i + 1));
|
|
|
|
/* Re-calculate checksum. */
|
|
xsdt->header.checksum = 0; /* Hope this won't get optimized away */
|
|
xsdt->header.checksum = acpi_checksum((u8 *)xsdt, xsdt->header.length);
|
|
|
|
/*
|
|
* And now the same thing for the RSDT. We use the same index as for
|
|
* now we want the XSDT and RSDT to always be in sync in coreboot.
|
|
*/
|
|
if (rsdt && (uintptr_t)table < UINT32_MAX) {
|
|
/* Add table to the RSDT. */
|
|
rsdt->entry[i] = (u32)(uintptr_t)table;
|
|
|
|
/* Fix RSDT length. */
|
|
rsdt->header.length = sizeof(acpi_header_t) + (sizeof(u32) * (i + 1));
|
|
|
|
/* Re-calculate checksum. */
|
|
rsdt->header.checksum = 0;
|
|
rsdt->header.checksum = acpi_checksum((u8 *)rsdt, rsdt->header.length);
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "ACPI: added table %d/%d, length now %d\n",
|
|
i + 1, entries_num, xsdt->header.length);
|
|
}
|
|
|
|
static enum cb_err acpi_fill_header(acpi_header_t *header, const char name[4],
|
|
enum acpi_tables table, uint32_t size)
|
|
{
|
|
if (!header)
|
|
return CB_ERR;
|
|
|
|
/* Fill out header fields. */
|
|
memcpy(header->signature, name, 4);
|
|
memcpy(header->oem_id, OEM_ID, 6);
|
|
memcpy(header->oem_table_id, ACPI_TABLE_CREATOR, 8);
|
|
memcpy(header->asl_compiler_id, ASLC, 4);
|
|
|
|
header->asl_compiler_revision = asl_revision;
|
|
header->revision = get_acpi_table_revision(table);
|
|
header->length = size;
|
|
|
|
return CB_SUCCESS;
|
|
}
|
|
|
|
static int acpi_create_mcfg_mmconfig(acpi_mcfg_mmconfig_t *mmconfig, u64 base,
|
|
u16 seg_nr, u8 start, u8 end)
|
|
{
|
|
memset(mmconfig, 0, sizeof(*mmconfig));
|
|
mmconfig->base_address = base;
|
|
mmconfig->pci_segment_group_number = seg_nr;
|
|
mmconfig->start_bus_number = start;
|
|
mmconfig->end_bus_number = end;
|
|
|
|
return sizeof(acpi_mcfg_mmconfig_t);
|
|
}
|
|
|
|
static void acpi_create_madt(acpi_header_t *header, void *unused)
|
|
{
|
|
acpi_madt_t *madt = (acpi_madt_t *)header;
|
|
unsigned long current = (unsigned long)madt + sizeof(acpi_madt_t);
|
|
|
|
memset(madt, 0, sizeof(*madt));
|
|
|
|
if (acpi_fill_header(header, "APIC", MADT, sizeof(acpi_madt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_arch_fill_madt(madt, current);
|
|
|
|
if (CONFIG(ACPI_CUSTOM_MADT))
|
|
current = acpi_fill_madt(current);
|
|
|
|
/* (Re)calculate length . */
|
|
header->length = current - (unsigned long)madt;
|
|
}
|
|
|
|
static unsigned long acpi_fill_mcfg(unsigned long current)
|
|
{
|
|
for (int i = 0; i < PCI_SEGMENT_GROUP_COUNT; i++) {
|
|
current += acpi_create_mcfg_mmconfig((acpi_mcfg_mmconfig_t *)current,
|
|
CONFIG_ECAM_MMCONF_BASE_ADDRESS + i * PCI_PER_SEGMENT_GROUP_ECAM_SIZE,
|
|
i,
|
|
0,
|
|
PCI_BUSES_PER_SEGMENT_GROUP - 1);
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
/* MCFG is defined in the PCI Firmware Specification 3.0. */
|
|
static void acpi_create_mcfg(acpi_header_t *header, void *unused)
|
|
{
|
|
acpi_mcfg_t *mcfg = (acpi_mcfg_t *)header;
|
|
unsigned long current = (unsigned long)mcfg + sizeof(acpi_mcfg_t);
|
|
|
|
|
|
if (acpi_fill_header(header, "MCFG", MCFG, sizeof(acpi_mcfg_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
if (CONFIG(ECAM_MMCONF_SUPPORT))
|
|
current = acpi_fill_mcfg(current);
|
|
|
|
/* (Re)calculate length */
|
|
header->length = current - (unsigned long)mcfg;
|
|
}
|
|
|
|
static void *get_tcpa_log(u32 *size)
|
|
{
|
|
const struct cbmem_entry *ce;
|
|
const u32 tcpa_default_log_len = 0x10000;
|
|
void *lasa;
|
|
ce = cbmem_entry_find(CBMEM_ID_TCPA_TCG_LOG);
|
|
if (ce) {
|
|
lasa = cbmem_entry_start(ce);
|
|
*size = cbmem_entry_size(ce);
|
|
printk(BIOS_DEBUG, "TCPA log found at %p\n", lasa);
|
|
return lasa;
|
|
}
|
|
lasa = cbmem_add(CBMEM_ID_TCPA_TCG_LOG, tcpa_default_log_len);
|
|
if (!lasa) {
|
|
printk(BIOS_ERR, "TCPA log creation failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "TCPA log created at %p\n", lasa);
|
|
memset(lasa, 0, tcpa_default_log_len);
|
|
|
|
*size = tcpa_default_log_len;
|
|
return lasa;
|
|
}
|
|
|
|
static void acpi_create_tcpa(acpi_header_t *header, void *unused)
|
|
{
|
|
if (tlcl_get_family() != TPM_1)
|
|
return;
|
|
|
|
acpi_tcpa_t *tcpa = (acpi_tcpa_t *)header;
|
|
u32 tcpa_log_len;
|
|
void *lasa;
|
|
|
|
lasa = get_tcpa_log(&tcpa_log_len);
|
|
if (!lasa)
|
|
return;
|
|
|
|
if (acpi_fill_header(header, "TCPA", TCPA, sizeof(acpi_tcpa_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
tcpa->platform_class = 0;
|
|
tcpa->laml = tcpa_log_len;
|
|
tcpa->lasa = (uintptr_t)lasa;
|
|
}
|
|
|
|
static void *get_tpm2_log(u32 *size)
|
|
{
|
|
const struct cbmem_entry *ce;
|
|
const u32 tpm2_default_log_len = 0x10000;
|
|
void *lasa;
|
|
ce = cbmem_entry_find(CBMEM_ID_TPM2_TCG_LOG);
|
|
if (ce) {
|
|
lasa = cbmem_entry_start(ce);
|
|
*size = cbmem_entry_size(ce);
|
|
printk(BIOS_DEBUG, "TPM2 log found at %p\n", lasa);
|
|
return lasa;
|
|
}
|
|
lasa = cbmem_add(CBMEM_ID_TPM2_TCG_LOG, tpm2_default_log_len);
|
|
if (!lasa) {
|
|
printk(BIOS_ERR, "TPM2 log creation failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "TPM2 log created at %p\n", lasa);
|
|
memset(lasa, 0, tpm2_default_log_len);
|
|
|
|
*size = tpm2_default_log_len;
|
|
return lasa;
|
|
}
|
|
|
|
static void acpi_create_tpm2(acpi_header_t *header, void *unused)
|
|
{
|
|
if (tlcl_get_family() != TPM_2)
|
|
return;
|
|
|
|
if (CONFIG(CRB_TPM) && !(CONFIG(SPI_TPM) || CONFIG(I2C_TPM) || CONFIG(MEMORY_MAPPED_TPM)))
|
|
if (!crb_tpm_is_active())
|
|
return;
|
|
|
|
acpi_tpm2_t *tpm2 = (acpi_tpm2_t *)header;
|
|
u32 tpm2_log_len;
|
|
void *lasa;
|
|
|
|
/*
|
|
* Some payloads like SeaBIOS depend on log area to use TPM2.
|
|
* Get the memory size and address of TPM2 log area or initialize it.
|
|
*/
|
|
lasa = get_tpm2_log(&tpm2_log_len);
|
|
if (!lasa)
|
|
tpm2_log_len = 0;
|
|
|
|
if (acpi_fill_header(header, "TPM2", TPM2, sizeof(acpi_tpm2_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
/* Hard to detect for coreboot. Just set it to 0 */
|
|
tpm2->platform_class = 0;
|
|
if (CONFIG(CRB_TPM)) {
|
|
/* Must be set to 7 for CRB Support */
|
|
tpm2->control_area = CONFIG_CRB_TPM_BASE_ADDRESS + 0x40;
|
|
tpm2->start_method = 7;
|
|
} else {
|
|
/* Must be set to 0 for FIFO interface support */
|
|
tpm2->control_area = 0;
|
|
tpm2->start_method = 6;
|
|
}
|
|
memset(tpm2->msp, 0, sizeof(tpm2->msp));
|
|
|
|
/* Fill the log area size and start address fields. */
|
|
tpm2->laml = tpm2_log_len;
|
|
tpm2->lasa = (uintptr_t)lasa;
|
|
}
|
|
|
|
static void acpi_ssdt_write_cbtable(void)
|
|
{
|
|
const struct cbmem_entry *cbtable;
|
|
uintptr_t base;
|
|
uint32_t size;
|
|
|
|
cbtable = cbmem_entry_find(CBMEM_ID_CBTABLE);
|
|
if (!cbtable)
|
|
return;
|
|
base = (uintptr_t)cbmem_entry_start(cbtable);
|
|
size = cbmem_entry_size(cbtable);
|
|
|
|
acpigen_write_device("CTBL");
|
|
acpigen_write_coreboot_hid(COREBOOT_ACPI_ID_CBTABLE);
|
|
acpigen_write_name_integer("_UID", 0);
|
|
if (CONFIG(EC_GOOGLE_CHROMEEC))
|
|
acpigen_write_STA(ACPI_STATUS_DEVICE_ALL_ON);
|
|
else
|
|
acpigen_write_STA(ACPI_STATUS_DEVICE_HIDDEN_ON);
|
|
acpigen_write_name("_CRS");
|
|
acpigen_write_resourcetemplate_header();
|
|
acpigen_resource_consumer_mmio(base, base + size - 1,
|
|
MEM_RSRC_FLAG_MEM_READ_ONLY
|
|
| MEM_RSRC_FLAG_MEM_ATTR_CACHE);
|
|
acpigen_write_resourcetemplate_footer();
|
|
acpigen_pop_len();
|
|
}
|
|
|
|
static void acpi_create_ssdt_generator(acpi_header_t *ssdt, void *unused)
|
|
{
|
|
unsigned long current = (unsigned long)ssdt + sizeof(acpi_header_t);
|
|
|
|
if (acpi_fill_header(ssdt, "SSDT", SSDT, sizeof(acpi_header_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
acpigen_set_current((char *)current);
|
|
|
|
/* Write object to declare coreboot tables */
|
|
acpi_ssdt_write_cbtable();
|
|
|
|
{
|
|
struct device *dev;
|
|
for (dev = all_devices; dev; dev = dev->next)
|
|
if (dev->enabled && dev->ops && dev->ops->acpi_fill_ssdt)
|
|
dev->ops->acpi_fill_ssdt(dev);
|
|
current = (unsigned long)acpigen_get_current();
|
|
}
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
ssdt->length = current - (unsigned long)ssdt;
|
|
}
|
|
|
|
int acpi_create_srat_mem(acpi_srat_mem_t *mem, u8 node, u32 basek, u32 sizek,
|
|
u32 flags)
|
|
{
|
|
mem->type = 1; /* Memory affinity structure */
|
|
mem->length = sizeof(acpi_srat_mem_t);
|
|
mem->base_address_low = (basek << 10);
|
|
mem->base_address_high = (basek >> (32 - 10));
|
|
mem->length_low = (sizek << 10);
|
|
mem->length_high = (sizek >> (32 - 10));
|
|
mem->proximity_domain = node;
|
|
mem->flags = flags;
|
|
|
|
return mem->length;
|
|
}
|
|
|
|
int acpi_create_srat_gia_pci(acpi_srat_gia_t *gia, u32 proximity_domain,
|
|
struct device *dev, u32 flags)
|
|
{
|
|
/* Only handle PCI devices. */
|
|
if (dev->path.type != DEVICE_PATH_PCI)
|
|
return 0;
|
|
|
|
gia->type = ACPI_SRAT_STRUCTURE_GIA;
|
|
gia->length = sizeof(acpi_srat_gia_t);
|
|
gia->proximity_domain = proximity_domain;
|
|
gia->dev_handle_type = ACPI_SRAT_GIA_DEV_HANDLE_PCI;
|
|
/* First two bytes has segment number */
|
|
gia->dev_handle[0] = dev->upstream->segment_group;
|
|
gia->dev_handle[1] = 0;
|
|
gia->dev_handle[2] = dev->upstream->secondary; /* Byte 2 has bus number */
|
|
/* Byte 3 has bits 7:3 for dev, bits 2:0 for func */
|
|
gia->dev_handle[3] = dev->path.pci.devfn;
|
|
gia->flags = flags;
|
|
|
|
return gia->length;
|
|
}
|
|
|
|
/* http://www.microsoft.com/whdc/system/sysinternals/sratdwn.mspx */
|
|
void acpi_create_srat(acpi_srat_t *srat,
|
|
unsigned long (*acpi_fill_srat)(unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(srat->header);
|
|
unsigned long current = (unsigned long)srat + sizeof(acpi_srat_t);
|
|
|
|
memset((void *)srat, 0, sizeof(acpi_srat_t));
|
|
|
|
if (acpi_fill_header(header, "SRAT", SRAT, sizeof(acpi_srat_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
srat->resv = 1; /* Spec: Reserved to 1 for backwards compatibility. */
|
|
|
|
current = acpi_fill_srat(current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)srat;
|
|
header->checksum = acpi_checksum((void *)srat, header->length);
|
|
}
|
|
|
|
int acpi_create_cedt_chbs(acpi_cedt_chbs_t *chbs, u32 uid, u32 cxl_ver, u64 base)
|
|
{
|
|
memset((void *)chbs, 0, sizeof(acpi_cedt_chbs_t));
|
|
|
|
chbs->type = ACPI_CEDT_STRUCTURE_CHBS;
|
|
chbs->length = sizeof(acpi_cedt_chbs_t);
|
|
chbs->uid = uid;
|
|
chbs->cxl_ver = cxl_ver;
|
|
chbs->base = base;
|
|
|
|
/*
|
|
* CXL spec 2.0 section 9.14.1.2 "CXL CHBS"
|
|
* CXL 1.1 spec compliant host bridge: 8KB
|
|
* CXL 2.0 spec compliant host bridge: 64KB
|
|
*/
|
|
if (cxl_ver == ACPI_CEDT_CHBS_CXL_VER_1_1)
|
|
chbs->len = 8 * KiB;
|
|
else if (cxl_ver == ACPI_CEDT_CHBS_CXL_VER_2_0)
|
|
chbs->len = 64 * KiB;
|
|
else
|
|
printk(BIOS_ERR, "ACPI(%s:%s): Incorrect CXL version:%d\n", __FILE__, __func__,
|
|
cxl_ver);
|
|
|
|
return chbs->length;
|
|
}
|
|
|
|
int acpi_create_cedt_cfmws(acpi_cedt_cfmws_t *cfmws, u64 base_hpa, u64 window_size, u8 eniw,
|
|
u32 hbig, u16 restriction, u16 qtg_id, const u32 *interleave_target)
|
|
{
|
|
memset((void *)cfmws, 0, sizeof(acpi_cedt_cfmws_t));
|
|
|
|
cfmws->type = ACPI_CEDT_STRUCTURE_CFMWS;
|
|
|
|
u8 niw = 0;
|
|
if (eniw >= 8)
|
|
printk(BIOS_ERR, "ACPI(%s:%s): Incorrect eniw::%d\n", __FILE__, __func__, eniw);
|
|
else
|
|
/* NIW = 2 ** ENIW */
|
|
niw = 0x1 << eniw;
|
|
/* 36 + 4 * NIW */
|
|
cfmws->length = sizeof(acpi_cedt_cfmws_t) + 4 * niw;
|
|
|
|
cfmws->base_hpa = base_hpa;
|
|
cfmws->window_size = window_size;
|
|
cfmws->eniw = eniw;
|
|
|
|
// 0: Standard Modulo Arithmetic. Other values reserved.
|
|
cfmws->interleave_arithmetic = 0;
|
|
|
|
cfmws->hbig = hbig;
|
|
cfmws->restriction = restriction;
|
|
cfmws->qtg_id = qtg_id;
|
|
memcpy(&cfmws->interleave_target, interleave_target, 4 * niw);
|
|
|
|
return cfmws->length;
|
|
}
|
|
|
|
void acpi_create_cedt(acpi_cedt_t *cedt, unsigned long (*acpi_fill_cedt)(unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(cedt->header);
|
|
unsigned long current = (unsigned long)cedt + sizeof(acpi_cedt_t);
|
|
|
|
memset((void *)cedt, 0, sizeof(acpi_cedt_t));
|
|
|
|
if (acpi_fill_header(header, "CEDT", CEDT, sizeof(acpi_cedt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_cedt(current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)cedt;
|
|
header->checksum = acpi_checksum((void *)cedt, header->length);
|
|
}
|
|
|
|
int acpi_create_hmat_mpda(acpi_hmat_mpda_t *mpda, u32 initiator, u32 memory)
|
|
{
|
|
memset((void *)mpda, 0, sizeof(acpi_hmat_mpda_t));
|
|
|
|
mpda->type = 0; /* Memory Proximity Domain Attributes structure */
|
|
mpda->length = sizeof(acpi_hmat_mpda_t);
|
|
/*
|
|
* Proximity Domain for Attached Initiator field is valid.
|
|
* Bit 1 and bit 2 are reserved since HMAT revision 2.
|
|
*/
|
|
mpda->flags = (1 << 0);
|
|
mpda->proximity_domain_initiator = initiator;
|
|
mpda->proximity_domain_memory = memory;
|
|
|
|
return mpda->length;
|
|
}
|
|
|
|
void acpi_create_hmat(acpi_hmat_t *hmat,
|
|
unsigned long (*acpi_fill_hmat)(unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(hmat->header);
|
|
unsigned long current = (unsigned long)hmat + sizeof(acpi_hmat_t);
|
|
|
|
memset((void *)hmat, 0, sizeof(acpi_hmat_t));
|
|
|
|
if (acpi_fill_header(header, "HMAT", HMAT, sizeof(acpi_hmat_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_hmat(current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)hmat;
|
|
header->checksum = acpi_checksum((void *)hmat, header->length);
|
|
}
|
|
|
|
/* http://h21007.www2.hp.com/portal/download/files/unprot/Itanium/slit.pdf */
|
|
void acpi_create_slit(acpi_slit_t *slit,
|
|
unsigned long (*acpi_fill_slit)(unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(slit->header);
|
|
unsigned long current = (unsigned long)slit + sizeof(acpi_slit_t);
|
|
|
|
memset((void *)slit, 0, sizeof(acpi_slit_t));
|
|
|
|
if (acpi_fill_header(header, "SLIT", SLIT, sizeof(acpi_slit_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_slit(current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)slit;
|
|
header->checksum = acpi_checksum((void *)slit, header->length);
|
|
}
|
|
|
|
/*
|
|
* This method adds the ACPI error injection capability. It fills the default information.
|
|
* HW dependent code (caller) can modify the defaults upon return. If no changes are necessary
|
|
* and the defaults are acceptable then caller can simply add the table (acpi_add_table).
|
|
* INPUTS:
|
|
* einj - ptr to the starting location of EINJ table
|
|
* actions - number of actions to trigger an error (HW dependent)
|
|
* addr - address of trigger action table. This should be ACPI reserved memory and it will be
|
|
* shared between OS and FW.
|
|
*/
|
|
void acpi_create_einj(acpi_einj_t *einj, uintptr_t addr, u8 actions)
|
|
{
|
|
int i;
|
|
acpi_header_t *header = &(einj->header);
|
|
acpi_injection_header_t *inj_header = &(einj->inj_header);
|
|
acpi_einj_smi_t *einj_smi = (acpi_einj_smi_t *)addr;
|
|
acpi_einj_trigger_table_t *tat;
|
|
if (!header)
|
|
return;
|
|
|
|
printk(BIOS_DEBUG, "%s einj_smi = %p\n", __func__, einj_smi);
|
|
memset(einj_smi, 0, sizeof(acpi_einj_smi_t));
|
|
tat = (acpi_einj_trigger_table_t *)((uint8_t *)einj_smi + sizeof(acpi_einj_smi_t));
|
|
tat->header_size = 16;
|
|
tat->revision = 0;
|
|
tat->table_size = sizeof(acpi_einj_trigger_table_t) +
|
|
sizeof(acpi_einj_action_table_t) * actions - 1;
|
|
tat->entry_count = actions;
|
|
printk(BIOS_DEBUG, "%s trigger_action_table = %p\n", __func__, tat);
|
|
|
|
for (i = 0; i < actions; i++) {
|
|
tat->trigger_action[i].action = TRIGGER_ERROR;
|
|
tat->trigger_action[i].instruction = NO_OP;
|
|
tat->trigger_action[i].flags = FLAG_IGNORE;
|
|
tat->trigger_action[i].reg.space_id = ACPI_ADDRESS_SPACE_MEMORY;
|
|
tat->trigger_action[i].reg.bit_width = 64;
|
|
tat->trigger_action[i].reg.bit_offset = 0;
|
|
tat->trigger_action[i].reg.access_size = ACPI_ACCESS_SIZE_QWORD_ACCESS;
|
|
tat->trigger_action[i].reg.addr = 0;
|
|
tat->trigger_action[i].value = 0;
|
|
tat->trigger_action[i].mask = 0xFFFFFFFF;
|
|
}
|
|
|
|
acpi_einj_action_table_t default_actions[ACTION_COUNT] = {
|
|
[0] = {
|
|
.action = BEGIN_INJECT_OP,
|
|
.instruction = WRITE_REGISTER_VALUE,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_state),
|
|
.value = 0,
|
|
.mask = 0xFFFFFFFF
|
|
},
|
|
[1] = {
|
|
.action = GET_TRIGGER_ACTION_TABLE,
|
|
.instruction = READ_REGISTER,
|
|
.flags = FLAG_IGNORE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->trigger_action_table),
|
|
.value = 0,
|
|
.mask = 0xFFFFFFFFFFFFFFFF
|
|
},
|
|
[2] = {
|
|
.action = SET_ERROR_TYPE,
|
|
.instruction = WRITE_REGISTER,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->err_inject[0]),
|
|
.value = 0,
|
|
.mask = 0xFFFFFFFF
|
|
},
|
|
[3] = {
|
|
.action = GET_ERROR_TYPE,
|
|
.instruction = READ_REGISTER,
|
|
.flags = FLAG_IGNORE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->err_inj_cap),
|
|
.value = 0,
|
|
.mask = 0xFFFFFFFF
|
|
},
|
|
[4] = {
|
|
.action = END_INJECT_OP,
|
|
.instruction = WRITE_REGISTER_VALUE,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_state),
|
|
.value = 0,
|
|
.mask = 0xFFFFFFFF
|
|
|
|
},
|
|
[5] = {
|
|
.action = EXECUTE_INJECT_OP,
|
|
.instruction = WRITE_REGISTER_VALUE,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_IO(),
|
|
.value = 0x9a,
|
|
.mask = 0xFFFF,
|
|
},
|
|
[6] = {
|
|
.action = CHECK_BUSY_STATUS,
|
|
.instruction = READ_REGISTER_VALUE,
|
|
.flags = FLAG_IGNORE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_status),
|
|
.value = 1,
|
|
.mask = 1,
|
|
},
|
|
[7] = {
|
|
.action = GET_CMD_STATUS,
|
|
.instruction = READ_REGISTER,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->cmd_sts),
|
|
.value = 0,
|
|
.mask = 0x1fe,
|
|
},
|
|
[8] = {
|
|
.action = SET_ERROR_TYPE_WITH_ADDRESS,
|
|
.instruction = WRITE_REGISTER,
|
|
.flags = FLAG_PRESERVE,
|
|
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->setaddrtable),
|
|
.value = 1,
|
|
.mask = 0xffffffff
|
|
}
|
|
};
|
|
|
|
einj_smi->err_inj_cap = ACPI_EINJ_DEFAULT_CAP;
|
|
einj_smi->trigger_action_table = (u64)(uintptr_t)tat;
|
|
|
|
for (i = 0; i < ACTION_COUNT; i++)
|
|
printk(BIOS_DEBUG, "default_actions[%d].reg.addr is %llx\n", i,
|
|
default_actions[i].reg.addr);
|
|
|
|
memset((void *)einj, 0, sizeof(*einj));
|
|
|
|
if (acpi_fill_header(header, "EINJ", EINJ, sizeof(acpi_einj_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
inj_header->einj_header_size = sizeof(acpi_injection_header_t);
|
|
inj_header->entry_count = ACTION_COUNT;
|
|
|
|
printk(BIOS_DEBUG, "%s einj->action_table = %p\n",
|
|
__func__, einj->action_table);
|
|
memcpy((void *)einj->action_table, (void *)default_actions, sizeof(einj->action_table));
|
|
header->checksum = acpi_checksum((void *)einj, sizeof(*einj));
|
|
}
|
|
|
|
void acpi_create_vfct(const struct device *device,
|
|
acpi_vfct_t *vfct,
|
|
unsigned long (*acpi_fill_vfct)(const struct device *device,
|
|
acpi_vfct_t *vfct_struct, unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(vfct->header);
|
|
unsigned long current = (unsigned long)vfct + sizeof(acpi_vfct_t);
|
|
|
|
memset((void *)vfct, 0, sizeof(acpi_vfct_t));
|
|
|
|
if (acpi_fill_header(header, "VFCT", VFCT, sizeof(acpi_vfct_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_vfct(device, vfct, current);
|
|
|
|
/* If no BIOS image, return with header->length == 0. */
|
|
if (!vfct->VBIOSImageOffset)
|
|
return;
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)vfct;
|
|
header->checksum = acpi_checksum((void *)vfct, header->length);
|
|
}
|
|
|
|
void acpi_create_ipmi(const struct device *device,
|
|
struct acpi_spmi *spmi,
|
|
const u16 ipmi_revision,
|
|
const acpi_addr_t *addr,
|
|
const enum acpi_ipmi_interface_type type,
|
|
const s8 gpe_interrupt,
|
|
const u32 apic_interrupt,
|
|
const u32 uid)
|
|
{
|
|
acpi_header_t *header = &(spmi->header);
|
|
memset((void *)spmi, 0, sizeof(struct acpi_spmi));
|
|
|
|
if (acpi_fill_header(header, "SPMI", SPMI, sizeof(struct acpi_spmi)) != CB_SUCCESS)
|
|
return;
|
|
|
|
spmi->reserved = 1;
|
|
|
|
if (device->path.type == DEVICE_PATH_PCI) {
|
|
spmi->pci_device_flag = ACPI_IPMI_PCI_DEVICE_FLAG;
|
|
spmi->pci_segment_group = device->upstream->segment_group;
|
|
spmi->pci_bus = device->upstream->secondary;
|
|
spmi->pci_device = device->path.pci.devfn >> 3;
|
|
spmi->pci_function = device->path.pci.devfn & 0x7;
|
|
} else if (type != IPMI_INTERFACE_SSIF) {
|
|
memcpy(spmi->uid, &uid, sizeof(spmi->uid));
|
|
}
|
|
|
|
spmi->base_address = *addr;
|
|
spmi->specification_revision = ipmi_revision;
|
|
|
|
spmi->interface_type = type;
|
|
|
|
if (gpe_interrupt >= 0 && gpe_interrupt < 32) {
|
|
spmi->gpe = gpe_interrupt;
|
|
spmi->interrupt_type |= ACPI_IPMI_INT_TYPE_SCI;
|
|
}
|
|
if (apic_interrupt > 0) {
|
|
spmi->global_system_interrupt = apic_interrupt;
|
|
spmi->interrupt_type |= ACPI_IPMI_INT_TYPE_APIC;
|
|
}
|
|
|
|
/* Calculate checksum. */
|
|
header->checksum = acpi_checksum((void *)spmi, header->length);
|
|
}
|
|
|
|
void acpi_create_ivrs(acpi_ivrs_t *ivrs,
|
|
unsigned long (*acpi_fill_ivrs)(acpi_ivrs_t *ivrs_struct,
|
|
unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(ivrs->header);
|
|
unsigned long current = (unsigned long)ivrs + sizeof(acpi_ivrs_t);
|
|
|
|
memset((void *)ivrs, 0, sizeof(acpi_ivrs_t));
|
|
|
|
if (acpi_fill_header(header, "IVRS", IVRS, sizeof(acpi_ivrs_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_ivrs(ivrs, current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)ivrs;
|
|
header->checksum = acpi_checksum((void *)ivrs, header->length);
|
|
}
|
|
|
|
void acpi_create_crat(struct acpi_crat_header *crat,
|
|
unsigned long (*acpi_fill_crat)(struct acpi_crat_header *crat_struct,
|
|
unsigned long current))
|
|
{
|
|
acpi_header_t *header = &(crat->header);
|
|
unsigned long current = (unsigned long)crat + sizeof(struct acpi_crat_header);
|
|
|
|
memset((void *)crat, 0, sizeof(struct acpi_crat_header));
|
|
|
|
if (acpi_fill_header(header, "CRAT", CRAT, sizeof(struct acpi_crat_header)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_crat(crat, current);
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
header->length = current - (unsigned long)crat;
|
|
header->checksum = acpi_checksum((void *)crat, header->length);
|
|
}
|
|
|
|
static void acpi_create_dbg2(acpi_dbg2_header_t *dbg2,
|
|
int port_type, int port_subtype,
|
|
acpi_addr_t *address, uint32_t address_size,
|
|
const char *device_path)
|
|
{
|
|
uintptr_t current;
|
|
acpi_dbg2_device_t *device;
|
|
uint32_t *dbg2_addr_size;
|
|
acpi_header_t *header;
|
|
size_t path_len;
|
|
const char *path;
|
|
char *namespace;
|
|
|
|
/* Fill out header fields. */
|
|
current = (uintptr_t)dbg2;
|
|
memset(dbg2, 0, sizeof(acpi_dbg2_header_t));
|
|
header = &(dbg2->header);
|
|
|
|
if (acpi_fill_header(header, "DBG2", DBG2, sizeof(acpi_dbg2_header_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
/* One debug device defined */
|
|
dbg2->devices_offset = sizeof(acpi_dbg2_header_t);
|
|
dbg2->devices_count = 1;
|
|
current += sizeof(acpi_dbg2_header_t);
|
|
|
|
/* Device comes after the header */
|
|
device = (acpi_dbg2_device_t *)current;
|
|
memset(device, 0, sizeof(acpi_dbg2_device_t));
|
|
current += sizeof(acpi_dbg2_device_t);
|
|
|
|
device->revision = 0;
|
|
device->address_count = 1;
|
|
device->port_type = port_type;
|
|
device->port_subtype = port_subtype;
|
|
|
|
/* Base Address comes after device structure */
|
|
memcpy((void *)current, address, sizeof(acpi_addr_t));
|
|
device->base_address_offset = current - (uintptr_t)device;
|
|
current += sizeof(acpi_addr_t);
|
|
|
|
/* Address Size comes after address structure */
|
|
dbg2_addr_size = (uint32_t *)current;
|
|
device->address_size_offset = current - (uintptr_t)device;
|
|
*dbg2_addr_size = address_size;
|
|
current += sizeof(uint32_t);
|
|
|
|
/* Namespace string comes last, use '.' if not provided */
|
|
path = device_path ? : ".";
|
|
/* Namespace string length includes NULL terminator */
|
|
path_len = strlen(path) + 1;
|
|
namespace = (char *)current;
|
|
device->namespace_string_length = path_len;
|
|
device->namespace_string_offset = current - (uintptr_t)device;
|
|
strncpy(namespace, path, path_len);
|
|
current += path_len;
|
|
|
|
/* Update structure lengths and checksum */
|
|
device->length = current - (uintptr_t)device;
|
|
header->length = current - (uintptr_t)dbg2;
|
|
header->checksum = acpi_checksum((uint8_t *)dbg2, header->length);
|
|
}
|
|
|
|
static unsigned long acpi_write_dbg2_uart(acpi_rsdp_t *rsdp, unsigned long current,
|
|
int space_id, uint64_t base, uint32_t size,
|
|
int access_size, const char *name)
|
|
{
|
|
acpi_dbg2_header_t *dbg2 = (acpi_dbg2_header_t *)current;
|
|
acpi_addr_t address;
|
|
|
|
memset(&address, 0, sizeof(address));
|
|
|
|
address.space_id = space_id;
|
|
address.addrl = (uint32_t)base;
|
|
address.addrh = (uint32_t)((base >> 32) & 0xffffffff);
|
|
address.access_size = access_size;
|
|
|
|
int subtype;
|
|
/* 16550-compatible with parameters defined in Generic Address Structure */
|
|
if (CONFIG(DRIVERS_UART_8250IO) || CONFIG(DRIVERS_UART_8250MEM))
|
|
subtype = ACPI_DBG2_PORT_SERIAL_16550;
|
|
else if (CONFIG(DRIVERS_UART_PL011))
|
|
subtype = ACPI_DBG2_PORT_SERIAL_ARM_PL011;
|
|
else
|
|
return current;
|
|
|
|
acpi_create_dbg2(dbg2,
|
|
ACPI_DBG2_PORT_SERIAL,
|
|
subtype,
|
|
&address, size,
|
|
name);
|
|
|
|
if (dbg2->header.length) {
|
|
current += dbg2->header.length;
|
|
current = acpi_align_current(current);
|
|
acpi_add_table(rsdp, dbg2);
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
unsigned long acpi_write_dbg2_pci_uart(acpi_rsdp_t *rsdp, unsigned long current,
|
|
const struct device *dev, uint8_t access_size)
|
|
{
|
|
struct resource *res;
|
|
|
|
if (!dev) {
|
|
printk(BIOS_DEBUG, "%s: Device not found\n", __func__);
|
|
return current;
|
|
}
|
|
if (!dev->enabled) {
|
|
printk(BIOS_INFO, "%s: Device not enabled\n", __func__);
|
|
return current;
|
|
}
|
|
res = probe_resource(dev, PCI_BASE_ADDRESS_0);
|
|
if (!res) {
|
|
printk(BIOS_ERR, "%s: Unable to find resource for %s\n",
|
|
__func__, dev_path(dev));
|
|
return current;
|
|
}
|
|
|
|
int space_id;
|
|
if (res->flags & IORESOURCE_IO)
|
|
space_id = ACPI_ADDRESS_SPACE_IO;
|
|
else if (res->flags & IORESOURCE_MEM)
|
|
space_id = ACPI_ADDRESS_SPACE_MEMORY;
|
|
else {
|
|
printk(BIOS_ERR, "%s: Unknown address space type\n", __func__);
|
|
return current;
|
|
}
|
|
|
|
return acpi_write_dbg2_uart(rsdp, current, space_id, res->base, res->size, access_size, acpi_device_path(dev));
|
|
}
|
|
|
|
unsigned long acpi_pl011_write_dbg2_uart(acpi_rsdp_t *rsdp, unsigned long current,
|
|
uint64_t base, const char *name)
|
|
{
|
|
return acpi_write_dbg2_uart(rsdp, current, ACPI_ADDRESS_SPACE_MEMORY, base,
|
|
sizeof(struct pl011_uart), ACPI_ACCESS_SIZE_DWORD_ACCESS,
|
|
name);
|
|
}
|
|
|
|
unsigned long acpi_16550_mmio32_write_dbg2_uart(acpi_rsdp_t *rsdp, unsigned long current,
|
|
uint64_t base, const char *name)
|
|
{
|
|
return acpi_write_dbg2_uart(rsdp, current, ACPI_ADDRESS_SPACE_MEMORY, base,
|
|
0x100, ACPI_ACCESS_SIZE_DWORD_ACCESS,
|
|
name);
|
|
}
|
|
|
|
static void acpi_create_facs(void *header)
|
|
{
|
|
acpi_facs_t *facs = header;
|
|
|
|
memcpy(facs->signature, "FACS", 4);
|
|
facs->length = sizeof(acpi_facs_t);
|
|
facs->hardware_signature = 0;
|
|
facs->firmware_waking_vector = 0;
|
|
facs->global_lock = 0;
|
|
facs->flags = 0;
|
|
facs->x_firmware_waking_vector_l = 0;
|
|
facs->x_firmware_waking_vector_h = 0;
|
|
facs->version = get_acpi_table_revision(FACS);
|
|
}
|
|
|
|
static void acpi_write_rsdt(acpi_rsdt_t *rsdt, char *oem_id, char *oem_table_id)
|
|
{
|
|
acpi_header_t *header = &(rsdt->header);
|
|
|
|
if (acpi_fill_header(header, "RSDT", RSDT, sizeof(acpi_rsdt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
/* Entries are filled in later, we come with an empty set. */
|
|
|
|
/* Fix checksum. */
|
|
header->checksum = acpi_checksum((void *)rsdt, sizeof(acpi_rsdt_t));
|
|
}
|
|
|
|
static void acpi_write_xsdt(acpi_xsdt_t *xsdt, char *oem_id, char *oem_table_id)
|
|
{
|
|
acpi_header_t *header = &(xsdt->header);
|
|
|
|
if (acpi_fill_header(header, "XSDT", XSDT, sizeof(acpi_xsdt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
/* Entries are filled in later, we come with an empty set. */
|
|
|
|
/* Fix checksum. */
|
|
header->checksum = acpi_checksum((void *)xsdt, sizeof(acpi_xsdt_t));
|
|
}
|
|
|
|
static void acpi_write_rsdp(acpi_rsdp_t *rsdp, acpi_rsdt_t *rsdt,
|
|
acpi_xsdt_t *xsdt, char *oem_id)
|
|
{
|
|
memset(rsdp, 0, sizeof(acpi_rsdp_t));
|
|
|
|
memcpy(rsdp->signature, RSDP_SIG, 8);
|
|
memcpy(rsdp->oem_id, oem_id, 6);
|
|
|
|
rsdp->length = sizeof(acpi_rsdp_t);
|
|
rsdp->rsdt_address = (uintptr_t)rsdt;
|
|
|
|
/*
|
|
* Revision: ACPI 1.0: 0, ACPI 2.0/3.0/4.0: 2.
|
|
*
|
|
* Some OSes expect an XSDT to be present for RSD PTR revisions >= 2.
|
|
* If we don't have an ACPI XSDT, force ACPI 1.0 (and thus RSD PTR
|
|
* revision 0).
|
|
*/
|
|
if (xsdt == NULL) {
|
|
rsdp->revision = 0;
|
|
} else {
|
|
rsdp->xsdt_address = (u64)(uintptr_t)xsdt;
|
|
rsdp->revision = get_acpi_table_revision(RSDP);
|
|
}
|
|
|
|
/* Calculate checksums. */
|
|
rsdp->checksum = acpi_checksum((void *)rsdp, 20);
|
|
rsdp->ext_checksum = acpi_checksum((void *)rsdp, sizeof(acpi_rsdp_t));
|
|
}
|
|
|
|
unsigned long acpi_create_hest_error_source(acpi_hest_t *hest,
|
|
acpi_hest_esd_t *esd, u16 type, void *data, u16 data_len)
|
|
{
|
|
acpi_header_t *header = &(hest->header);
|
|
acpi_hest_hen_t *hen;
|
|
void *pos;
|
|
u16 len;
|
|
|
|
pos = esd;
|
|
memset(pos, 0, sizeof(acpi_hest_esd_t));
|
|
len = 0;
|
|
esd->type = type; /* MCE */
|
|
esd->source_id = hest->error_source_count;
|
|
esd->flags = 0; /* FIRMWARE_FIRST */
|
|
esd->enabled = 1;
|
|
esd->prealloc_erecords = 1;
|
|
esd->max_section_per_record = 0x1;
|
|
|
|
len += sizeof(acpi_hest_esd_t);
|
|
pos = esd + 1;
|
|
|
|
switch (type) {
|
|
case 0: /* MCE */
|
|
break;
|
|
case 1: /* CMC */
|
|
hen = (acpi_hest_hen_t *)(pos);
|
|
memset(pos, 0, sizeof(acpi_hest_hen_t));
|
|
hen->type = 3; /* SCI? */
|
|
hen->length = sizeof(acpi_hest_hen_t);
|
|
hen->conf_we = 0; /* Configuration Write Enable. */
|
|
hen->poll_interval = 0;
|
|
hen->vector = 0;
|
|
hen->sw2poll_threshold_val = 0;
|
|
hen->sw2poll_threshold_win = 0;
|
|
hen->error_threshold_val = 0;
|
|
hen->error_threshold_win = 0;
|
|
len += sizeof(acpi_hest_hen_t);
|
|
pos = hen + 1;
|
|
break;
|
|
case 2: /* NMI */
|
|
case 6: /* AER Root Port */
|
|
case 7: /* AER Endpoint */
|
|
case 8: /* AER Bridge */
|
|
case 9: /* Generic Hardware Error Source. */
|
|
/* TODO: */
|
|
break;
|
|
default:
|
|
printk(BIOS_DEBUG, "Invalid type of Error Source.");
|
|
break;
|
|
}
|
|
hest->error_source_count++;
|
|
|
|
memcpy(pos, data, data_len);
|
|
len += data_len;
|
|
if (header)
|
|
header->length += len;
|
|
|
|
return len;
|
|
}
|
|
|
|
/* ACPI 4.0 */
|
|
void acpi_write_hest(acpi_hest_t *hest,
|
|
unsigned long (*acpi_fill_hest)(acpi_hest_t *hest))
|
|
{
|
|
acpi_header_t *header = &(hest->header);
|
|
|
|
memset(hest, 0, sizeof(acpi_hest_t));
|
|
|
|
if (acpi_fill_header(header, "HEST", HEST, sizeof(acpi_hest_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
acpi_fill_hest(hest);
|
|
|
|
/* Calculate checksums. */
|
|
header->checksum = acpi_checksum((void *)hest, header->length);
|
|
}
|
|
|
|
/* ACPI 3.0b */
|
|
static void acpi_create_bert(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_BERT))
|
|
return;
|
|
|
|
acpi_bert_t *bert = (acpi_bert_t *)header;
|
|
|
|
void *region;
|
|
size_t size;
|
|
if (acpi_soc_get_bert_region(®ion, &size) != CB_SUCCESS)
|
|
return;
|
|
|
|
if (acpi_fill_header(header, "BERT", BERT, sizeof(acpi_bert_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
bert->error_region = (uintptr_t)region;
|
|
bert->region_length = (size_t)size;
|
|
}
|
|
|
|
__weak void arch_fill_fadt(acpi_fadt_t *fadt) { }
|
|
__weak void soc_fill_fadt(acpi_fadt_t *fadt) { }
|
|
__weak void mainboard_fill_fadt(acpi_fadt_t *fadt) { }
|
|
|
|
static acpi_header_t *dsdt;
|
|
static void acpi_create_fadt(acpi_header_t *header, void *arg1)
|
|
{
|
|
acpi_fadt_t *fadt = (acpi_fadt_t *)header;
|
|
acpi_facs_t *facs = (acpi_facs_t *)(*(acpi_facs_t **)arg1);
|
|
|
|
if (acpi_fill_header(header, "FACP", FADT, sizeof(acpi_fadt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
fadt->FADT_MinorVersion = get_acpi_fadt_minor_version();
|
|
if ((uintptr_t)facs <= UINT32_MAX)
|
|
fadt->firmware_ctrl = (uintptr_t)facs;
|
|
else
|
|
fadt->x_firmware_ctl_h = (uint32_t)((uint64_t)(uintptr_t)facs >> 32);
|
|
fadt->x_firmware_ctl_l = (uint32_t)(uintptr_t)facs;
|
|
|
|
if ((uintptr_t)dsdt <= UINT32_MAX)
|
|
fadt->dsdt = (uintptr_t)dsdt;
|
|
else
|
|
fadt->x_dsdt_h = (uint32_t)((uint64_t)(uintptr_t)dsdt >> 32);
|
|
fadt->x_dsdt_l = (uint32_t)(uintptr_t)dsdt;
|
|
|
|
/* should be 0 ACPI 3.0 */
|
|
fadt->reserved = 0;
|
|
|
|
/* P_LVLx latencies are not used as CPU _CST will override them. */
|
|
fadt->p_lvl2_lat = ACPI_FADT_C2_NOT_SUPPORTED;
|
|
fadt->p_lvl3_lat = ACPI_FADT_C3_NOT_SUPPORTED;
|
|
|
|
/* Use CPU _PTC instead to provide P_CNT details. */
|
|
fadt->duty_offset = 0;
|
|
fadt->duty_width = 0;
|
|
|
|
fadt->preferred_pm_profile = acpi_get_preferred_pm_profile();
|
|
|
|
arch_fill_fadt(fadt);
|
|
|
|
acpi_fill_fadt(fadt);
|
|
|
|
soc_fill_fadt(fadt);
|
|
mainboard_fill_fadt(fadt);
|
|
}
|
|
|
|
static void acpi_create_lpit(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_LPIT))
|
|
return;
|
|
|
|
acpi_lpit_t *lpit = (acpi_lpit_t *)header;
|
|
unsigned long current = (unsigned long)lpit + sizeof(acpi_lpit_t);
|
|
|
|
if (acpi_fill_header(header, "LPIT", LPIT, sizeof(acpi_lpit_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_fill_lpit(current);
|
|
|
|
/* (Re)calculate length. */
|
|
header->length = current - (unsigned long)lpit;
|
|
}
|
|
|
|
static void acpi_create_gtdt(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_GTDT))
|
|
return;
|
|
|
|
acpi_gtdt_t *gtdt = (acpi_gtdt_t *)header;
|
|
unsigned long current = (unsigned long)gtdt + sizeof(acpi_gtdt_t);
|
|
|
|
if (acpi_fill_header(header, "GTDT", GTDT, sizeof(acpi_gtdt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
/* Fill out header fields. */
|
|
gtdt->platform_timer_offset = sizeof(acpi_gtdt_t);
|
|
|
|
acpi_soc_fill_gtdt(gtdt);
|
|
current = acpi_soc_gtdt_add_timers(>dt->platform_timer_count, current);
|
|
|
|
/* (Re)calculate length. */
|
|
header->length = current - (unsigned long)gtdt;
|
|
}
|
|
|
|
unsigned long acpi_gtdt_add_timer_block(unsigned long current, const uint64_t address,
|
|
struct acpi_gtdt_timer_entry *timers, size_t number)
|
|
{
|
|
struct acpi_gtdt_timer_block *block = (struct acpi_gtdt_timer_block *)current;
|
|
memset(block, 0, sizeof(struct acpi_gtdt_timer_block));
|
|
|
|
assert(number < 8 && number != 0);
|
|
const size_t entries_size = number * sizeof(struct acpi_gtdt_timer_entry);
|
|
|
|
block->header.type = ACPI_GTDT_TYPE_TIMER_BLOCK;
|
|
block->header.length = sizeof(struct acpi_gtdt_timer_block)
|
|
+ entries_size;
|
|
block->block_address = address;
|
|
block->timer_count = number;
|
|
block->timer_offset = sizeof(struct acpi_gtdt_timer_block);
|
|
current += sizeof(struct acpi_gtdt_timer_block);
|
|
memcpy((void *)current, timers, entries_size);
|
|
current += entries_size;
|
|
return current;
|
|
}
|
|
|
|
unsigned long acpi_gtdt_add_watchdog(unsigned long current, uint64_t refresh_frame,
|
|
uint64_t control_frame, uint32_t gsiv, uint32_t flags)
|
|
{
|
|
struct acpi_gtdt_watchdog *wd = (struct acpi_gtdt_watchdog *)current;
|
|
memset(wd, 0, sizeof(struct acpi_gtdt_watchdog));
|
|
|
|
wd->header.type = ACPI_GTDT_TYPE_WATCHDOG;
|
|
wd->header.length = sizeof(struct acpi_gtdt_watchdog);
|
|
wd->refresh_frame_address = refresh_frame;
|
|
wd->control_frame_address = control_frame;
|
|
wd->timer_interrupt = gsiv;
|
|
wd->timer_flags = flags;
|
|
|
|
return current + sizeof(struct acpi_gtdt_watchdog);
|
|
}
|
|
|
|
static void acpi_create_iort(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_IORT))
|
|
return;
|
|
|
|
acpi_iort_t *iort = (acpi_iort_t *)header;
|
|
unsigned long current = (unsigned long)iort + sizeof(acpi_iort_t);
|
|
|
|
if (acpi_fill_header(header, "IORT", IORT, sizeof(acpi_iort_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
iort->node_count = 0;
|
|
iort->node_offset = current - (unsigned long)iort;
|
|
|
|
current = acpi_soc_fill_iort(iort, current);
|
|
|
|
/* (Re)calculate length */
|
|
header->length = current - (unsigned long)iort;
|
|
}
|
|
|
|
static void acpi_create_wdat(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_WDAT_WDT))
|
|
return;
|
|
|
|
acpi_wdat_t *wdat = (acpi_wdat_t *)header;
|
|
unsigned long current = (unsigned long)wdat + sizeof(acpi_wdat_t);
|
|
|
|
memset((void *)wdat, 0, sizeof(acpi_wdat_t));
|
|
|
|
if (acpi_fill_header(header, "WDAT", WDAT, sizeof(acpi_wdat_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
current = acpi_soc_fill_wdat(wdat, current);
|
|
|
|
/* (Re)calculate length. */
|
|
header->length = current - (unsigned long)wdat;
|
|
}
|
|
|
|
unsigned long acpi_create_lpi_desc_ncst(acpi_lpi_desc_ncst_t *lpi_desc, uint16_t uid)
|
|
{
|
|
memset(lpi_desc, 0, sizeof(acpi_lpi_desc_ncst_t));
|
|
lpi_desc->header.length = sizeof(acpi_lpi_desc_ncst_t);
|
|
lpi_desc->header.type = ACPI_LPI_DESC_TYPE_NATIVE_CSTATE;
|
|
lpi_desc->header.uid = uid;
|
|
|
|
return lpi_desc->header.length;
|
|
}
|
|
|
|
static void acpi_create_pptt(acpi_header_t *header, void *unused)
|
|
{
|
|
if (!CONFIG(ACPI_PPTT))
|
|
return;
|
|
|
|
if (acpi_fill_header(header, "PPTT", PPTT, sizeof(acpi_pptt_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
acpi_pptt_t *pptt = (acpi_pptt_t *)header;
|
|
acpi_create_pptt_body(pptt);
|
|
}
|
|
|
|
static uint8_t acpi_spcr_type(void)
|
|
{
|
|
/* 16550-compatible with parameters defined in Generic Address Structure */
|
|
if (CONFIG(DRIVERS_UART_8250IO) || CONFIG(DRIVERS_UART_8250MEM))
|
|
return 0x12;
|
|
if (CONFIG(DRIVERS_UART_PL011))
|
|
return 0x3;
|
|
|
|
printk(BIOS_ERR, "%s: unknown serial type\n", __func__);
|
|
return 0xff;
|
|
}
|
|
|
|
static void acpi_create_spcr(acpi_header_t *header, void *unused)
|
|
{
|
|
acpi_spcr_t *spcr = (acpi_spcr_t *)header;
|
|
struct lb_serial serial;
|
|
|
|
if (!CONFIG(CONSOLE_SERIAL))
|
|
return;
|
|
|
|
if (fill_lb_serial(&serial) != CB_SUCCESS)
|
|
return;
|
|
|
|
if (acpi_fill_header(header, "SPCR", SPCR, sizeof(acpi_spcr_t)) != CB_SUCCESS)
|
|
return;
|
|
|
|
spcr->interface_type = acpi_spcr_type();
|
|
assert(serial.type == LB_SERIAL_TYPE_IO_MAPPED
|
|
|| serial.type == LB_SERIAL_TYPE_MEMORY_MAPPED);
|
|
spcr->base_address.space_id = serial.type == LB_SERIAL_TYPE_IO_MAPPED ?
|
|
ACPI_ADDRESS_SPACE_IO : ACPI_ADDRESS_SPACE_MEMORY;
|
|
spcr->base_address.bit_width = serial.regwidth * 8;
|
|
spcr->base_address.bit_offset = 0;
|
|
switch (serial.regwidth) {
|
|
case 1:
|
|
spcr->base_address.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS;
|
|
break;
|
|
case 2:
|
|
spcr->base_address.access_size = ACPI_ACCESS_SIZE_WORD_ACCESS;
|
|
break;
|
|
case 4:
|
|
spcr->base_address.access_size = ACPI_ACCESS_SIZE_DWORD_ACCESS;
|
|
break;
|
|
default:
|
|
printk(BIOS_ERR, "%s, Invalid serial regwidth\n", __func__);
|
|
}
|
|
|
|
spcr->base_address.addrl = serial.baseaddr;
|
|
spcr->base_address.addrh = 0;
|
|
spcr->interrupt_type = 0;
|
|
spcr->irq = 0;
|
|
spcr->configured_baudrate = 0; /* Have the OS use whatever is currently set */
|
|
spcr->parity = 0;
|
|
spcr->stop_bits = 1;
|
|
spcr->flow_control = 0;
|
|
spcr->terminal_type = 2; /* 2 = VT-UTF8 */
|
|
spcr->language = 0;
|
|
spcr->pci_did = 0xffff;
|
|
spcr->pci_vid = 0xffff;
|
|
|
|
header->checksum = acpi_checksum((void *)spcr, header->length);
|
|
}
|
|
|
|
unsigned long __weak fw_cfg_acpi_tables(unsigned long start)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void preload_acpi_dsdt(void)
|
|
{
|
|
const char *file = CONFIG_CBFS_PREFIX "/dsdt.aml";
|
|
|
|
if (!CONFIG(CBFS_PRELOAD))
|
|
return;
|
|
|
|
printk(BIOS_DEBUG, "Preloading %s\n", file);
|
|
cbfs_preload(file);
|
|
}
|
|
|
|
static void acpi_create_dsdt(acpi_header_t *header, void *dsdt_file_arg)
|
|
{
|
|
dsdt = header;
|
|
acpi_header_t *dsdt_file = *(acpi_header_t **)dsdt_file_arg;
|
|
unsigned long current = (unsigned long)header;
|
|
|
|
dsdt = (acpi_header_t *)current;
|
|
memcpy(dsdt, dsdt_file, sizeof(acpi_header_t));
|
|
if (dsdt->length >= sizeof(acpi_header_t)) {
|
|
current += sizeof(acpi_header_t);
|
|
|
|
acpigen_set_current((char *)current);
|
|
|
|
if (CONFIG(ACPI_SOC_NVS))
|
|
acpi_fill_gnvs();
|
|
if (CONFIG(CHROMEOS_NVS))
|
|
acpi_fill_cnvs();
|
|
|
|
current = (unsigned long)acpigen_get_current();
|
|
memcpy((char *)current,
|
|
(char *)dsdt_file + sizeof(acpi_header_t),
|
|
dsdt->length - sizeof(acpi_header_t));
|
|
current += dsdt->length - sizeof(acpi_header_t);
|
|
|
|
/* (Re)calculate length. */
|
|
dsdt->length = current - (unsigned long)dsdt;
|
|
}
|
|
}
|
|
|
|
static void acpi_create_slic(acpi_header_t *header, void *slic_file_arg)
|
|
{
|
|
acpi_header_t *slic_file = *(acpi_header_t **)slic_file_arg;
|
|
acpi_header_t *slic = header;
|
|
if (slic_file)
|
|
memcpy(slic, slic_file, slic_file->length);
|
|
}
|
|
|
|
static uintptr_t coreboot_rsdp;
|
|
|
|
uintptr_t get_coreboot_rsdp(void)
|
|
{
|
|
return coreboot_rsdp;
|
|
}
|
|
|
|
static void acpixtract_compatible_hexdump(const void *memory, size_t length)
|
|
{
|
|
size_t i, j;
|
|
uint8_t *line;
|
|
size_t num_bytes;
|
|
|
|
for (i = 0; i < length; i += 16) {
|
|
num_bytes = MIN(length - i, 16);
|
|
line = ((uint8_t *)memory) + i;
|
|
|
|
printk(BIOS_SPEW, " %04zX:", i);
|
|
for (j = 0; j < num_bytes; j++)
|
|
printk(BIOS_SPEW, " %02x", line[j]);
|
|
for (; j < 16; j++)
|
|
printk(BIOS_SPEW, " ");
|
|
printk(BIOS_SPEW, " ");
|
|
for (j = 0; j < num_bytes; j++)
|
|
printk(BIOS_SPEW, "%c",
|
|
isprint(line[j]) ? line[j] : '.');
|
|
printk(BIOS_SPEW, "\n");
|
|
}
|
|
}
|
|
|
|
static void acpidump_print(void *table_ptr)
|
|
{
|
|
if (table_ptr == NULL)
|
|
return;
|
|
const acpi_header_t *header = (acpi_header_t *)table_ptr;
|
|
const size_t table_size = header->length;
|
|
printk(BIOS_SPEW, "%.4s @ 0x0000000000000000\n", header->signature);
|
|
acpixtract_compatible_hexdump(table_ptr, table_size);
|
|
printk(BIOS_SPEW, "\n");
|
|
}
|
|
|
|
unsigned long write_acpi_tables(const unsigned long start)
|
|
{
|
|
unsigned long current;
|
|
acpi_rsdp_t *rsdp;
|
|
acpi_rsdt_t *rsdt = NULL;
|
|
acpi_xsdt_t *xsdt = NULL;
|
|
acpi_facs_t *facs = NULL;
|
|
acpi_header_t *slic_file;
|
|
acpi_header_t *ssdt = NULL;
|
|
acpi_header_t *dsdt_file;
|
|
struct device *dev;
|
|
unsigned long fw;
|
|
size_t slic_size, dsdt_size;
|
|
char oem_id[6], oem_table_id[8];
|
|
|
|
const struct acpi_table_generator {
|
|
void (*create_table)(acpi_header_t *table, void *arg);
|
|
void *args;
|
|
size_t min_size;
|
|
} tables[] = {
|
|
{ acpi_create_dsdt, &dsdt_file, sizeof(acpi_header_t) },
|
|
{ acpi_create_fadt, &facs, sizeof(acpi_fadt_t) },
|
|
{ acpi_create_slic, &slic_file, sizeof(acpi_header_t) },
|
|
{ acpi_create_ssdt_generator, NULL, sizeof(acpi_header_t) },
|
|
{ acpi_create_mcfg, NULL, sizeof(acpi_mcfg_t) },
|
|
{ acpi_create_tcpa, NULL, sizeof(acpi_tcpa_t) },
|
|
{ acpi_create_tpm2, NULL, sizeof(acpi_tpm2_t) },
|
|
{ acpi_create_lpit, NULL, sizeof(acpi_lpit_t) },
|
|
{ acpi_create_madt, NULL, sizeof(acpi_header_t) },
|
|
{ acpi_create_bert, NULL, sizeof(acpi_bert_t) },
|
|
{ acpi_create_spcr, NULL, sizeof(acpi_spcr_t) },
|
|
{ acpi_create_gtdt, NULL, sizeof(acpi_gtdt_t) },
|
|
{ acpi_create_pptt, NULL, sizeof(acpi_pptt_t) },
|
|
{ acpi_create_iort, NULL, sizeof(acpi_iort_t) },
|
|
{ acpi_create_wdat, NULL, sizeof(acpi_wdat_t) },
|
|
};
|
|
|
|
current = start;
|
|
|
|
/* Align ACPI tables to 16byte */
|
|
current = acpi_align_current(current);
|
|
|
|
/* Special case for qemu */
|
|
fw = fw_cfg_acpi_tables(current);
|
|
if (fw) {
|
|
rsdp = NULL;
|
|
/* Find RSDP. */
|
|
for (void *p = (void *)current; p < (void *)fw; p += 16) {
|
|
if (valid_rsdp((acpi_rsdp_t *)p)) {
|
|
rsdp = p;
|
|
coreboot_rsdp = (uintptr_t)rsdp;
|
|
break;
|
|
}
|
|
}
|
|
if (!rsdp)
|
|
return fw;
|
|
|
|
current = fw;
|
|
current = acpi_align_current(current);
|
|
if (rsdp->xsdt_address == 0) {
|
|
acpi_rsdt_t *existing_rsdt = (acpi_rsdt_t *)(uintptr_t)rsdp->rsdt_address;
|
|
|
|
/*
|
|
* Qemu only provides a smaller ACPI 1.0 RSDP, thus
|
|
* allocate a bigger ACPI 2.0 RSDP structure.
|
|
*/
|
|
rsdp = (acpi_rsdp_t *)current;
|
|
current += sizeof(acpi_rsdp_t);
|
|
coreboot_rsdp = (uintptr_t)rsdp;
|
|
|
|
xsdt = (acpi_xsdt_t *)current;
|
|
current += sizeof(acpi_xsdt_t);
|
|
current = acpi_align_current(current);
|
|
|
|
/*
|
|
* Qemu only creates an RSDT.
|
|
* Add an XSDT based on the existing RSDT entries.
|
|
*/
|
|
acpi_write_rsdp(rsdp, existing_rsdt, xsdt, oem_id);
|
|
acpi_write_xsdt(xsdt, oem_id, oem_table_id);
|
|
/*
|
|
* Copy existing entries to the new XSDT. This will override existing
|
|
* RSDT entries with the same value.
|
|
*/
|
|
for (int i = 0; existing_rsdt->entry[i]; i++)
|
|
acpi_add_table(rsdp, (void *)(uintptr_t)existing_rsdt->entry[i]);
|
|
}
|
|
|
|
/* Add BOOT0000 for Linux google firmware driver */
|
|
printk(BIOS_DEBUG, "ACPI: * SSDT\n");
|
|
ssdt = (acpi_header_t *)current;
|
|
current += sizeof(acpi_header_t);
|
|
|
|
memset((void *)ssdt, 0, sizeof(acpi_header_t));
|
|
|
|
memcpy(&ssdt->signature, "SSDT", 4);
|
|
ssdt->revision = get_acpi_table_revision(SSDT);
|
|
memcpy(&ssdt->oem_id, OEM_ID, 6);
|
|
memcpy(&ssdt->oem_table_id, oem_table_id, 8);
|
|
ssdt->oem_revision = 42;
|
|
memcpy(&ssdt->asl_compiler_id, ASLC, 4);
|
|
ssdt->asl_compiler_revision = asl_revision;
|
|
ssdt->length = sizeof(acpi_header_t);
|
|
|
|
acpigen_set_current((char *)current);
|
|
|
|
/* Write object to declare coreboot tables */
|
|
acpi_ssdt_write_cbtable();
|
|
|
|
/* (Re)calculate length and checksum. */
|
|
ssdt->length = current - (unsigned long)ssdt;
|
|
ssdt->checksum = acpi_checksum((void *)ssdt, ssdt->length);
|
|
|
|
acpi_create_ssdt_generator(ssdt, NULL);
|
|
|
|
acpi_add_table(rsdp, ssdt);
|
|
|
|
return current;
|
|
}
|
|
|
|
dsdt_file = cbfs_map(CONFIG_CBFS_PREFIX "/dsdt.aml", &dsdt_size);
|
|
if (!dsdt_file) {
|
|
printk(BIOS_ERR, "No DSDT file, skipping ACPI tables\n");
|
|
return start;
|
|
}
|
|
|
|
if (dsdt_file->length > dsdt_size
|
|
|| dsdt_file->length < sizeof(acpi_header_t)
|
|
|| memcmp(dsdt_file->signature, "DSDT", 4) != 0) {
|
|
printk(BIOS_ERR, "Invalid DSDT file, skipping ACPI tables\n");
|
|
cbfs_unmap(dsdt_file);
|
|
return start;
|
|
}
|
|
|
|
slic_file = cbfs_map(CONFIG_CBFS_PREFIX "/slic", &slic_size);
|
|
if (slic_file
|
|
&& (slic_file->length > slic_size
|
|
|| slic_file->length < sizeof(acpi_header_t)
|
|
|| (memcmp(slic_file->signature, "SLIC", 4) != 0
|
|
&& memcmp(slic_file->signature, "MSDM", 4) != 0))) {
|
|
cbfs_unmap(slic_file);
|
|
slic_file = 0;
|
|
}
|
|
|
|
if (slic_file) {
|
|
memcpy(oem_id, slic_file->oem_id, 6);
|
|
memcpy(oem_table_id, slic_file->oem_table_id, 8);
|
|
} else {
|
|
memcpy(oem_id, OEM_ID, 6);
|
|
memcpy(oem_table_id, ACPI_TABLE_CREATOR, 8);
|
|
}
|
|
|
|
printk(BIOS_INFO, "ACPI: Writing ACPI tables at %lx.\n", start);
|
|
|
|
/* We need at least an RSDP, RSDT for ACPI 1.0 compat, otherwise XSDT */
|
|
rsdp = (acpi_rsdp_t *)current;
|
|
coreboot_rsdp = (uintptr_t)rsdp;
|
|
current += sizeof(acpi_rsdp_t);
|
|
current = acpi_align_current(current);
|
|
if (current + sizeof(acpi_rsdt_t) - 1 <= UINT32_MAX) {
|
|
rsdt = (acpi_rsdt_t *)current;
|
|
current += sizeof(acpi_rsdt_t);
|
|
current = acpi_align_current(current);
|
|
} else {
|
|
printk(BIOS_INFO, "Not adding RSDT because tables reside above 4G.");
|
|
}
|
|
|
|
xsdt = (acpi_xsdt_t *)current;
|
|
current += sizeof(acpi_xsdt_t);
|
|
current = acpi_align_current(current);
|
|
|
|
/* clear all table memory */
|
|
memset((void *)start, 0, current - start);
|
|
|
|
acpi_write_rsdp(rsdp, rsdt, xsdt, oem_id);
|
|
acpi_write_rsdt(rsdt, oem_id, oem_table_id);
|
|
acpi_write_xsdt(xsdt, oem_id, oem_table_id);
|
|
|
|
if (ENV_X86) {
|
|
printk(BIOS_DEBUG, "ACPI: * FACS\n");
|
|
current = ALIGN_UP(current, 64);
|
|
facs = (acpi_facs_t *)current;
|
|
current += sizeof(acpi_facs_t);
|
|
current = acpi_align_current(current);
|
|
acpi_create_facs(facs);
|
|
}
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(tables); i++) {
|
|
acpi_header_t *header = (acpi_header_t *)current;
|
|
memset(header, 0, tables[i].min_size);
|
|
tables[i].create_table(header, tables[i].args);
|
|
if (header->length < tables[i].min_size)
|
|
continue;
|
|
header->checksum = 0;
|
|
header->checksum = acpi_checksum((void *)header, header->length);
|
|
current += header->length;
|
|
current = acpi_align_current(current);
|
|
|
|
if (tables[i].create_table == acpi_create_dsdt)
|
|
continue;
|
|
|
|
printk(BIOS_DEBUG, "ACPI: * %.4s\n", header->signature);
|
|
acpi_add_table(rsdp, header);
|
|
}
|
|
|
|
/*
|
|
* cbfs_unmap() uses mem_pool_free() which works correctly only
|
|
* if freeing is done in reverse order than memory allocation.
|
|
* This is why unmapping of dsdt_file must be done after
|
|
* unmapping slic file.
|
|
*/
|
|
cbfs_unmap(slic_file);
|
|
cbfs_unmap(dsdt_file);
|
|
|
|
printk(BIOS_DEBUG, "current = %lx\n", current);
|
|
|
|
for (dev = all_devices; dev; dev = dev->next) {
|
|
if (dev->ops && dev->ops->write_acpi_tables) {
|
|
current = dev->ops->write_acpi_tables(dev, current,
|
|
rsdp);
|
|
current = acpi_align_current(current);
|
|
}
|
|
}
|
|
|
|
printk(BIOS_INFO, "ACPI: done.\n");
|
|
|
|
if (CONFIG(DEBUG_ACPICA_COMPATIBLE)) {
|
|
printk(BIOS_DEBUG, "Printing ACPI tables in ACPICA compatible format\n");
|
|
if (facs)
|
|
acpidump_print(facs);
|
|
acpidump_print(dsdt);
|
|
for (size_t i = 0; xsdt->entry[i] != 0; i++) {
|
|
acpidump_print((void *)(uintptr_t)xsdt->entry[i]);
|
|
}
|
|
printk(BIOS_DEBUG, "Done printing ACPI tables in ACPICA compatible format\n");
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
static acpi_rsdp_t *valid_rsdp(acpi_rsdp_t *rsdp)
|
|
{
|
|
if (strncmp((char *)rsdp, RSDP_SIG, sizeof(RSDP_SIG) - 1) != 0)
|
|
return NULL;
|
|
|
|
printk(BIOS_DEBUG, "Looking on %p for valid checksum\n", rsdp);
|
|
|
|
if (acpi_checksum((void *)rsdp, 20) != 0)
|
|
return NULL;
|
|
printk(BIOS_DEBUG, "Checksum 1 passed\n");
|
|
|
|
if ((rsdp->revision > 1) && (acpi_checksum((void *)rsdp,
|
|
rsdp->length) != 0))
|
|
return NULL;
|
|
printk(BIOS_DEBUG, "Checksum 2 passed all OK\n");
|
|
|
|
return rsdp;
|
|
}
|
|
|
|
void *acpi_find_wakeup_vector(void)
|
|
{
|
|
char *p, *end;
|
|
acpi_xsdt_t *xsdt;
|
|
acpi_facs_t *facs;
|
|
acpi_fadt_t *fadt = NULL;
|
|
acpi_rsdp_t *rsdp = NULL;
|
|
void *wake_vec;
|
|
int i;
|
|
|
|
if (!acpi_is_wakeup_s3())
|
|
return NULL;
|
|
|
|
printk(BIOS_DEBUG, "Trying to find the wakeup vector...\n");
|
|
|
|
/* Find RSDP. */
|
|
for (p = (char *)0xe0000; p < (char *)0xfffff; p += 16) {
|
|
rsdp = valid_rsdp((acpi_rsdp_t *)p);
|
|
if (rsdp)
|
|
break;
|
|
}
|
|
|
|
if (rsdp == NULL) {
|
|
printk(BIOS_ALERT,
|
|
"No RSDP found, wake up from S3 not possible.\n");
|
|
return NULL;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "RSDP found at %p\n", rsdp);
|
|
xsdt = (acpi_xsdt_t *)(uintptr_t)rsdp->xsdt_address;
|
|
|
|
end = (char *)xsdt + xsdt->header.length;
|
|
printk(BIOS_DEBUG, "XSDT found at %p ends at %p\n", xsdt, end);
|
|
|
|
for (i = 0; ((char *)&xsdt->entry[i]) < end; i++) {
|
|
fadt = (acpi_fadt_t *)(uintptr_t)xsdt->entry[i];
|
|
if (strncmp((char *)fadt, "FACP", 4) == 0)
|
|
break;
|
|
fadt = NULL;
|
|
}
|
|
|
|
if (fadt == NULL) {
|
|
printk(BIOS_ALERT,
|
|
"No FADT found, wake up from S3 not possible.\n");
|
|
return NULL;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "FADT found at %p\n", fadt);
|
|
facs = (acpi_facs_t *)(uintptr_t)((uint64_t)fadt->x_firmware_ctl_l
|
|
| (uint64_t)fadt->x_firmware_ctl_h << 32);
|
|
|
|
if (facs == NULL) {
|
|
printk(BIOS_ALERT,
|
|
"No FACS found, wake up from S3 not possible.\n");
|
|
return NULL;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "FACS found at %p\n", facs);
|
|
wake_vec = (void *)(uintptr_t)facs->firmware_waking_vector;
|
|
printk(BIOS_DEBUG, "OS waking vector is %p\n", wake_vec);
|
|
|
|
return wake_vec;
|
|
}
|
|
|
|
__weak int acpi_get_gpe(int gpe)
|
|
{
|
|
return -1; /* implemented by SOC */
|
|
}
|
|
|
|
u8 get_acpi_fadt_minor_version(void)
|
|
{
|
|
return ACPI_FADT_MINOR_VERSION_0;
|
|
}
|
|
|
|
int get_acpi_table_revision(enum acpi_tables table)
|
|
{
|
|
switch (table) {
|
|
case FADT:
|
|
return ACPI_FADT_REV_ACPI_6;
|
|
case MADT: /* ACPI 3.0: 2, ACPI 4.0/5.0: 3, ACPI 6.2b/6.3: 5 */
|
|
return 3;
|
|
case MCFG:
|
|
return 1;
|
|
case TCPA:
|
|
return 2;
|
|
case TPM2:
|
|
return 4;
|
|
case SSDT: /* ACPI 3.0 up to 6.3: 2 */
|
|
return 2;
|
|
case SRAT: /* ACPI 2.0: 1, ACPI 3.0: 2, ACPI 4.0 up to 6.4: 3 */
|
|
return 3;
|
|
case HMAT: /* ACPI 6.4: 2 */
|
|
return 2;
|
|
case DMAR:
|
|
return 1;
|
|
case SLIT: /* ACPI 2.0 up to 6.3: 1 */
|
|
return 1;
|
|
case SPMI: /* IMPI 2.0 */
|
|
return 5;
|
|
case HPET: /* Currently 1. Table added in ACPI 2.0. */
|
|
return 1;
|
|
case VFCT: /* ACPI 2.0/3.0/4.0: 1 */
|
|
return 1;
|
|
case IVRS:
|
|
return IVRS_FORMAT_MIXED;
|
|
case DBG2:
|
|
return 0;
|
|
case FACS: /* ACPI 2.0/3.0: 1, ACPI 4.0 up to 6.3: 2 */
|
|
return 1;
|
|
case RSDT: /* ACPI 1.0 up to 6.3: 1 */
|
|
return 1;
|
|
case XSDT: /* ACPI 2.0 up to 6.3: 1 */
|
|
return 1;
|
|
case RSDP: /* ACPI 2.0 up to 6.3: 2 */
|
|
return 2;
|
|
case EINJ:
|
|
return 1;
|
|
case HEST:
|
|
return 1;
|
|
case NHLT:
|
|
return 5;
|
|
case BERT:
|
|
return 1;
|
|
case CEDT: /* CXL 3.0 section 9.17.1 */
|
|
return 1;
|
|
case CRAT:
|
|
return 1;
|
|
case LPIT: /* ACPI 5.1 up to 6.3: 0 */
|
|
return 0;
|
|
case SPCR:
|
|
return 4;
|
|
case GTDT:
|
|
return 3;
|
|
case PPTT: /* ACPI 6.4 */
|
|
return 3;
|
|
case IORT: /* IO Remapping Table E.e */
|
|
return 6;
|
|
case WDAT:
|
|
return 1;
|
|
default:
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|