Files
my-os-project2/kernel/hal/x86_64/uACPI/source/tables.c
2025-08-17 18:37:57 +02:00

1401 lines
38 KiB
C

#include <uacpi/internal/tables.h>
#include <uacpi/internal/utilities.h>
#include <uacpi/internal/stdlib.h>
#include <uacpi/internal/interpreter.h>
#include <uacpi/internal/mutex.h>
DYNAMIC_ARRAY_WITH_INLINE_STORAGE(
table_array, struct uacpi_installed_table, UACPI_STATIC_TABLE_ARRAY_LEN
)
DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL(
table_array, struct uacpi_installed_table, static
)
static struct table_array tables;
static uacpi_bool early_table_access;
static uacpi_table_installation_handler installation_handler;
#ifndef UACPI_BAREBONES_MODE
static uacpi_handle table_mutex;
#define ENSURE_TABLES_ONLINE() \
do { \
if (!early_table_access) \
UACPI_ENSURE_INIT_LEVEL_AT_LEAST( \
UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED \
); \
} while (0)
#else
/*
* Use a dummy function instead of a macro to prevent the following error:
* error: statement with no effect [-Werror=unused-value]
*/
static inline uacpi_status dummy_mutex_acquire_release(uacpi_handle mtx)
{
UACPI_UNUSED(mtx);
return UACPI_STATUS_OK;
}
#define table_mutex UACPI_NULL
#define uacpi_acquire_native_mutex_may_be_null dummy_mutex_acquire_release
#define uacpi_release_native_mutex_may_be_null dummy_mutex_acquire_release
#define ENSURE_TABLES_ONLINE() \
do { \
if (!early_table_access) \
return UACPI_STATUS_INIT_LEVEL_MISMATCH; \
} while (0)
#endif // !UACPI_BAREBONES_MODE
static uacpi_status table_install_physical_with_origin_unlocked(
uacpi_phys_addr phys, enum uacpi_table_origin origin,
const uacpi_char *expected_signature, uacpi_table *out_table
);
static uacpi_status table_install_with_origin_unlocked(
void *virt, enum uacpi_table_origin origin, uacpi_table *out_table
);
UACPI_PACKED(struct uacpi_rxsdt {
struct acpi_sdt_hdr hdr;
uacpi_u8 ptr_bytes[];
})
static void dump_table_header(
uacpi_phys_addr phys_addr, void *hdr
)
{
struct acpi_sdt_hdr *sdt = hdr;
if (uacpi_signatures_match(hdr, ACPI_FACS_SIGNATURE)) {
uacpi_info(
"FACS 0x%016"UACPI_PRIX64" %08X\n", UACPI_FMT64(phys_addr),
sdt->length
);
return;
}
if (!uacpi_memcmp(hdr, ACPI_RSDP_SIGNATURE, sizeof(ACPI_RSDP_SIGNATURE) - 1)) {
struct acpi_rsdp *rsdp = hdr;
uacpi_info(
"RSDP 0x%016"UACPI_PRIX64" %08X v%02X (%6.6s)\n",
UACPI_FMT64(phys_addr), rsdp->revision >= 2 ? rsdp->length : 20,
rsdp->revision, rsdp->oemid
);
return;
}
uacpi_info(
"%.4s 0x%016"UACPI_PRIX64" %08X v%02X (%6.6s %8.8s)\n",
sdt->signature, UACPI_FMT64(phys_addr), sdt->length, sdt->revision,
sdt->oemid, sdt->oem_table_id
);
}
static uacpi_status initialize_from_rxsdt(uacpi_phys_addr rxsdt_addr,
uacpi_size entry_size)
{
struct uacpi_rxsdt *rxsdt;
uacpi_size i, entry_bytes, map_len = sizeof(*rxsdt);
uacpi_phys_addr entry_addr;
uacpi_status ret;
rxsdt = uacpi_kernel_map(rxsdt_addr, map_len);
if (rxsdt == UACPI_NULL)
return UACPI_STATUS_MAPPING_FAILED;
dump_table_header(rxsdt_addr, rxsdt);
ret = uacpi_check_table_signature(rxsdt,
entry_size == 8 ? ACPI_XSDT_SIGNATURE : ACPI_RSDT_SIGNATURE);
if (uacpi_unlikely_error(ret))
goto error_out;
map_len = rxsdt->hdr.length;
uacpi_kernel_unmap(rxsdt, sizeof(*rxsdt));
if (uacpi_unlikely(map_len < (sizeof(*rxsdt) + entry_size)))
return UACPI_STATUS_INVALID_TABLE_LENGTH;
// Make sure length is aligned to entry size so we don't OOB
entry_bytes = map_len - sizeof(*rxsdt);
entry_bytes &= ~(entry_size - 1);
rxsdt = uacpi_kernel_map(rxsdt_addr, map_len);
if (uacpi_unlikely(rxsdt == UACPI_NULL))
return UACPI_STATUS_MAPPING_FAILED;
ret = uacpi_verify_table_checksum(rxsdt, map_len);
if (uacpi_unlikely_error(ret))
goto error_out;
for (i = 0; i < entry_bytes; i += entry_size) {
uacpi_u64 entry_phys_addr_large = 0;
uacpi_memcpy(&entry_phys_addr_large, &rxsdt->ptr_bytes[i], entry_size);
if (!entry_phys_addr_large)
continue;
entry_addr = uacpi_truncate_phys_addr_with_warn(entry_phys_addr_large);
ret = uacpi_table_install_physical_with_origin(
entry_addr, UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL, UACPI_NULL
);
if (uacpi_unlikely(ret != UACPI_STATUS_OK &&
ret != UACPI_STATUS_OVERRIDDEN))
goto error_out;
}
ret = UACPI_STATUS_OK;
error_out:
uacpi_kernel_unmap(rxsdt, map_len);
return ret;
}
static uacpi_status initialize_from_rsdp(void)
{
uacpi_status ret;
uacpi_phys_addr rsdp_phys;
struct acpi_rsdp *rsdp;
uacpi_phys_addr rxsdt;
uacpi_size rxsdt_entry_size;
g_uacpi_rt_ctx.is_rev1 = UACPI_TRUE;
ret = uacpi_kernel_get_rsdp(&rsdp_phys);
if (uacpi_unlikely_error(ret))
return ret;
rsdp = uacpi_kernel_map(rsdp_phys, sizeof(struct acpi_rsdp));
if (rsdp == UACPI_NULL)
return UACPI_STATUS_MAPPING_FAILED;
dump_table_header(rsdp_phys, rsdp);
if (rsdp->revision > 1 && rsdp->xsdt_addr &&
!uacpi_check_flag(UACPI_FLAG_BAD_XSDT))
{
rxsdt = uacpi_truncate_phys_addr_with_warn(rsdp->xsdt_addr);
rxsdt_entry_size = 8;
} else {
rxsdt = (uacpi_phys_addr)rsdp->rsdt_addr;
rxsdt_entry_size = 4;
}
uacpi_kernel_unmap(rsdp, sizeof(struct acpi_rsdp));
if (!rxsdt) {
uacpi_error("both RSDT & XSDT tables are NULL!\n");
return UACPI_STATUS_INVALID_ARGUMENT;
}
return initialize_from_rxsdt(rxsdt, rxsdt_entry_size);
}
uacpi_status uacpi_setup_early_table_access(
void *temporary_buffer, uacpi_size buffer_size
)
{
uacpi_status ret;
#ifndef UACPI_BAREBONES_MODE
UACPI_ENSURE_INIT_LEVEL_IS(UACPI_INIT_LEVEL_EARLY);
#endif
if (uacpi_unlikely(early_table_access))
return UACPI_STATUS_INIT_LEVEL_MISMATCH;
if (uacpi_unlikely(buffer_size < sizeof(struct uacpi_installed_table)))
return UACPI_STATUS_INVALID_ARGUMENT;
uacpi_logger_initialize();
tables.dynamic_storage = temporary_buffer;
tables.dynamic_capacity = buffer_size / sizeof(struct uacpi_installed_table);
early_table_access = UACPI_TRUE;
ret = initialize_from_rsdp();
if (uacpi_unlikely_error(ret))
uacpi_deinitialize_tables();
return ret;
}
#ifndef UACPI_BAREBONES_MODE
static uacpi_iteration_decision warn_if_early_referenced(
void *user, struct uacpi_installed_table *tbl, uacpi_size idx
)
{
UACPI_UNUSED(user);
if (uacpi_unlikely(tbl->reference_count != 0)) {
uacpi_warn(
"table "UACPI_PRI_TBL_HDR" (%zu) still has %d early reference(s)!\n",
UACPI_FMT_TBL_HDR(&tbl->hdr), idx, tbl->reference_count
);
}
return UACPI_ITERATION_DECISION_CONTINUE;
}
uacpi_status uacpi_initialize_tables(void)
{
if (early_table_access) {
uacpi_size num_tables;
uacpi_for_each_table(0, warn_if_early_referenced, UACPI_NULL);
// Reallocate the user buffer into a normal heap array
num_tables = table_array_size(&tables);
if (num_tables > table_array_inline_capacity(&tables)) {
void *new_buf;
/*
* Allocate a new buffer with size equal to exactly the number of
* dynamic tables (that live in the user provided temporary buffer).
*/
num_tables -= table_array_inline_capacity(&tables);
new_buf = uacpi_kernel_alloc(
sizeof(struct uacpi_installed_table) * num_tables
);
if (uacpi_unlikely(new_buf == UACPI_NULL))
return UACPI_STATUS_OUT_OF_MEMORY;
uacpi_memcpy(new_buf, tables.dynamic_storage,
sizeof(struct uacpi_installed_table) * num_tables);
tables.dynamic_storage = new_buf;
tables.dynamic_capacity = num_tables;
} else {
/*
* User-provided temporary buffer was not used at all, just remove
* any references to it.
*/
tables.dynamic_storage = UACPI_NULL;
tables.dynamic_capacity = 0;
}
early_table_access = UACPI_FALSE;
} else {
uacpi_status ret;
ret = initialize_from_rsdp();
if (uacpi_unlikely_error(ret))
return ret;
}
if (!uacpi_is_hardware_reduced()) {
struct acpi_fadt *fadt = &g_uacpi_rt_ctx.fadt;
uacpi_table tbl;
if (fadt->x_firmware_ctrl) {
uacpi_status ret;
ret = table_install_physical_with_origin_unlocked(
fadt->x_firmware_ctrl, UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL,
ACPI_FACS_SIGNATURE, &tbl
);
if (uacpi_unlikely(ret != UACPI_STATUS_OK &&
ret != UACPI_STATUS_OVERRIDDEN))
return ret;
g_uacpi_rt_ctx.facs = tbl.ptr;
}
}
table_mutex = uacpi_kernel_create_mutex();
if (uacpi_unlikely(table_mutex == UACPI_NULL))
return UACPI_STATUS_OUT_OF_MEMORY;
return UACPI_STATUS_OK;
}
#endif // !UACPI_BAREBONES_MODE
void uacpi_deinitialize_tables(void)
{
uacpi_size i;
for (i = 0; i < table_array_size(&tables); ++i) {
struct uacpi_installed_table *tbl = table_array_at(&tables, i);
switch (tbl->origin) {
#ifndef UACPI_BAREBONES_MODE
case UACPI_TABLE_ORIGIN_FIRMWARE_VIRTUAL:
uacpi_free(tbl->ptr, tbl->hdr.length);
break;
#endif
case UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL:
case UACPI_TABLE_ORIGIN_HOST_PHYSICAL:
if (tbl->reference_count != 0)
uacpi_kernel_unmap(tbl->ptr, tbl->hdr.length);
break;
default:
break;
}
}
if (early_table_access) {
uacpi_memzero(&tables, sizeof(tables));
early_table_access = UACPI_FALSE;
} else {
table_array_clear(&tables);
}
installation_handler = UACPI_NULL;
#ifndef UACPI_BAREBONES_MODE
if (table_mutex)
uacpi_kernel_free_mutex(table_mutex);
table_mutex = UACPI_NULL;
#endif
}
uacpi_status uacpi_set_table_installation_handler(
uacpi_table_installation_handler handler
)
{
uacpi_status ret;
ret = uacpi_acquire_native_mutex_may_be_null(table_mutex);
if (uacpi_unlikely_error(ret))
return ret;
if (installation_handler != UACPI_NULL && handler != UACPI_NULL)
goto out;
installation_handler = handler;
out:
uacpi_release_native_mutex_may_be_null(table_mutex);
return ret;
}
static uacpi_status initialize_fadt(const void*);
static uacpi_u8 table_checksum(void *table, uacpi_size size)
{
uacpi_u8 *bytes = table;
uacpi_u8 csum = 0;
uacpi_size i;
for (i = 0; i < size; ++i)
csum += bytes[i];
return csum;
}
uacpi_status uacpi_verify_table_checksum(void *table, uacpi_size size)
{
uacpi_status ret = UACPI_STATUS_OK;
uacpi_u8 csum;
csum = table_checksum(table, size);
if (uacpi_unlikely(csum != 0)) {
enum uacpi_log_level lvl = UACPI_LOG_WARN;
struct acpi_sdt_hdr *hdr = table;
if (uacpi_check_flag(UACPI_FLAG_BAD_CSUM_FATAL)) {
ret = UACPI_STATUS_BAD_CHECKSUM;
lvl = UACPI_LOG_ERROR;
}
uacpi_log_lvl(
lvl, "invalid table "UACPI_PRI_TBL_HDR" checksum %d!\n",
UACPI_FMT_TBL_HDR(hdr), csum
);
}
return ret;
}
uacpi_bool uacpi_signatures_match(const void *const lhs, const void *const rhs)
{
return uacpi_memcmp(lhs, rhs, sizeof(uacpi_object_name)) == 0;
}
uacpi_status uacpi_check_table_signature(void *table, const uacpi_char *expect)
{
uacpi_status ret = UACPI_STATUS_OK;
if (!uacpi_signatures_match(table, expect)) {
enum uacpi_log_level lvl = UACPI_LOG_WARN;
struct acpi_sdt_hdr *hdr = table;
if (uacpi_check_flag(UACPI_FLAG_BAD_TBL_SIGNATURE_FATAL)) {
ret = UACPI_STATUS_INVALID_SIGNATURE;
lvl = UACPI_LOG_ERROR;
}
uacpi_log_lvl(
lvl,
"invalid table "UACPI_PRI_TBL_HDR" signature (expected '%.4s')\n",
UACPI_FMT_TBL_HDR(hdr), expect
);
}
return ret;
}
static uacpi_status table_alloc(
struct uacpi_installed_table **out_tbl, uacpi_size *out_idx
)
{
struct uacpi_installed_table *tbl;
if (early_table_access &&
table_array_size(&tables) == table_array_capacity(&tables)) {
uacpi_warn("early table access buffer capacity exhausted!\n");
return UACPI_STATUS_OUT_OF_MEMORY;
}
tbl = table_array_alloc(&tables);
if (uacpi_unlikely(tbl == UACPI_NULL))
return UACPI_STATUS_OUT_OF_MEMORY;
*out_tbl = tbl;
*out_idx = table_array_size(&tables) - 1;
return UACPI_STATUS_OK;
}
static uacpi_status get_external_table_header(
uacpi_phys_addr phys_addr, struct acpi_sdt_hdr *out_hdr
)
{
void *virt;
virt = uacpi_kernel_map(phys_addr, sizeof(*out_hdr));
if (uacpi_unlikely(virt == UACPI_NULL))
return UACPI_STATUS_MAPPING_FAILED;
uacpi_memcpy(out_hdr, virt, sizeof(*out_hdr));
uacpi_kernel_unmap(virt, sizeof(*out_hdr));
return UACPI_STATUS_OK;
}
static uacpi_status table_ref_unlocked(struct uacpi_installed_table *tbl)
{
switch (tbl->reference_count) {
case 0: {
uacpi_status ret;
if (tbl->flags & UACPI_TABLE_INVALID)
return UACPI_STATUS_INVALID_ARGUMENT;
if (tbl->origin != UACPI_TABLE_ORIGIN_HOST_PHYSICAL &&
tbl->origin != UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL)
break;
tbl->ptr = uacpi_kernel_map(tbl->phys_addr, tbl->hdr.length);
if (uacpi_unlikely(tbl->ptr == UACPI_NULL))
return UACPI_STATUS_MAPPING_FAILED;
if (!(tbl->flags & UACPI_TABLE_CSUM_VERIFIED)) {
ret = uacpi_verify_table_checksum(tbl->ptr, tbl->hdr.length);
if (uacpi_unlikely_error(ret)) {
uacpi_kernel_unmap(tbl->ptr, tbl->hdr.length);
tbl->flags |= UACPI_TABLE_INVALID;
tbl->ptr = UACPI_NULL;
return ret;
}
tbl->flags |= UACPI_TABLE_CSUM_VERIFIED;
}
break;
}
case 0xFFFF - 1:
uacpi_warn(
"too many references for "UACPI_PRI_TBL_HDR
", mapping permanently\n", UACPI_FMT_TBL_HDR(&tbl->hdr)
);
break;
default:
break;
}
if (uacpi_likely(tbl->reference_count != 0xFFFF))
tbl->reference_count++;
return UACPI_STATUS_OK;
}
static uacpi_status table_unref_unlocked(struct uacpi_installed_table *tbl)
{
switch (tbl->reference_count) {
case 0:
uacpi_warn(
"tried to unref table "UACPI_PRI_TBL_HDR" with no references\n",
UACPI_FMT_TBL_HDR(&tbl->hdr)
);
return UACPI_STATUS_INVALID_ARGUMENT;
case 1:
if (tbl->origin != UACPI_TABLE_ORIGIN_HOST_PHYSICAL &&
tbl->origin != UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL)
break;
uacpi_kernel_unmap(tbl->ptr, tbl->hdr.length);
tbl->ptr = UACPI_NULL;
break;
case 0xFFFF:
/*
* Consider the reference count (overflow) of 0xFFFF to be a permanently
* mapped table as we don't know the actual number of references.
*/
return UACPI_STATUS_OK;
default:
break;
}
tbl->reference_count--;
return UACPI_STATUS_OK;
}
static uacpi_status verify_and_install_table(
struct acpi_sdt_hdr *hdr, uacpi_phys_addr phys_addr, void *virt_addr,
enum uacpi_table_origin origin, uacpi_table *out_table
)
{
uacpi_status ret;
struct uacpi_installed_table *table;
uacpi_bool is_fadt;
uacpi_size idx;
uacpi_u8 flags = 0;
is_fadt = uacpi_signatures_match(hdr->signature, ACPI_FADT_SIGNATURE);
/*
* FACS is the only(?) table without a checksum because it has OSPM
* writable fields. Don't try to validate it here.
*/
if (uacpi_signatures_match(hdr->signature, ACPI_FACS_SIGNATURE)) {
flags |= UACPI_TABLE_CSUM_VERIFIED;
} else if (uacpi_check_flag(UACPI_FLAG_PROACTIVE_TBL_CSUM) || is_fadt ||
out_table != UACPI_NULL) {
void *mapping = virt_addr;
// We may already have a valid mapping, reuse it if we do
if (mapping == UACPI_NULL)
mapping = uacpi_kernel_map(phys_addr, hdr->length);
if (uacpi_unlikely(mapping == UACPI_NULL))
return UACPI_STATUS_MAPPING_FAILED;
ret = uacpi_verify_table_checksum(mapping, hdr->length);
if (uacpi_likely_success(ret)) {
if (is_fadt)
ret = initialize_fadt(mapping);
flags |= UACPI_TABLE_CSUM_VERIFIED;
}
if (virt_addr == UACPI_NULL)
uacpi_kernel_unmap(mapping, hdr->length);
if (uacpi_unlikely_error(ret))
return ret;
}
if (uacpi_signatures_match(hdr->signature, ACPI_DSDT_SIGNATURE))
g_uacpi_rt_ctx.is_rev1 = hdr->revision < 2;
ret = table_alloc(&table, &idx);
if (uacpi_unlikely_error(ret))
return ret;
dump_table_header(phys_addr, hdr);
uacpi_memcpy(&table->hdr, hdr, sizeof(*hdr));
table->reference_count = 0;
table->phys_addr = phys_addr;
table->ptr = virt_addr;
table->flags = flags;
table->origin = origin;
if (out_table == UACPI_NULL)
return UACPI_STATUS_OK;
table->reference_count++;
out_table->ptr = virt_addr;
out_table->index = idx;
return UACPI_STATUS_OK;
}
static uacpi_status handle_table_override(
uacpi_table_installation_disposition disposition, uacpi_u64 address,
uacpi_table *out_table
)
{
uacpi_status ret;
switch (disposition) {
case UACPI_TABLE_INSTALLATION_DISPOSITON_VIRTUAL_OVERRIDE:
ret = table_install_with_origin_unlocked(
UACPI_VIRT_ADDR_TO_PTR((uacpi_virt_addr)address),
UACPI_TABLE_ORIGIN_HOST_VIRTUAL,
out_table
);
return ret;
case UACPI_TABLE_INSTALLATION_DISPOSITON_PHYSICAL_OVERRIDE:
return table_install_physical_with_origin_unlocked(
(uacpi_phys_addr)address,
UACPI_TABLE_ORIGIN_HOST_PHYSICAL,
UACPI_NULL,
out_table
);
default:
uacpi_error("invalid table installation disposition %d\n", disposition);
return UACPI_STATUS_INTERNAL_ERROR;
}
}
static uacpi_status table_install_physical_with_origin_unlocked(
uacpi_phys_addr phys, enum uacpi_table_origin origin,
const uacpi_char *expected_signature, uacpi_table *out_table
)
{
struct acpi_sdt_hdr hdr;
void *virt = UACPI_NULL;
uacpi_status ret;
ret = get_external_table_header(phys, &hdr);
if (uacpi_unlikely_error(ret))
return ret;
if (uacpi_unlikely(hdr.length < sizeof(struct acpi_sdt_hdr))) {
uacpi_warn(
"bogus table '%.4s' (0x%016"UACPI_PRIX64") size: %u bytes\n",
hdr.signature, UACPI_FMT64(phys), hdr.length
);
}
if (expected_signature != UACPI_NULL) {
ret = uacpi_check_table_signature(&hdr, expected_signature);
if (uacpi_unlikely_error(ret))
return ret;
}
if (installation_handler != UACPI_NULL || out_table != UACPI_NULL) {
virt = uacpi_kernel_map(phys, hdr.length);
if (uacpi_unlikely(!virt))
return UACPI_STATUS_MAPPING_FAILED;
}
if (origin == UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL &&
installation_handler != UACPI_NULL) {
uacpi_u64 override;
uacpi_table_installation_disposition disposition;
disposition = installation_handler(virt, &override);
switch (disposition) {
case UACPI_TABLE_INSTALLATION_DISPOSITON_ALLOW:
break;
case UACPI_TABLE_INSTALLATION_DISPOSITON_DENY:
uacpi_info(
"table '%.4s' (0x%016"UACPI_PRIX64") installation denied "
"by host\n", hdr.signature, UACPI_FMT64(phys)
);
ret = UACPI_STATUS_DENIED;
goto out;
default:
uacpi_info(
"table '%.4s' (0x%016"UACPI_PRIX64") installation "
"overridden by host\n", hdr.signature, UACPI_FMT64(phys)
);
ret = handle_table_override(disposition, override, out_table);
if (uacpi_likely_success(ret))
ret = UACPI_STATUS_OVERRIDDEN;
goto out;
}
}
ret = verify_and_install_table(&hdr, phys, virt, origin, out_table);
out:
// We don't unmap only in this case
if (ret == UACPI_STATUS_OK && out_table != UACPI_NULL)
return ret;
if (virt != UACPI_NULL)
uacpi_kernel_unmap(virt, hdr.length);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_table_install_physical_with_origin(
uacpi_phys_addr phys, enum uacpi_table_origin origin, uacpi_table *out_table
)
{
uacpi_status ret;
ret = uacpi_acquire_native_mutex_may_be_null(table_mutex);
if (uacpi_unlikely_error(ret))
return ret;
ret = table_install_physical_with_origin_unlocked(
phys, origin, UACPI_NULL, out_table
);
uacpi_release_native_mutex_may_be_null(table_mutex);
return ret;
}
static uacpi_status table_install_with_origin_unlocked(
void *virt, enum uacpi_table_origin origin, uacpi_table *out_table
)
{
struct acpi_sdt_hdr *hdr = virt;
if (uacpi_unlikely(hdr->length < sizeof(struct acpi_sdt_hdr))) {
uacpi_error("invalid table '%.4s' (%p) size: %u\n",
hdr->signature, virt, hdr->length);
return UACPI_STATUS_INVALID_TABLE_LENGTH;
}
#ifndef UACPI_BAREBONES_MODE
if (origin == UACPI_TABLE_ORIGIN_FIRMWARE_VIRTUAL &&
installation_handler != UACPI_NULL) {
uacpi_u64 override;
uacpi_table_installation_disposition disposition;
disposition = installation_handler(virt, &override);
switch (disposition) {
case UACPI_TABLE_INSTALLATION_DISPOSITON_ALLOW:
break;
case UACPI_TABLE_INSTALLATION_DISPOSITON_DENY:
uacpi_info(
"table "UACPI_PRI_TBL_HDR" installation denied by host\n",
UACPI_FMT_TBL_HDR(hdr)
);
return UACPI_STATUS_DENIED;
default: {
uacpi_status ret;
uacpi_info(
"table "UACPI_PRI_TBL_HDR" installation overridden by host\n",
UACPI_FMT_TBL_HDR(hdr)
);
ret = handle_table_override(disposition, override, out_table);
if (uacpi_likely_success(ret))
ret = UACPI_STATUS_OVERRIDDEN;
return ret;
}
}
}
#endif
return verify_and_install_table(
hdr, 0, virt, origin, out_table
);
}
uacpi_status uacpi_table_install_with_origin(
void *virt, enum uacpi_table_origin origin, uacpi_table *out_table
)
{
uacpi_status ret;
ret = uacpi_acquire_native_mutex_may_be_null(table_mutex);
if (uacpi_unlikely_error(ret))
return ret;
ret = table_install_with_origin_unlocked(virt, origin, out_table);
uacpi_release_native_mutex_may_be_null(table_mutex);
return ret;
}
uacpi_status uacpi_table_install(void *virt, uacpi_table *out_table)
{
ENSURE_TABLES_ONLINE();
return uacpi_table_install_with_origin(
virt, UACPI_TABLE_ORIGIN_HOST_VIRTUAL, out_table
);
}
uacpi_status uacpi_table_install_physical(
uacpi_phys_addr addr, uacpi_table *out_table
)
{
ENSURE_TABLES_ONLINE();
return uacpi_table_install_physical_with_origin(
addr, UACPI_TABLE_ORIGIN_HOST_PHYSICAL, out_table
);
}
uacpi_status uacpi_for_each_table(
uacpi_size base_idx, uacpi_table_iteration_callback cb, void *user
)
{
uacpi_status ret;
uacpi_size idx;
struct uacpi_installed_table *tbl;
uacpi_iteration_decision dec;
ENSURE_TABLES_ONLINE();
ret = uacpi_acquire_native_mutex_may_be_null(table_mutex);
if (uacpi_unlikely_error(ret))
return ret;
for (idx = base_idx; idx < table_array_size(&tables); ++idx) {
tbl = table_array_at(&tables, idx);
if (tbl->flags & UACPI_TABLE_INVALID)
continue;
dec = cb(user, tbl, idx);
if (dec == UACPI_ITERATION_DECISION_BREAK)
break;
}
uacpi_release_native_mutex_may_be_null(table_mutex);
return ret;
}
enum search_type {
SEARCH_TYPE_BY_ID,
SEARCH_TYPE_MATCH,
};
struct table_search_ctx {
union {
const uacpi_table_identifiers *id;
uacpi_table_match_callback match_cb;
};
uacpi_table *out_table;
uacpi_u8 search_type;
uacpi_status status;
};
static uacpi_iteration_decision do_search_tables(
void *user, struct uacpi_installed_table *tbl, uacpi_size idx
)
{
struct table_search_ctx *ctx = user;
uacpi_table *out_table;
uacpi_status ret;
switch (ctx->search_type) {
case SEARCH_TYPE_BY_ID: {
const uacpi_table_identifiers *id = ctx->id;
if (!uacpi_signatures_match(&id->signature, tbl->hdr.signature))
return UACPI_ITERATION_DECISION_CONTINUE;
if (id->oemid[0] != '\0' &&
uacpi_memcmp(id->oemid, tbl->hdr.oemid, sizeof(id->oemid)) != 0)
return UACPI_ITERATION_DECISION_CONTINUE;
if (id->oem_table_id[0] != '\0' &&
uacpi_memcmp(id->oem_table_id, tbl->hdr.oem_table_id,
sizeof(id->oem_table_id)) != 0)
return UACPI_ITERATION_DECISION_CONTINUE;
break;
}
case SEARCH_TYPE_MATCH:
if (!ctx->match_cb(tbl))
return UACPI_ITERATION_DECISION_CONTINUE;
break;
default:
ctx->status = UACPI_STATUS_INVALID_ARGUMENT;
return UACPI_ITERATION_DECISION_BREAK;
}
ret = table_ref_unlocked(tbl);
if (uacpi_likely_success(ret)) {
out_table = ctx->out_table;
out_table->ptr = tbl->ptr;
out_table->index = idx;
ctx->status = ret;
return UACPI_ITERATION_DECISION_BREAK;
}
/*
* Don't abort nor propagate bad checksums, just pretend this table never
* existed and go on with the search.
*/
if (ret == UACPI_STATUS_BAD_CHECKSUM)
return UACPI_ITERATION_DECISION_CONTINUE;
ctx->status = ret;
return UACPI_ITERATION_DECISION_BREAK;
}
#ifndef UACPI_BAREBONES_MODE
uacpi_status uacpi_table_match(
uacpi_size base_idx, uacpi_table_match_callback cb, uacpi_table *out_table
)
{
uacpi_status ret;
struct table_search_ctx ctx = { 0 };
ctx.match_cb = cb;
ctx.search_type = SEARCH_TYPE_MATCH;
ctx.out_table = out_table;
ctx.status = UACPI_STATUS_NOT_FOUND;
ret = uacpi_for_each_table(base_idx, do_search_tables, &ctx);
if (uacpi_unlikely_error(ret))
return ret;
return ctx.status;
}
#endif
static uacpi_status find_table(
uacpi_size base_idx, const uacpi_table_identifiers *id,
uacpi_table *out_table
)
{
uacpi_status ret;
struct table_search_ctx ctx = { 0 };
ctx.id = id;
ctx.out_table = out_table;
ctx.search_type = SEARCH_TYPE_BY_ID;
ctx.status = UACPI_STATUS_NOT_FOUND;
ret = uacpi_for_each_table(base_idx, do_search_tables, &ctx);
if (uacpi_unlikely_error(ret))
return ret;
return ctx.status;
}
uacpi_status uacpi_table_find_by_signature(
const uacpi_char *signature_string, struct uacpi_table *out_table
)
{
struct uacpi_table_identifiers id = { 0 };
id.signature.text[0] = signature_string[0];
id.signature.text[1] = signature_string[1];
id.signature.text[2] = signature_string[2];
id.signature.text[3] = signature_string[3];
ENSURE_TABLES_ONLINE();
return find_table(0, &id, out_table);
}
uacpi_status uacpi_table_find_next_with_same_signature(
uacpi_table *in_out_table
)
{
struct uacpi_table_identifiers id = { 0 };
ENSURE_TABLES_ONLINE();
if (uacpi_unlikely(in_out_table->ptr == UACPI_NULL))
return UACPI_STATUS_INVALID_ARGUMENT;
uacpi_memcpy(&id.signature, in_out_table->hdr->signature,
sizeof(id.signature));
uacpi_table_unref(in_out_table);
return find_table(in_out_table->index + 1, &id, in_out_table);
}
uacpi_status uacpi_table_find(
const uacpi_table_identifiers *id, uacpi_table *out_table
)
{
ENSURE_TABLES_ONLINE();
return find_table(0, id, out_table);
}
#define TABLE_CTL_SET_FLAGS (1 << 0)
#define TABLE_CTL_CLEAR_FLAGS (1 << 1)
#define TABLE_CTL_VALIDATE_SET_FLAGS (1 << 2)
#define TABLE_CTL_VALIDATE_CLEAR_FLAGS (1 << 3)
#define TABLE_CTL_GET (1 << 4)
#define TABLE_CTL_PUT (1 << 5)
struct table_ctl_request {
uacpi_u8 type;
uacpi_u8 expect_set;
uacpi_u8 expect_clear;
uacpi_u8 set;
uacpi_u8 clear;
void *out_tbl;
};
static uacpi_status table_ctl(uacpi_size idx, struct table_ctl_request *req)
{
uacpi_status ret;
struct uacpi_installed_table *tbl;
ENSURE_TABLES_ONLINE();
ret = uacpi_acquire_native_mutex_may_be_null(table_mutex);
if (uacpi_unlikely_error(ret))
return ret;
if (uacpi_unlikely(table_array_size(&tables) <= idx)) {
uacpi_error(
"requested invalid table index %zu (%zu tables installed)\n",
idx, table_array_size(&tables)
);
ret = UACPI_STATUS_INVALID_ARGUMENT;
goto out;
}
tbl = table_array_at(&tables, idx);
if (uacpi_unlikely(tbl->flags & UACPI_TABLE_INVALID))
return UACPI_STATUS_INVALID_ARGUMENT;
if (req->type & TABLE_CTL_VALIDATE_SET_FLAGS) {
uacpi_u8 mask = req->expect_set;
if (uacpi_unlikely((tbl->flags & mask) != mask)) {
uacpi_error(
"unexpected table '%.4s' flags %02X, expected %02X to be set\n",
tbl->hdr.signature, tbl->flags, mask
);
ret = UACPI_STATUS_INVALID_ARGUMENT;
goto out;
}
}
if (req->type & TABLE_CTL_VALIDATE_CLEAR_FLAGS) {
uacpi_u8 mask = req->expect_clear;
if (uacpi_unlikely((tbl->flags & mask) != 0)) {
uacpi_error(
"unexpected table '%.4s' flags %02X, expected %02X "
"to be clear\n", tbl->hdr.signature, tbl->flags, mask
);
ret = UACPI_STATUS_ALREADY_EXISTS;
goto out;
}
}
if (req->type & TABLE_CTL_GET) {
ret = table_ref_unlocked(tbl);
if (uacpi_unlikely_error(ret))
goto out;
req->out_tbl = tbl->ptr;
}
if (req->type & TABLE_CTL_PUT) {
ret = table_unref_unlocked(tbl);
if (uacpi_unlikely_error(ret))
goto out;
}
if (req->type & TABLE_CTL_SET_FLAGS)
tbl->flags |= req->set;
if (req->type & TABLE_CTL_CLEAR_FLAGS)
tbl->flags &= ~req->clear;
out:
uacpi_release_native_mutex_may_be_null(table_mutex);
return ret;
}
#ifndef UACPI_BAREBONES_MODE
uacpi_status uacpi_table_load_with_cause(
uacpi_size idx, enum uacpi_table_load_cause cause
)
{
uacpi_status ret;
struct table_ctl_request req = {
.type = TABLE_CTL_SET_FLAGS | TABLE_CTL_VALIDATE_CLEAR_FLAGS |
TABLE_CTL_GET,
.set = UACPI_TABLE_LOADED,
.expect_clear = UACPI_TABLE_LOADED,
};
ret = table_ctl(idx, &req);
if (uacpi_unlikely_error(ret))
return ret;
ret = uacpi_execute_table(req.out_tbl, cause);
req.type = TABLE_CTL_PUT;
table_ctl(idx, &req);
return ret;
}
uacpi_status uacpi_table_load(uacpi_size idx)
{
return uacpi_table_load_with_cause(idx, UACPI_TABLE_LOAD_CAUSE_HOST);
}
void uacpi_table_mark_as_loaded(uacpi_size idx)
{
struct table_ctl_request req = {
.type = TABLE_CTL_SET_FLAGS, .set = UACPI_TABLE_LOADED
};
table_ctl(idx, &req);
}
#endif // !UACPI_BAREBONES_MODE
uacpi_status uacpi_table_ref(uacpi_table *tbl)
{
struct table_ctl_request req = {
.type = TABLE_CTL_GET
};
return table_ctl(tbl->index, &req);
}
uacpi_status uacpi_table_unref(uacpi_table *tbl)
{
struct table_ctl_request req = {
.type = TABLE_CTL_PUT
};
return table_ctl(tbl->index, &req);
}
uacpi_u16 fadt_version_sizes[] = {
116, 132, 244, 244, 268, 276
};
static void fadt_ensure_correct_revision(struct acpi_fadt *fadt)
{
uacpi_size current_rev, rev;
current_rev = fadt->hdr.revision;
for (rev = 0; rev < UACPI_ARRAY_SIZE(fadt_version_sizes); ++rev) {
if (fadt->hdr.length <= fadt_version_sizes[rev])
break;
}
if (rev == UACPI_ARRAY_SIZE(fadt_version_sizes)) {
uacpi_trace(
"FADT revision (%zu) is likely greater than the last "
"supported, reducing to %zu\n", current_rev, rev
);
fadt->hdr.revision = rev;
return;
}
rev++;
if (current_rev != rev && !(rev == 3 && current_rev == 4)) {
uacpi_warn(
"FADT length %u doesn't match expected for revision %zu, "
"assuming version %zu\n", fadt->hdr.length, current_rev,
rev
);
fadt->hdr.revision = rev;
}
}
static void gas_init_system_io(
struct acpi_gas *gas, uacpi_u64 address, uacpi_u8 byte_size
)
{
gas->address = address;
gas->address_space_id = UACPI_ADDRESS_SPACE_SYSTEM_IO;
gas->register_bit_width = UACPI_MIN(255, byte_size * 8);
gas->register_bit_offset = 0;
gas->access_size = 0;
}
struct register_description {
uacpi_size offset, xoffset;
uacpi_size length_offset;
};
#define fadt_offset(field) uacpi_offsetof(struct acpi_fadt, field)
/*
* We convert all the legacy registers into GAS format and write them into
* the x_* fields for convenience and faster access at runtime.
*/
static struct register_description fadt_registers[] = {
{
.offset = fadt_offset(pm1a_evt_blk),
.xoffset = fadt_offset(x_pm1a_evt_blk),
.length_offset = fadt_offset(pm1_evt_len),
},
{
.offset = fadt_offset(pm1b_evt_blk),
.xoffset = fadt_offset(x_pm1b_evt_blk),
.length_offset = fadt_offset(pm1_evt_len),
},
{
.offset = fadt_offset(pm1a_cnt_blk),
.xoffset = fadt_offset(x_pm1a_cnt_blk),
.length_offset = fadt_offset(pm1_cnt_len),
},
{
.offset = fadt_offset(pm1b_cnt_blk),
.xoffset = fadt_offset(x_pm1b_cnt_blk),
.length_offset = fadt_offset(pm1_cnt_len),
},
{
.offset = fadt_offset(pm2_cnt_blk),
.xoffset = fadt_offset(x_pm2_cnt_blk),
.length_offset = fadt_offset(pm2_cnt_len),
},
{
.offset = fadt_offset(pm_tmr_blk),
.xoffset = fadt_offset(x_pm_tmr_blk),
.length_offset = fadt_offset(pm_tmr_len),
},
{
.offset = fadt_offset(gpe0_blk),
.xoffset = fadt_offset(x_gpe0_blk),
.length_offset = fadt_offset(gpe0_blk_len),
},
{
.offset = fadt_offset(gpe1_blk),
.xoffset = fadt_offset(x_gpe1_blk),
.length_offset = fadt_offset(gpe1_blk_len),
},
};
static void *fadt_relative(uacpi_size offset)
{
return ((uacpi_u8*)&g_uacpi_rt_ctx.fadt) + offset;
}
static void convert_registers_to_gas(void)
{
uacpi_size i;
struct register_description *desc;
struct acpi_gas *gas;
uacpi_u32 legacy_addr;
uacpi_u8 length;
for (i = 0; i < UACPI_ARRAY_SIZE(fadt_registers); ++i) {
desc = &fadt_registers[i];
legacy_addr = *(uacpi_u32*)fadt_relative(desc->offset);
length = *(uacpi_u8*)fadt_relative(desc->length_offset);
gas = fadt_relative(desc->xoffset);
if (gas->address)
continue;
gas_init_system_io(gas, legacy_addr, length);
}
}
#ifndef UACPI_BAREBONES_MODE
static void split_one_block(
struct acpi_gas *src, struct acpi_gas *dst0, struct acpi_gas *dst1
)
{
uacpi_size byte_length;
if (src->address == 0)
return;
byte_length = src->register_bit_width / 8;
byte_length /= 2;
gas_init_system_io(dst0, src->address, byte_length);
gas_init_system_io(dst1, src->address + byte_length, byte_length);
}
static void split_event_blocks(void)
{
split_one_block(
&g_uacpi_rt_ctx.fadt.x_pm1a_evt_blk,
&g_uacpi_rt_ctx.pm1a_status_blk,
&g_uacpi_rt_ctx.pm1a_enable_blk
);
split_one_block(
&g_uacpi_rt_ctx.fadt.x_pm1b_evt_blk,
&g_uacpi_rt_ctx.pm1b_status_blk,
&g_uacpi_rt_ctx.pm1b_enable_blk
);
}
#endif // !UACPI_BAREBONES_MODE
static uacpi_status initialize_fadt(const void *virt)
{
uacpi_status ret;
struct acpi_fadt *fadt = &g_uacpi_rt_ctx.fadt;
const struct acpi_sdt_hdr *hdr = virt;
/*
* Here we (roughly) follow ACPICA initialization sequence to make sure we
* handle potential BIOS quirks with garbage inside FADT correctly.
*/
uacpi_memcpy(fadt, hdr, UACPI_MIN(sizeof(*fadt), hdr->length));
#if !defined(UACPI_REDUCED_HARDWARE) && !defined(UACPI_BAREBONES_MODE)
g_uacpi_rt_ctx.is_hardware_reduced = fadt->flags & ACPI_HW_REDUCED_ACPI;
#endif
fadt_ensure_correct_revision(fadt);
/*
* These are reserved prior to version 3, so zero them out to work around
* BIOS implementations that might dirty these.
*/
if (fadt->hdr.revision <= 2) {
fadt->preferred_pm_profile = 0;
fadt->pstate_cnt = 0;
fadt->cst_cnt = 0;
fadt->iapc_boot_arch = 0;
}
if (!fadt->x_dsdt)
fadt->x_dsdt = fadt->dsdt;
if (fadt->x_dsdt) {
ret = table_install_physical_with_origin_unlocked(
fadt->x_dsdt, UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL,
ACPI_DSDT_SIGNATURE, UACPI_NULL
);
if (uacpi_unlikely(ret != UACPI_STATUS_OK &&
ret != UACPI_STATUS_OVERRIDDEN))
return ret;
}
/*
* Unconditionally use 32 bit FACS if it exists, as 64 bit FACS is known
* to cause issues on some firmware:
* https://bugzilla.kernel.org/show_bug.cgi?id=74021
*
* Note that we don't install it here as FACS needs permanent mapping, which
* we might not be able to obtain at this point in case of early table
* access.
*/
if (fadt->firmware_ctrl)
fadt->x_firmware_ctrl = fadt->firmware_ctrl;
if (!uacpi_is_hardware_reduced()) {
convert_registers_to_gas();
#ifndef UACPI_BAREBONES_MODE
split_event_blocks();
#endif
}
return UACPI_STATUS_OK;
}
uacpi_status uacpi_table_fadt(struct acpi_fadt **out_fadt)
{
ENSURE_TABLES_ONLINE();
*out_fadt = &g_uacpi_rt_ctx.fadt;
return UACPI_STATUS_OK;
}