For QEMU we want to add new tables to the end of what QEMU provides. Add a function to find the correct place for a new table. Add a function to support this. Signed-off-by: Simon Glass <sjg@chromium.org>
305 lines
7.0 KiB
C
305 lines
7.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Utility functions for ACPI
|
|
*
|
|
* Copyright 2023 Google LLC
|
|
*/
|
|
|
|
#define LOG_CATEGORY LOGC_ACPI
|
|
|
|
#include <errno.h>
|
|
#include <mapmem.h>
|
|
#include <tables_csum.h>
|
|
#include <version_string.h>
|
|
#include <acpi/acpi_table.h>
|
|
#include <asm/global_data.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
/*
|
|
* OEM_REVISION is 32-bit unsigned number. It should be increased only when
|
|
* changing software version. Therefore it should not depend on build time.
|
|
* U-Boot calculates it from U-Boot version and represent it in hexadecimal
|
|
* notation. As U-Boot version is in form year.month set low 8 bits to 0x01
|
|
* to have valid date. So for U-Boot version 2021.04 OEM_REVISION is set to
|
|
* value 0x20210401.
|
|
*/
|
|
#define OEM_REVISION ((((version_num / 1000) % 10) << 28) | \
|
|
(((version_num / 100) % 10) << 24) | \
|
|
(((version_num / 10) % 10) << 20) | \
|
|
((version_num % 10) << 16) | \
|
|
(((version_num_patch / 10) % 10) << 12) | \
|
|
((version_num_patch % 10) << 8) | \
|
|
0x01)
|
|
|
|
void acpi_update_checksum(struct acpi_table_header *header)
|
|
{
|
|
header->checksum = 0;
|
|
header->checksum = table_compute_checksum(header, header->length);
|
|
}
|
|
|
|
static bool acpi_valid_rsdp(struct acpi_rsdp *rsdp)
|
|
{
|
|
if (strncmp((char *)rsdp, RSDP_SIG, sizeof(RSDP_SIG) - 1) != 0)
|
|
return false;
|
|
|
|
debug("Looking on %p for valid checksum\n", rsdp);
|
|
|
|
if (table_compute_checksum((void *)rsdp, 20) != 0)
|
|
return false;
|
|
debug("acpi rsdp checksum 1 passed\n");
|
|
|
|
if (rsdp->revision > 1 &&
|
|
table_compute_checksum((void *)rsdp, rsdp->length))
|
|
return false;
|
|
debug("acpi rsdp checksum 2 passed\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* setup_search() - Set up for searching through the RSDT/XSDT
|
|
*
|
|
* Looks for XSDT first and uses those entries if available, else RSDT
|
|
*
|
|
* @rsdtp: Returns RSDT if present
|
|
* @xsdtp: Returns XSDT if present
|
|
* Return: number of entries in table, -ENOENT if there is no RSDP, -EINVAL if
|
|
* the RSDP is invalid, -ENOTSYNC if both tables exist and their counts
|
|
* disagree
|
|
*/
|
|
static int setup_search(struct acpi_rsdt **rsdtp, struct acpi_xsdt **xsdtp)
|
|
{
|
|
struct acpi_rsdp *rsdp;
|
|
struct acpi_rsdt *rsdt = NULL;
|
|
struct acpi_xsdt *xsdt = NULL;
|
|
int len, count = 0;
|
|
|
|
|
|
rsdp = map_sysmem(gd_acpi_start(), 0);
|
|
if (!rsdp)
|
|
return -ENOENT;
|
|
if (!acpi_valid_rsdp(rsdp))
|
|
return -EINVAL;
|
|
if (rsdp->xsdt_address) {
|
|
xsdt = nomap_sysmem(rsdp->xsdt_address, 0);
|
|
len = xsdt->header.length - sizeof(xsdt->header);
|
|
count = len / sizeof(u64);
|
|
}
|
|
|
|
if (rsdp->rsdt_address) {
|
|
int rcount;
|
|
|
|
rsdt = nomap_sysmem(rsdp->rsdt_address, 0);
|
|
len = rsdt->header.length - sizeof(rsdt->header);
|
|
rcount = len / sizeof(u32);
|
|
if (xsdt) {
|
|
if (rcount != count)
|
|
return -ENOTSYNC;
|
|
} else {
|
|
count = rcount;
|
|
}
|
|
}
|
|
*rsdtp = rsdt;
|
|
*xsdtp = xsdt;
|
|
|
|
return count;
|
|
}
|
|
|
|
struct acpi_table_header *acpi_find_table(const char *sig)
|
|
{
|
|
struct acpi_rsdt *rsdt;
|
|
struct acpi_xsdt *xsdt;
|
|
int i, count, ret;
|
|
|
|
ret = setup_search(&rsdt, &xsdt);
|
|
if (ret < 0) {
|
|
log_warning("acpi: Failed to find tables (err=%d)\n", ret);
|
|
return NULL;
|
|
}
|
|
if (!ret)
|
|
return NULL;
|
|
count = ret;
|
|
|
|
if (!strcmp("RSDT", sig))
|
|
return &rsdt->header;
|
|
if (!strcmp("XSDT", sig))
|
|
return &xsdt->header;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct acpi_table_header *hdr;
|
|
|
|
if (xsdt)
|
|
hdr = nomap_sysmem(xsdt->entry[i], 0);
|
|
else
|
|
hdr = nomap_sysmem(rsdt->entry[i], 0);
|
|
if (!memcmp(hdr->signature, sig, ACPI_NAME_LEN))
|
|
return hdr;
|
|
if (!memcmp(hdr->signature, "FACP", ACPI_NAME_LEN)) {
|
|
struct acpi_fadt *fadt = (struct acpi_fadt *)hdr;
|
|
|
|
if (!memcmp(sig, "DSDT", ACPI_NAME_LEN)) {
|
|
void *dsdt;
|
|
|
|
if (fadt->header.revision >= 3 && fadt->x_dsdt)
|
|
dsdt = nomap_sysmem(fadt->x_dsdt, 0);
|
|
else if (fadt->dsdt)
|
|
dsdt = nomap_sysmem(fadt->dsdt, 0);
|
|
else
|
|
dsdt = NULL;
|
|
return dsdt;
|
|
}
|
|
|
|
if (!memcmp(sig, "FACS", ACPI_NAME_LEN)) {
|
|
void *facs;
|
|
|
|
if (fadt->header.revision >= 3 &&
|
|
fadt->x_firmware_ctrl)
|
|
facs = nomap_sysmem(fadt->x_firmware_ctrl, 0);
|
|
else if (fadt->firmware_ctrl)
|
|
facs = nomap_sysmem(fadt->firmware_ctrl, 0);
|
|
else
|
|
facs = NULL;
|
|
return facs;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void acpi_fill_header(struct acpi_table_header *header, char *signature)
|
|
{
|
|
memcpy(header->signature, signature, 4);
|
|
memcpy(header->oem_id, OEM_ID, 6);
|
|
memcpy(header->oem_table_id, OEM_TABLE_ID, 8);
|
|
header->oem_revision = OEM_REVISION;
|
|
memcpy(header->creator_id, ASLC_ID, 4);
|
|
header->creator_revision = ASL_REVISION;
|
|
}
|
|
|
|
void acpi_align(struct acpi_ctx *ctx)
|
|
{
|
|
ctx->current = (void *)ALIGN((ulong)ctx->current, 16);
|
|
}
|
|
|
|
void acpi_align64(struct acpi_ctx *ctx)
|
|
{
|
|
ctx->current = (void *)ALIGN((ulong)ctx->current, 64);
|
|
}
|
|
|
|
void acpi_inc(struct acpi_ctx *ctx, uint amount)
|
|
{
|
|
ctx->current += amount;
|
|
}
|
|
|
|
void acpi_inc_align(struct acpi_ctx *ctx, uint amount)
|
|
{
|
|
ctx->current += amount;
|
|
acpi_align(ctx);
|
|
}
|
|
|
|
/**
|
|
* Add an ACPI table to the RSDT (and XSDT) structure, recalculate length
|
|
* and checksum.
|
|
*/
|
|
int acpi_add_table(struct acpi_ctx *ctx, void *table)
|
|
{
|
|
int i, entries_num;
|
|
struct acpi_rsdt *rsdt;
|
|
struct acpi_xsdt *xsdt;
|
|
|
|
if (!ctx->rsdt && !ctx->xsdt) {
|
|
log_err("ACPI: Error: no RSDT / XSDT\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* On legacy x86 platforms the RSDT is mandatory while the XSDT is not.
|
|
* On other platforms there might be no memory below 4GiB, thus RSDT is NULL.
|
|
*/
|
|
if (ctx->rsdt) {
|
|
rsdt = ctx->rsdt;
|
|
|
|
/* This should always be MAX_ACPI_TABLES */
|
|
entries_num = ARRAY_SIZE(rsdt->entry);
|
|
|
|
for (i = 0; i < entries_num; i++) {
|
|
if (rsdt->entry[i] == 0)
|
|
break;
|
|
}
|
|
|
|
if (i >= entries_num) {
|
|
log_err("ACPI: Error: too many tables\n");
|
|
return -E2BIG;
|
|
}
|
|
|
|
/* Add table to the RSDT */
|
|
rsdt->entry[i] = nomap_to_sysmem(table);
|
|
|
|
/* Fix RSDT length or the kernel will assume invalid entries */
|
|
rsdt->header.length = sizeof(struct acpi_table_header) +
|
|
(sizeof(u32) * (i + 1));
|
|
|
|
/* Re-calculate checksum */
|
|
acpi_update_checksum(&rsdt->header);
|
|
}
|
|
|
|
if (ctx->xsdt) {
|
|
/*
|
|
* And now the same thing for the XSDT. We use the same index as for
|
|
* now we want the XSDT and RSDT to always be in sync in U-Boot
|
|
*/
|
|
xsdt = ctx->xsdt;
|
|
|
|
/* 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) {
|
|
log_err("ACPI: Error: too many tables\n");
|
|
return -E2BIG;
|
|
}
|
|
|
|
/* Add table to the XSDT */
|
|
xsdt->entry[i] = nomap_to_sysmem(table);
|
|
|
|
/* Fix XSDT length */
|
|
xsdt->header.length = sizeof(struct acpi_table_header) +
|
|
(sizeof(u64) * (i + 1));
|
|
|
|
/* Re-calculate checksum */
|
|
acpi_update_checksum(&xsdt->header);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void *acpi_get_end(void)
|
|
{
|
|
const struct acpi_table_header *end;
|
|
struct acpi_rsdt *rsdt;
|
|
struct acpi_xsdt *xsdt;
|
|
int i, count;
|
|
|
|
count = setup_search(&rsdt, &xsdt);
|
|
if (!count)
|
|
return NULL;
|
|
|
|
end = xsdt ? &xsdt->header : &rsdt->header;
|
|
for (i = 0; i < count; i++) {
|
|
const struct acpi_table_header *hdr;
|
|
|
|
if (xsdt)
|
|
hdr = nomap_sysmem(xsdt->entry[i], 0);
|
|
else
|
|
hdr = nomap_sysmem(rsdt->entry[i], 0);
|
|
end = max(hdr, end);
|
|
}
|
|
|
|
return (void *)end + end->length;
|
|
}
|