zephyr/lib/acpi/acpi.c
Najumon Ba f25dfcf88c lib: acpi: added acpi support using acpica lib
Add ACPI support for Zephyr using acpica open source
project. ACPI subsystem use to discover and configure
hardware components, perform power management (e.g. putting
unused hardware components to sleep), auto configuration (e.g.
Plug and Play and hot swapping) etc.

Signed-off-by: Najumon Ba <najumon.ba@intel.com>
2023-06-30 17:53:01 +03:00

650 lines
14 KiB
C

/*
* Copyright (c) 2023 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "acpi.h"
#include "accommon.h"
#include "acapps.h"
#include <aecommon.h>
#include <zephyr/drivers/pcie/pcie.h>
#include <zephyr/acpi/acpi.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ACPI, CONFIG_ACPI_LOG_LEVEL);
struct acpi {
struct acpi_dev child_dev[CONFIG_ACPI_DEV_MAX];
int num_dev;
ACPI_PCI_ROUTING_TABLE pci_prt_table[CONFIG_ACPI_MAX_PRT_ENTRY];
bool early_init;
int status;
};
static struct acpi bus_ctx = {
.status = AE_NOT_CONFIGURED,
};
static ACPI_TABLE_DESC acpi_table[CONFIG_ACPI_MAX_INIT_TABLES];
static int acpi_init(void);
static int check_init_status(void)
{
int ret;
if (ACPI_SUCCESS(bus_ctx.status)) {
return 0;
}
if (bus_ctx.status == AE_NOT_CONFIGURED) {
LOG_DBG("ACPI init\n");
ret = acpi_init();
} else {
LOG_ERR("ACPI init was not success\n");
ret = -EIO;
}
return ret;
}
static void notify_handler(ACPI_HANDLE device, UINT32 value, void *ctx)
{
ACPI_INFO(("Received a notify 0x%X", value));
}
static ACPI_STATUS region_handler(UINT32 Function, ACPI_PHYSICAL_ADDRESS address, UINT32 bit_width,
UINT64 *value, void *handler_ctx, void *region_ctx)
{
return AE_OK;
}
static ACPI_STATUS region_init(ACPI_HANDLE RegionHandle, UINT32 Function, void *handler_ctx,
void **region_ctx)
{
if (Function == ACPI_REGION_DEACTIVATE) {
*region_ctx = NULL;
} else {
*region_ctx = RegionHandle;
}
return AE_OK;
}
static ACPI_STATUS install_handlers(void)
{
ACPI_STATUS status;
/* Install global notify handler */
status = AcpiInstallNotifyHandler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY, notify_handler,
NULL);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While installing Notify handler"));
goto exit;
}
status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_SYSTEM_MEMORY,
region_handler, region_init, NULL);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While installing an OpRegion handler"));
}
exit:
return status;
}
static ACPI_STATUS initialize_acpica(void)
{
ACPI_STATUS status;
/* Initialize the ACPI subsystem */
status = AcpiInitializeSubsystem();
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While initializing ACPI"));
goto exit;
}
/* Initialize the ACPI Table Manager and get all ACPI tables */
if (!bus_ctx.early_init) {
status = AcpiInitializeTables(NULL, 16, FALSE);
} else {
/* Copy the root table list to dynamic memory if already initialized */
status = AcpiReallocateRootTable();
}
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While initializing Table Manager"));
goto exit;
}
/* Initialize the ACPI hardware */
status = AcpiEnableSubsystem(ACPI_FULL_INITIALIZATION);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While enabling ACPI"));
goto exit;
}
/* Install local handlers */
status = install_handlers();
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While installing handlers"));
goto exit;
}
/* Create the ACPI namespace from ACPI tables */
status = AcpiLoadTables();
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While loading ACPI tables"));
goto exit;
}
/* Complete the ACPI namespace object initialization */
status = AcpiInitializeObjects(ACPI_FULL_INITIALIZATION);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "While initializing ACPI objects"));
}
exit:
return status;
}
static ACPI_NAMESPACE_NODE *acpi_name_lookup(char *name)
{
char *path;
ACPI_STATUS status;
ACPI_NAMESPACE_NODE *node;
LOG_DBG("");
status = AcpiNsInternalizeName(name, &path);
if (ACPI_FAILURE(status)) {
LOG_ERR("Invalid namestring: %s", name);
return NULL;
}
status = AcpiNsLookup(NULL, path, ACPI_TYPE_ANY, ACPI_IMODE_EXECUTE,
ACPI_NS_NO_UPSEARCH | ACPI_NS_DONT_OPEN_SCOPE, NULL, &node);
if (ACPI_FAILURE(status)) {
LOG_ERR("Could not locate name: %s, %d", name, status);
node = NULL;
}
ACPI_FREE(path);
return node;
}
static ACPI_NAMESPACE_NODE *acpi_evaluate_method(char *bus_name, char *method)
{
ACPI_NAMESPACE_NODE *node;
ACPI_NAMESPACE_NODE *handle;
ACPI_NAMESPACE_NODE *prt_node = NULL;
LOG_DBG("%s", bus_name);
handle = acpi_name_lookup(bus_name);
if (!handle) {
LOG_ERR("No ACPI node with given name: %s", bus_name);
goto exit;
}
if (handle->Type != ACPI_TYPE_DEVICE) {
LOG_ERR("No ACPI node foud with given name: %s", bus_name);
goto exit;
}
node = ACPI_CAST_PTR(ACPI_NAMESPACE_NODE, handle);
(void)AcpiGetHandle(node, method, ACPI_CAST_PTR(ACPI_HANDLE, &prt_node));
if (!prt_node) {
LOG_ERR("No entry for the ACPI node with given name: %s", bus_name);
goto exit;
}
return node;
exit:
return NULL;
}
static ACPI_STATUS acpi_enable_pic_mode(void)
{
ACPI_STATUS status;
ACPI_OBJECT_LIST arg_list;
ACPI_OBJECT arg[1];
arg_list.Count = 1;
arg_list.Pointer = arg;
arg[0].Type = ACPI_TYPE_INTEGER;
arg[0].Integer.Value = 1;
status = AcpiEvaluateObject(NULL, "\\_PIC", &arg_list, NULL);
if (ACPI_FAILURE(status)) {
LOG_WRN("error While executing \\_pic method: %d", status);
}
return status;
}
static int acpi_get_irq_table(struct acpi *bus, char *bus_name,
ACPI_PCI_ROUTING_TABLE *rt_table, uint32_t rt_size)
{
ACPI_BUFFER rt_buffer;
ACPI_NAMESPACE_NODE *node;
int status;
LOG_DBG("%s", bus_name);
node = acpi_evaluate_method(bus_name, METHOD_NAME__PRT);
if (!node) {
LOG_ERR("Evaluation failed for given device: %s", bus_name);
return -ENODEV;
}
rt_buffer.Pointer = rt_table;
rt_buffer.Length = ACPI_DEBUG_BUFFER_SIZE;
status = AcpiGetIrqRoutingTable(node, &rt_buffer);
if (ACPI_FAILURE(status)) {
LOG_ERR("unable to retrieve IRQ Routing Table: %s", bus_name);
return -EIO;
}
for (int i = 0; i < CONFIG_ACPI_MAX_PRT_ENTRY; i++) {
if (!bus->pci_prt_table[i].SourceIndex) {
break;
}
/* mark the PRT irq numbers as reserved. */
arch_irq_set_used(bus->pci_prt_table[i].SourceIndex);
}
return 0;
}
static int acpi_retrieve_legacy_irq(struct acpi *bus)
{
/* TODO: assume platform have only one PCH with single PCI bus (bus 0). */
return acpi_get_irq_table(bus, "_SB.PC00", bus->pci_prt_table, sizeof(bus->pci_prt_table));
}
static ACPI_STATUS dev_resource_enum_callback(ACPI_HANDLE obj_handle, UINT32 level, void *ctx,
void **ret_value)
{
ACPI_NAMESPACE_NODE *node;
ACPI_BUFFER rt_buffer;
struct acpi *bus = (struct acpi *)ctx;
struct acpi_dev *child_dev;
node = ACPI_CAST_PTR(ACPI_NAMESPACE_NODE, obj_handle);
char *path_name;
int status;
ACPI_DEVICE_INFO *dev_info;
LOG_DBG("%s %p\n", __func__, node);
/* get device info such as HID, Class ID etc. */
status = AcpiGetObjectInfo(obj_handle, &dev_info);
if (ACPI_FAILURE(status)) {
LOG_ERR("AcpiGetObjectInfo failed: %s", AcpiFormatException(status));
goto exit;
}
if (bus->num_dev >= CONFIG_ACPI_DEV_MAX) {
return AE_NO_MEMORY;
}
child_dev = (struct acpi_dev *)&bus->child_dev[bus->num_dev++];
child_dev->handle = obj_handle;
child_dev->dev_info = dev_info;
path_name = AcpiNsGetNormalizedPathname(node, TRUE);
if (!path_name) {
LOG_ERR("No memory for path_name\n");
goto exit;
} else {
LOG_DBG("Device path: %s\n", path_name);
child_dev->path = path_name;
}
rt_buffer.Pointer = NULL;
rt_buffer.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
status = AcpiGetCurrentResources(node, &rt_buffer);
if (ACPI_FAILURE(status)) {
LOG_DBG("AcpiGetCurrentResources failed: %s", AcpiFormatException(status));
} else {
child_dev->res_lst = rt_buffer.Pointer;
}
exit:
return status;
}
static int acpi_enum_devices(struct acpi *bus)
{
LOG_DBG("");
AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX,
dev_resource_enum_callback, NULL, bus, NULL);
return 0;
}
static int acpi_init(void)
{
int status;
LOG_DBG("");
if (bus_ctx.status != AE_NOT_CONFIGURED) {
LOG_DBG("acpi init already done");
return bus_ctx.status;
}
/* For debug version only */
ACPI_DEBUG_INITIALIZE();
status = initialize_acpica();
if (ACPI_FAILURE(status)) {
LOG_ERR("Error in ACPI init:%d", status);
goto exit;
}
/* Enable IO APIC mode */
status = acpi_enable_pic_mode();
if (ACPI_FAILURE(status)) {
LOG_WRN("Error in enable pic mode acpi method:%d", status);
}
status = acpi_retrieve_legacy_irq(&bus_ctx);
if (status) {
LOG_ERR("Error in retrieve legacy interrupt info:%d", status);
goto exit;
}
acpi_enum_devices(&bus_ctx);
exit:
bus_ctx.status = status;
return status;
}
static int acpi_early_init(void)
{
ACPI_STATUS status;
LOG_DBG("");
if (bus_ctx.early_init) {
LOG_DBG("acpi early init already done");
return 0;
}
status = AcpiInitializeTables(acpi_table, CONFIG_ACPI_MAX_INIT_TABLES, TRUE);
if (ACPI_FAILURE(status)) {
LOG_ERR("Error in acpi table init:%d", status);
return -EIO;
}
bus_ctx.early_init = true;
return 0;
}
uint32_t acpi_legacy_irq_get(pcie_bdf_t bdf)
{
uint32_t slot = PCIE_BDF_TO_DEV(bdf), pin;
LOG_DBG("");
if (check_init_status()) {
return UINT_MAX;
}
pin = (pcie_conf_read(bdf, PCIE_CONF_INTR) >> 8) & 0x3;
LOG_DBG("Device irq info: slot:%d pin:%d", slot, pin);
for (int i = 0; i < CONFIG_ACPI_MAX_PRT_ENTRY; i++) {
if (((bus_ctx.pci_prt_table[i].Address >> 16) & 0xffff) == slot &&
bus_ctx.pci_prt_table[i].Pin + 1 == pin) {
LOG_DBG("[%d]Device irq info: slot:%d pin:%d irq:%d", i, slot, pin,
bus_ctx.pci_prt_table[i].SourceIndex);
return bus_ctx.pci_prt_table[i].SourceIndex;
}
}
return UINT_MAX;
}
int acpi_current_resource_get(char *dev_name, ACPI_RESOURCE **res)
{
ACPI_BUFFER rt_buffer;
ACPI_NAMESPACE_NODE *node;
int status;
LOG_DBG("%s", dev_name);
status = check_init_status();
if (status) {
return status;
}
node = acpi_evaluate_method(dev_name, METHOD_NAME__CRS);
if (!node) {
LOG_ERR("Evaluation failed for given device: %s", dev_name);
status = -ENOTSUP;
goto exit;
}
rt_buffer.Pointer = NULL;
rt_buffer.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
status = AcpiGetCurrentResources(node, &rt_buffer);
if (ACPI_FAILURE(status)) {
LOG_ERR("AcpiGetCurrentResources failed: %s", AcpiFormatException(status));
status = -ENOTSUP;
} else {
*res = rt_buffer.Pointer;
}
exit:
return status;
}
int acpi_possible_resource_get(char *dev_name, ACPI_RESOURCE **res)
{
ACPI_BUFFER rt_buffer;
ACPI_NAMESPACE_NODE *node;
int status;
LOG_DBG("%s", dev_name);
status = check_init_status();
if (status) {
return status;
}
node = acpi_evaluate_method(dev_name, METHOD_NAME__PRS);
if (!node) {
LOG_ERR("Evaluation failed for given device: %s", dev_name);
status = -ENOTSUP;
goto exit;
}
rt_buffer.Pointer = NULL;
rt_buffer.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
AcpiGetPossibleResources(node, &rt_buffer);
*res = rt_buffer.Pointer;
exit:
return status;
}
int acpi_current_resource_free(ACPI_RESOURCE *res)
{
ACPI_FREE(res);
return 0;
}
int acpi_get_irq_routing_table(char *bus_name,
ACPI_PCI_ROUTING_TABLE *rt_table, size_t rt_size)
{
int ret;
ret = check_init_status();
if (ret) {
return ret;
}
return acpi_get_irq_table(&bus_ctx, bus_name, rt_table, rt_size);
}
ACPI_RESOURCE *acpi_resource_parse(ACPI_RESOURCE *res, int res_type)
{
do {
if (!res->Length) {
LOG_DBG("Error: zero length found!\n");
break;
} else if (res->Type == res_type) {
break;
}
res = ACPI_NEXT_RESOURCE(res);
} while (res->Type != ACPI_RESOURCE_TYPE_END_TAG);
if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) {
return NULL;
}
return res;
}
static int acpi_res_type(ACPI_RESOURCE *res)
{
int type;
switch (res->Type) {
case ACPI_RESOURCE_TYPE_IO:
type = ACPI_RESOURCE_TYPE_IO;
break;
case ACPI_RESOURCE_TYPE_FIXED_IO:
type = ACPI_RESOURCE_TYPE_FIXED_IO;
break;
case ACPI_RESOURCE_TYPE_MEMORY24:
type = ACPI_RESOURCE_TYPE_MEMORY24;
break;
case ACPI_RESOURCE_TYPE_MEMORY32:
type = ACPI_RESOURCE_TYPE_MEMORY32;
break;
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
type = ACPI_RESOURCE_TYPE_FIXED_MEMORY32;
break;
case ACPI_RESOURCE_TYPE_ADDRESS16:
type = ACPI_RESOURCE_TYPE_ADDRESS16;
break;
case ACPI_RESOURCE_TYPE_ADDRESS32:
type = ACPI_RESOURCE_TYPE_ADDRESS32;
break;
case ACPI_RESOURCE_TYPE_ADDRESS64:
type = ACPI_RESOURCE_TYPE_ADDRESS64;
break;
case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
type = ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64;
break;
default:
type = ACPI_RESOURCE_TYPE_MAX;
}
return type;
}
int acpi_device_type_get(ACPI_RESOURCE *res)
{
int type = ACPI_RESOURCE_TYPE_MAX;
do {
if (!res->Length) {
LOG_ERR("Error: zero length found!\n");
break;
}
type = acpi_res_type(res);
if (type != ACPI_RESOURCE_TYPE_MAX) {
break;
}
res = ACPI_NEXT_RESOURCE(res);
} while (res->Type != ACPI_RESOURCE_TYPE_END_TAG);
return type;
}
struct acpi_dev *acpi_device_get(char *hid, int inst)
{
struct acpi_dev *child_dev;
int i = 0, inst_id;
LOG_DBG("");
if (check_init_status()) {
return NULL;
}
do {
child_dev = &bus_ctx.child_dev[i];
if (!child_dev->path) {
LOG_DBG("NULL device path found\n");
continue;
}
if (!child_dev->res_lst || !child_dev->dev_info ||
!child_dev->dev_info->HardwareId.Length) {
continue;
}
if (!strcmp(hid, child_dev->dev_info->HardwareId.String)) {
if (child_dev->dev_info->UniqueId.Length) {
inst_id = atoi(child_dev->dev_info->UniqueId.String);
if (inst_id == inst) {
return child_dev;
}
} else {
return child_dev;
}
}
} while (i++ < bus_ctx.num_dev);
return NULL;
}
struct acpi_dev *acpi_device_by_index_get(int index)
{
return index < bus_ctx.num_dev ? &bus_ctx.child_dev[index] : NULL;
}
int acpi_table_get(char *signature, int inst, void **acpi_table)
{
int status;
ACPI_TABLE_HEADER *table;
if (!bus_ctx.early_init) {
status = acpi_early_init();
if (status) {
LOG_ERR("ACPI early int failed");
return status;
}
}
status = AcpiGetTable(signature, inst, &table);
if (ACPI_FAILURE(status)) {
LOG_ERR("ACPI get table failed: %d", status);
return -EIO;
}
*acpi_table = table;
return 0;
}