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>
This commit is contained in:
parent
90347583ac
commit
f25dfcf88c
8 changed files with 1117 additions and 1 deletions
|
@ -82,5 +82,4 @@ source "drivers/watchdog/Kconfig"
|
|||
source "drivers/wifi/Kconfig"
|
||||
source "drivers/xen/Kconfig"
|
||||
source "drivers/sip_svc/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
125
include/zephyr/acpi/acpi.h
Normal file
125
include/zephyr/acpi/acpi.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_ACPI_H_
|
||||
#define ZEPHYR_INCLUDE_DRIVERS_ACPI_H_
|
||||
#include <acpica/source/include/acpi.h>
|
||||
#include <zephyr/drivers/pcie/pcie.h>
|
||||
|
||||
#define ACPI_RES_INVALID ACPI_RESOURCE_TYPE_MAX
|
||||
|
||||
struct acpi_dev {
|
||||
ACPI_HANDLE handle;
|
||||
char *path;
|
||||
char hid[CONFIG_ACPI_HID_LEN_MAX];
|
||||
ACPI_RESOURCE *res_lst;
|
||||
int res_type;
|
||||
ACPI_DEVICE_INFO *dev_info;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief retrieve legacy interrupt number for a PCI device.
|
||||
*
|
||||
* @param bdf the BDF of endpoint/PCI device
|
||||
* @return return IRQ number or UINT_MAX if not fund
|
||||
*/
|
||||
uint32_t acpi_legacy_irq_get(pcie_bdf_t bdf);
|
||||
|
||||
/**
|
||||
* @brief retrieve current resource setting of a device.
|
||||
*
|
||||
* @param dev_name the name of the device
|
||||
* @param res the list of acpi resource list
|
||||
* @return return 0 on success or error code
|
||||
*/
|
||||
int acpi_current_resource_get(char *dev_name, ACPI_RESOURCE **res);
|
||||
|
||||
/**
|
||||
* @brief retrieve possible resource setting of a device.
|
||||
*
|
||||
* @param dev_name the name of the device
|
||||
* @param res the list of acpi resource list
|
||||
* @return return 0 on success or error code
|
||||
*/
|
||||
int acpi_possible_resource_get(char *dev_name, ACPI_RESOURCE **res);
|
||||
|
||||
/**
|
||||
* @brief Free current resource list memory which is retrived by
|
||||
* acpi_current_resource_get.
|
||||
*
|
||||
* @param res the list of acpi resource list
|
||||
* @return return 0 on success or error code
|
||||
*/
|
||||
int acpi_current_resource_free(ACPI_RESOURCE *res);
|
||||
|
||||
/**
|
||||
* @brief retrieve IRQ routing table of a bus.
|
||||
*
|
||||
* @param bus_name the name of the bus
|
||||
* @param rt_table the IRQ routing table
|
||||
* @param rt_size the the size of IRQ routing table
|
||||
* @return return 0 on success or error code
|
||||
*/
|
||||
int acpi_get_irq_routing_table(char *bus_name,
|
||||
ACPI_PCI_ROUTING_TABLE *rt_table, size_t rt_size);
|
||||
|
||||
/**
|
||||
* @brief parse resource table for given resource type.
|
||||
*
|
||||
* @param res the list of acpi resource list
|
||||
* @param res_type the acpi resource type
|
||||
* @return resource list for the given type on success or NULL
|
||||
*/
|
||||
ACPI_RESOURCE *acpi_resource_parse(ACPI_RESOURCE *res, int res_type);
|
||||
|
||||
/**
|
||||
* @brief retrieve acpi device info for given hardware id and unique id.
|
||||
*
|
||||
* @param hid the hardware id of the acpi child device
|
||||
* @param inst the unique id of the acpi child device
|
||||
* @return acpi child device info on success or NULL
|
||||
*/
|
||||
struct acpi_dev *acpi_device_get(char *hid, int inst);
|
||||
|
||||
/**
|
||||
* @brief retrieve acpi device info form index.
|
||||
*
|
||||
* @param index the device index of an acpi child device
|
||||
* @return acpi child device info on success or NULL
|
||||
*/
|
||||
struct acpi_dev *acpi_device_by_index_get(int index);
|
||||
|
||||
/**
|
||||
* @brief parse resource table for irq info.
|
||||
*
|
||||
* @param res_lst the list of acpi resource list
|
||||
* @return irq resource list on success or NULL
|
||||
*/
|
||||
static inline ACPI_RESOURCE_IRQ *acpi_irq_res_get(ACPI_RESOURCE *res_lst)
|
||||
{
|
||||
ACPI_RESOURCE *res = acpi_resource_parse(res_lst, ACPI_RESOURCE_TYPE_IRQ);
|
||||
|
||||
return res ? &res->Data.Irq : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief parse resource table for identify resource type.
|
||||
*
|
||||
* @param res the list of acpi resource list
|
||||
* @return resource type on success or invalid resource type
|
||||
*/
|
||||
int acpi_device_type_get(ACPI_RESOURCE *res);
|
||||
|
||||
/**
|
||||
* @brief retrieve acpi table for the given signature.
|
||||
*
|
||||
* @param signature pointer to the 4-character ACPI signature for the requested table
|
||||
* @param inst instance number for the requested table
|
||||
* @param acpi_table pointer to the acpi table
|
||||
* @return return 0 on success or error code
|
||||
*/
|
||||
int acpi_table_get(char *signature, int inst, void **acpi_table);
|
||||
|
||||
#endif
|
|
@ -9,3 +9,4 @@ add_subdirectory(hash)
|
|||
add_subdirectory(os)
|
||||
add_subdirectory_ifdef(CONFIG_SMF smf)
|
||||
add_subdirectory_ifdef(CONFIG_OPENAMP open-amp)
|
||||
add_subdirectory_ifdef(CONFIG_ACPI acpi)
|
||||
|
|
|
@ -17,4 +17,5 @@ source "lib/open-amp/Kconfig"
|
|||
|
||||
source "lib/smf/Kconfig"
|
||||
|
||||
source "lib/acpi/Kconfig"
|
||||
endmenu
|
||||
|
|
6
lib/acpi/CMakeLists.txt
Normal file
6
lib/acpi/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources(acpi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ACPI_SHELL acpi_shell.c)
|
54
lib/acpi/Kconfig
Normal file
54
lib/acpi/Kconfig
Normal file
|
@ -0,0 +1,54 @@
|
|||
# ACPI configuration options
|
||||
|
||||
# Copyright (c) 2023 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig ACPI
|
||||
bool "ACPI support"
|
||||
help
|
||||
This option enables support for ACPI driver.
|
||||
|
||||
if ACPI
|
||||
|
||||
module = ACPI
|
||||
module-str = acpi
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
config ACPI_MAX_PRT_ENTRY
|
||||
int "Size of PRT buffer"
|
||||
default 4096
|
||||
help
|
||||
Size of PRT table buffer.
|
||||
|
||||
config ACPI_SHELL
|
||||
bool "ACPI command Shell"
|
||||
default y
|
||||
depends on SHELL
|
||||
help
|
||||
Enable commands for debugging ACPI using the built-in shell.
|
||||
|
||||
config ACPI_DEV_MAX
|
||||
int "maximum child devices"
|
||||
default 1000
|
||||
help
|
||||
maximum acpi child devices.
|
||||
|
||||
config ACPI_INIT_PRIORITY
|
||||
int "acpi boot time init level"
|
||||
default 42
|
||||
help
|
||||
boot time init level for acpi driver.
|
||||
|
||||
config ACPI_MAX_INIT_TABLES
|
||||
int "acpi table size"
|
||||
default 16
|
||||
help
|
||||
acpi table size.
|
||||
|
||||
endif # ACPI
|
||||
|
||||
config ACPI_HID_LEN_MAX
|
||||
int "Size of HID name"
|
||||
default 12
|
||||
help
|
||||
Size of HID string.
|
649
lib/acpi/acpi.c
Normal file
649
lib/acpi/acpi.c
Normal file
|
@ -0,0 +1,649 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
281
lib/acpi/acpi_shell.c
Normal file
281
lib/acpi/acpi_shell.c
Normal file
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <stdbool.h>
|
||||
#include <zephyr/drivers/pcie/pcie.h>
|
||||
#include <zephyr/acpi/acpi.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
|
||||
#define MAX_PR_BUFF (4096)
|
||||
|
||||
static ACPI_PCI_ROUTING_TABLE irq_prt_table[CONFIG_ACPI_MAX_PRT_ENTRY];
|
||||
static uint8_t prs_buffer[MAX_PR_BUFF];
|
||||
|
||||
static void dump_dev_res(const struct shell *sh, ACPI_RESOURCE *res_lst)
|
||||
{
|
||||
ACPI_RESOURCE *res = res_lst;
|
||||
|
||||
shell_print(sh, "\n**** ACPI Device Resource Info ****\n");
|
||||
|
||||
do {
|
||||
|
||||
if (!res->Length) {
|
||||
shell_error(sh, "Error: zero length found!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (res->Type) {
|
||||
case ACPI_RESOURCE_TYPE_IRQ:
|
||||
shell_print(sh, "\nACPI_RESOURCE_TYPE_IRQ\n\n");
|
||||
ACPI_RESOURCE_IRQ *irq_res = &res->Data.Irq;
|
||||
|
||||
shell_print(sh,
|
||||
"DescriptorLength: %x, Triggering:%x, Polarity:%x, Shareable:%x,",
|
||||
irq_res->DescriptorLength, irq_res->Triggering, irq_res->Polarity,
|
||||
irq_res->Shareable);
|
||||
shell_print(sh,
|
||||
"InterruptCount:%d, Interrupts[0]:%x\n", irq_res->InterruptCount,
|
||||
irq_res->Interrupts[0]);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_IO:
|
||||
ACPI_RESOURCE_IO * io_res = &res->Data.Io;
|
||||
|
||||
shell_print(sh, "\n ACPI_RESOURCE_TYPE_IO\n");
|
||||
shell_print(sh,
|
||||
"IoDecode: %x, Alignment:%x, AddressLength:%x, Minimum:%x,Maximum:%x\n",
|
||||
io_res->IoDecode, io_res->Alignment,
|
||||
io_res->AddressLength, io_res->Minimum,
|
||||
io_res->Maximum);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_DMA:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_DMA\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_START_DEPENDENT\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_END_DEPENDENT:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_END_DEPENDENT\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_FIXED_IO:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_FIXED_IO\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_VENDOR:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_VENDOR\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_MEMORY24:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_MEMORY24\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_MEMORY32:
|
||||
ACPI_RESOURCE_MEMORY32 * mem_res = &res->Data.Memory32;
|
||||
|
||||
shell_print(sh, "\nACPI_RESOURCE_TYPE_MEMORY32\n\n");
|
||||
shell_print(sh, "Minimum:%x, Maximum:%x\n",
|
||||
mem_res->Minimum, mem_res->Maximum);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
||||
ACPI_RESOURCE_FIXED_MEMORY32 * fix_mem_res = &res->Data.FixedMemory32;
|
||||
|
||||
shell_print(sh, "\nACPI_RESOURCE_TYPE_FIXED_MEMORY32\n\n");
|
||||
shell_print(sh, "Address:%x\n", fix_mem_res->Address);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_ADDRESS16:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_ADDRESS16\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_ADDRESS32:
|
||||
ACPI_RESOURCE_ADDRESS32 * add_res = &res->Data.Address32;
|
||||
|
||||
shell_print(sh, "\nACPI_RESOURCE_TYPE_ADDRESS32\n\n");
|
||||
shell_print(sh, "Minimum:%x, Maximum:%x\n", add_res->Address.Minimum,
|
||||
add_res->Address.Maximum);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_ADDRESS64:
|
||||
ACPI_RESOURCE_ADDRESS64 * add_res64 = &res->Data.Address64;
|
||||
|
||||
shell_print(sh, "\nACPI_RESOURCE_TYPE_ADDRESS64\n\n");
|
||||
shell_print(sh,
|
||||
"Minimum:%llx, Maximum:%llx\n", add_res64->Address.Minimum,
|
||||
add_res64->Address.Maximum);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_EXTENDED_IRQ\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_GENERIC_REGISTER:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_GENERIC_REGISTER\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_GPIO:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_GPIO\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_FIXED_DMA:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_FIXED_DMA\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_SERIAL_BUS:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_SERIAL_BUS\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_PIN_FUNCTION:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_PIN_FUNCTION\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_PIN_CONFIG:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_PIN_CONFIG\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_PIN_GROUP:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_PIN_GROUP\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_PIN_GROUP_FUNCTION:
|
||||
shell_print(sh,
|
||||
"ACPI_RESOURCE_TYPE_PIN_GROUP_FUNCTION\n\n");
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_PIN_GROUP_CONFIG:
|
||||
shell_print(sh, "ACPI_RESOURCE_TYPE_PIN_GROUP_CONFIG\n\n");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
res = ACPI_NEXT_RESOURCE(res);
|
||||
|
||||
} while (res->Type != ACPI_RESOURCE_TYPE_END_TAG);
|
||||
}
|
||||
|
||||
static int dump_dev_crs(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
int status;
|
||||
ACPI_RESOURCE *res_lst;
|
||||
|
||||
if (argc < 2) {
|
||||
shell_error(sh, "invalid arugment\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
status = acpi_current_resource_get(argv[1], &res_lst);
|
||||
if (status) {
|
||||
shell_error(sh, "Error on ACPI _CRS method: %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
dump_dev_res(sh, res_lst);
|
||||
|
||||
acpi_current_resource_free(res_lst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dump_dev_prs(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
int status;
|
||||
ACPI_RESOURCE *res_lst = (ACPI_RESOURCE *)prs_buffer;
|
||||
|
||||
if (argc < 2) {
|
||||
shell_error(sh, "invalid arugment\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
status = acpi_possible_resource_get(argv[1], &res_lst);
|
||||
if (status) {
|
||||
shell_error(sh, "Error in on ACPI _PRS method: %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
dump_dev_res(sh, res_lst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dump_prt(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
int status, cnt;
|
||||
ACPI_PCI_ROUTING_TABLE *prt;
|
||||
|
||||
if (argc < 2) {
|
||||
shell_error(sh, "invalid arugment\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
status = acpi_get_irq_routing_table(argv[1],
|
||||
irq_prt_table, sizeof(irq_prt_table));
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
prt = irq_prt_table;
|
||||
for (cnt = 0; prt->Length; cnt++) {
|
||||
shell_print(sh, "[%02X] PCI IRQ Routing Table Package\n", cnt);
|
||||
shell_print(sh,
|
||||
"DevNum: %lld Pin:%d IRQ: %d\n", (prt->Address >> 16) & 0xFFFF, prt->Pin,
|
||||
prt->SourceIndex);
|
||||
|
||||
prt = ACPI_ADD_PTR(ACPI_PCI_ROUTING_TABLE, prt, prt->Length);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enum_dev(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
struct acpi_dev *dev;
|
||||
|
||||
if (argc < 2) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev = acpi_device_get(argv[1], 0);
|
||||
if (!dev || !dev->res_lst) {
|
||||
shell_error(sh, "acpi get device failed for HID: %s\n", argv[1]);
|
||||
return -EIO;
|
||||
}
|
||||
shell_print(sh, "\nName:%s\n", dev->path ? dev->path : "Non");
|
||||
dump_dev_res(sh, dev->res_lst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_table(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
int status;
|
||||
ACPI_TABLE_HEADER *table;
|
||||
|
||||
if (argc < 2) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
shell_print(sh, "ACPI Table Name: %s\n", argv[1]);
|
||||
|
||||
status = acpi_table_get(argv[1], 0, (void **)&table);
|
||||
if (status) {
|
||||
shell_error(sh, "ACPI get table failed: %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
shell_print(sh, "ACPI Table Info:\n");
|
||||
shell_print(sh, "Signature: %4s Table Length:%d Revision:%d OemId:%s\n",
|
||||
table->Signature, table->Length, table->Revision, table->OemId);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(
|
||||
sub_acpi,
|
||||
SHELL_CMD(crs, NULL,
|
||||
"display device current resource settings (eg:acpi crs _SB.PC00.LPCB.RTC)",
|
||||
dump_dev_crs),
|
||||
SHELL_CMD(prs, NULL,
|
||||
"display device possible resource settings (eg:acpi crs _SB.PC00.LPCB.RTC)",
|
||||
dump_dev_prs),
|
||||
SHELL_CMD(prt, NULL, "display PRT details for a given bus (eg:acpi prt _SB.PC00)",
|
||||
dump_prt),
|
||||
SHELL_CMD(enum, NULL,
|
||||
"enumerate device using hid (for enum HPET timer device,eg:acpi enum PNP0103)",
|
||||
enum_dev),
|
||||
SHELL_CMD(rd_table, NULL,
|
||||
"read acpi table (for read mad table, eg:acpi read_table APIC)",
|
||||
read_table),
|
||||
SHELL_SUBCMD_SET_END /* Array terminated. */
|
||||
);
|
||||
|
||||
SHELL_CMD_REGISTER(acpi, &sub_acpi, "Demo commands", NULL);
|
Loading…
Add table
Add a link
Reference in a new issue