root/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/******************************************************************************
 *
 * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables
 *
 * Copyright (C) 2000 - 2025, Intel Corp.
 *
 *****************************************************************************/

#include "acpidump.h"

#define _COMPONENT          ACPI_OS_SERVICES
ACPI_MODULE_NAME("oslinuxtbl")

#ifndef PATH_MAX
#define PATH_MAX 256
#endif
/* List of information about obtained ACPI tables */
typedef struct osl_table_info {
        struct osl_table_info *next;
        u32 instance;
        char signature[ACPI_NAMESEG_SIZE] ACPI_NONSTRING;

} osl_table_info;

/* Local prototypes */

static acpi_status osl_table_initialize(void);

static acpi_status
osl_table_name_from_file(char *filename, char *signature, u32 *instance);

static acpi_status osl_add_table_to_list(char *signature, u32 instance);

static acpi_status
osl_read_table_from_file(char *filename,
                         acpi_size file_offset,
                         struct acpi_table_header **table);

static acpi_status
osl_map_table(acpi_size address,
              char *signature, struct acpi_table_header **table);

static void osl_unmap_table(struct acpi_table_header *table);

static acpi_physical_address
osl_find_rsdp_via_efi_by_keyword(FILE * file, const char *keyword);

static acpi_physical_address osl_find_rsdp_via_efi(void);

static acpi_status osl_load_rsdp(void);

static acpi_status osl_list_customized_tables(char *directory);

static acpi_status
osl_get_customized_table(char *pathname,
                         char *signature,
                         u32 instance,
                         struct acpi_table_header **table,
                         acpi_physical_address *address);

static acpi_status osl_list_bios_tables(void);

static acpi_status
osl_get_bios_table(char *signature,
                   u32 instance,
                   struct acpi_table_header **table,
                   acpi_physical_address *address);

static acpi_status osl_get_last_status(acpi_status default_status);

/* File locations */

#define DYNAMIC_TABLE_DIR   "/sys/firmware/acpi/tables/dynamic"
#define STATIC_TABLE_DIR    "/sys/firmware/acpi/tables"
#define EFI_SYSTAB          "/sys/firmware/efi/systab"

/* Should we get dynamically loaded SSDTs from DYNAMIC_TABLE_DIR? */

u8 gbl_dump_dynamic_tables = TRUE;

/* Initialization flags */

u8 gbl_table_list_initialized = FALSE;

/* Local copies of main ACPI tables */

struct acpi_table_rsdp gbl_rsdp;
struct acpi_table_fadt *gbl_fadt = NULL;
struct acpi_table_rsdt *gbl_rsdt = NULL;
struct acpi_table_xsdt *gbl_xsdt = NULL;

/* Table addresses */

acpi_physical_address gbl_fadt_address = 0;
acpi_physical_address gbl_rsdp_address = 0;

/* Revision of RSD PTR */

u8 gbl_revision = 0;

struct osl_table_info *gbl_table_list_head = NULL;
u32 gbl_table_count = 0;

/******************************************************************************
 *
 * FUNCTION:    osl_get_last_status
 *
 * PARAMETERS:  default_status  - Default error status to return
 *
 * RETURN:      Status; Converted from errno.
 *
 * DESCRIPTION: Get last errno and convert it to acpi_status.
 *
 *****************************************************************************/

static acpi_status osl_get_last_status(acpi_status default_status)
{

        switch (errno) {
        case EACCES:
        case EPERM:

                return (AE_ACCESS);

        case ENOENT:

                return (AE_NOT_FOUND);

        case ENOMEM:

                return (AE_NO_MEMORY);

        default:

                return (default_status);
        }
}

/******************************************************************************
 *
 * FUNCTION:    acpi_os_get_table_by_address
 *
 * PARAMETERS:  address         - Physical address of the ACPI table
 *              table           - Where a pointer to the table is returned
 *
 * RETURN:      Status; Table buffer is returned if AE_OK.
 *              AE_NOT_FOUND: A valid table was not found at the address
 *
 * DESCRIPTION: Get an ACPI table via a physical memory address.
 *
 *****************************************************************************/

acpi_status
acpi_os_get_table_by_address(acpi_physical_address address,
                             struct acpi_table_header **table)
{
        u32 table_length;
        struct acpi_table_header *mapped_table;
        struct acpi_table_header *local_table = NULL;
        acpi_status status = AE_OK;

        /* Get main ACPI tables from memory on first invocation of this function */

        status = osl_table_initialize();
        if (ACPI_FAILURE(status)) {
                return (status);
        }

        /* Map the table and validate it */

        status = osl_map_table(address, NULL, &mapped_table);
        if (ACPI_FAILURE(status)) {
                return (status);
        }

        /* Copy table to local buffer and return it */

        table_length = ap_get_table_length(mapped_table);
        if (table_length == 0) {
                status = AE_BAD_HEADER;
                goto exit;
        }

        local_table = calloc(1, table_length);
        if (!local_table) {
                status = AE_NO_MEMORY;
                goto exit;
        }

        memcpy(local_table, mapped_table, table_length);

exit:
        osl_unmap_table(mapped_table);
        *table = local_table;
        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    acpi_os_get_table_by_name
 *
 * PARAMETERS:  signature       - ACPI Signature for desired table. Must be
 *                                a null terminated 4-character string.
 *              instance        - Multiple table support for SSDT/UEFI (0...n)
 *                                Must be 0 for other tables.
 *              table           - Where a pointer to the table is returned
 *              address         - Where the table physical address is returned
 *
 * RETURN:      Status; Table buffer and physical address returned if AE_OK.
 *              AE_LIMIT: Instance is beyond valid limit
 *              AE_NOT_FOUND: A table with the signature was not found
 *
 * NOTE:        Assumes the input signature is uppercase.
 *
 *****************************************************************************/

acpi_status
acpi_os_get_table_by_name(char *signature,
                          u32 instance,
                          struct acpi_table_header **table,
                          acpi_physical_address *address)
{
        acpi_status status;

        /* Get main ACPI tables from memory on first invocation of this function */

        status = osl_table_initialize();
        if (ACPI_FAILURE(status)) {
                return (status);
        }

        /* Not a main ACPI table, attempt to extract it from the RSDT/XSDT */

        if (!gbl_dump_customized_tables) {

                /* Attempt to get the table from the memory */

                status =
                    osl_get_bios_table(signature, instance, table, address);
        } else {
                /* Attempt to get the table from the static directory */

                status = osl_get_customized_table(STATIC_TABLE_DIR, signature,
                                                  instance, table, address);
        }

        if (ACPI_FAILURE(status) && status == AE_LIMIT) {
                if (gbl_dump_dynamic_tables) {

                        /* Attempt to get a dynamic table */

                        status =
                            osl_get_customized_table(DYNAMIC_TABLE_DIR,
                                                     signature, instance, table,
                                                     address);
                }
        }

        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    osl_add_table_to_list
 *
 * PARAMETERS:  signature       - Table signature
 *              instance        - Table instance
 *
 * RETURN:      Status; Successfully added if AE_OK.
 *              AE_NO_MEMORY: Memory allocation error
 *
 * DESCRIPTION: Insert a table structure into OSL table list.
 *
 *****************************************************************************/

static acpi_status osl_add_table_to_list(char *signature, u32 instance)
{
        struct osl_table_info *new_info;
        struct osl_table_info *next;
        u32 next_instance = 0;
        u8 found = FALSE;

        new_info = calloc(1, sizeof(struct osl_table_info));
        if (!new_info) {
                return (AE_NO_MEMORY);
        }

        ACPI_COPY_NAMESEG(new_info->signature, signature);

        if (!gbl_table_list_head) {
                gbl_table_list_head = new_info;
        } else {
                next = gbl_table_list_head;
                while (1) {
                        if (ACPI_COMPARE_NAMESEG(next->signature, signature)) {
                                if (next->instance == instance) {
                                        found = TRUE;
                                }
                                if (next->instance >= next_instance) {
                                        next_instance = next->instance + 1;
                                }
                        }

                        if (!next->next) {
                                break;
                        }
                        next = next->next;
                }
                next->next = new_info;
        }

        if (found) {
                if (instance) {
                        fprintf(stderr,
                                "%4.4s: Warning unmatched table instance %d, expected %d\n",
                                signature, instance, next_instance);
                }
                instance = next_instance;
        }

        new_info->instance = instance;
        gbl_table_count++;

        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    acpi_os_get_table_by_index
 *
 * PARAMETERS:  index           - Which table to get
 *              table           - Where a pointer to the table is returned
 *              instance        - Where a pointer to the table instance no. is
 *                                returned
 *              address         - Where the table physical address is returned
 *
 * RETURN:      Status; Table buffer and physical address returned if AE_OK.
 *              AE_LIMIT: Index is beyond valid limit
 *
 * DESCRIPTION: Get an ACPI table via an index value (0 through n). Returns
 *              AE_LIMIT when an invalid index is reached. Index is not
 *              necessarily an index into the RSDT/XSDT.
 *
 *****************************************************************************/

acpi_status
acpi_os_get_table_by_index(u32 index,
                           struct acpi_table_header **table,
                           u32 *instance, acpi_physical_address *address)
{
        struct osl_table_info *info;
        acpi_status status;
        u32 i;

        /* Get main ACPI tables from memory on first invocation of this function */

        status = osl_table_initialize();
        if (ACPI_FAILURE(status)) {
                return (status);
        }

        /* Validate Index */

        if (index >= gbl_table_count) {
                return (AE_LIMIT);
        }

        /* Point to the table list entry specified by the Index argument */

        info = gbl_table_list_head;
        for (i = 0; i < index; i++) {
                info = info->next;
        }

        /* Now we can just get the table via the signature */

        status = acpi_os_get_table_by_name(info->signature, info->instance,
                                           table, address);

        if (ACPI_SUCCESS(status)) {
                *instance = info->instance;
        }
        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    osl_find_rsdp_via_efi_by_keyword
 *
 * PARAMETERS:  keyword         - Character string indicating ACPI GUID version
 *                                in the EFI table
 *
 * RETURN:      RSDP address if found
 *
 * DESCRIPTION: Find RSDP address via EFI using keyword indicating the ACPI
 *              GUID version.
 *
 *****************************************************************************/

static acpi_physical_address
osl_find_rsdp_via_efi_by_keyword(FILE * file, const char *keyword)
{
        char buffer[80];
        unsigned long long address = 0;
        char format[32];

        snprintf(format, 32, "%s=%s", keyword, "%llx");
        fseek(file, 0, SEEK_SET);
        while (fgets(buffer, 80, file)) {
                if (sscanf(buffer, format, &address) == 1) {
                        break;
                }
        }

        return ((acpi_physical_address)(address));
}

/******************************************************************************
 *
 * FUNCTION:    osl_find_rsdp_via_efi
 *
 * PARAMETERS:  None
 *
 * RETURN:      RSDP address if found
 *
 * DESCRIPTION: Find RSDP address via EFI.
 *
 *****************************************************************************/

static acpi_physical_address osl_find_rsdp_via_efi(void)
{
        FILE *file;
        acpi_physical_address address = 0;

        file = fopen(EFI_SYSTAB, "r");
        if (file) {
                address = osl_find_rsdp_via_efi_by_keyword(file, "ACPI20");
                if (!address) {
                        address =
                            osl_find_rsdp_via_efi_by_keyword(file, "ACPI");
                }
                fclose(file);
        }

        return (address);
}

/******************************************************************************
 *
 * FUNCTION:    osl_load_rsdp
 *
 * PARAMETERS:  None
 *
 * RETURN:      Status
 *
 * DESCRIPTION: Scan and load RSDP.
 *
 *****************************************************************************/

static acpi_status osl_load_rsdp(void)
{
        struct acpi_table_header *mapped_table;
        u8 *rsdp_address;
        acpi_physical_address rsdp_base;
        acpi_size rsdp_size;

        /* Get RSDP from memory */

        rsdp_size = sizeof(struct acpi_table_rsdp);
        if (gbl_rsdp_base) {
                rsdp_base = gbl_rsdp_base;
        } else {
                rsdp_base = osl_find_rsdp_via_efi();
        }

        if (!rsdp_base) {
                rsdp_base = ACPI_HI_RSDP_WINDOW_BASE;
                rsdp_size = ACPI_HI_RSDP_WINDOW_SIZE;
        }

        rsdp_address = acpi_os_map_memory(rsdp_base, rsdp_size);
        if (!rsdp_address) {
                return (osl_get_last_status(AE_BAD_ADDRESS));
        }

        /* Search low memory for the RSDP */

        mapped_table = ACPI_CAST_PTR(struct acpi_table_header,
                                     acpi_tb_scan_memory_for_rsdp(rsdp_address,
                                                                  rsdp_size));
        if (!mapped_table) {
                acpi_os_unmap_memory(rsdp_address, rsdp_size);
                return (AE_NOT_FOUND);
        }

        gbl_rsdp_address =
            rsdp_base + (ACPI_CAST8(mapped_table) - rsdp_address);

        memcpy(&gbl_rsdp, mapped_table, sizeof(struct acpi_table_rsdp));
        acpi_os_unmap_memory(rsdp_address, rsdp_size);

        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    osl_can_use_xsdt
 *
 * PARAMETERS:  None
 *
 * RETURN:      TRUE if XSDT is allowed to be used.
 *
 * DESCRIPTION: This function collects logic that can be used to determine if
 *              XSDT should be used instead of RSDT.
 *
 *****************************************************************************/

static u8 osl_can_use_xsdt(void)
{
        if (gbl_revision && !acpi_gbl_do_not_use_xsdt) {
                return (TRUE);
        } else {
                return (FALSE);
        }
}

/******************************************************************************
 *
 * FUNCTION:    osl_table_initialize
 *
 * PARAMETERS:  None
 *
 * RETURN:      Status
 *
 * DESCRIPTION: Initialize ACPI table data. Get and store main ACPI tables to
 *              local variables. Main ACPI tables include RSDT, FADT, RSDT,
 *              and/or XSDT.
 *
 *****************************************************************************/

static acpi_status osl_table_initialize(void)
{
        acpi_status status;
        acpi_physical_address address;

        if (gbl_table_list_initialized) {
                return (AE_OK);
        }

        if (!gbl_dump_customized_tables) {

                /* Get RSDP from memory */

                status = osl_load_rsdp();
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                /* Get XSDT from memory */

                if (gbl_rsdp.revision && !gbl_do_not_dump_xsdt) {
                        if (gbl_xsdt) {
                                free(gbl_xsdt);
                                gbl_xsdt = NULL;
                        }

                        gbl_revision = 2;
                        status = osl_get_bios_table(ACPI_SIG_XSDT, 0,
                                                    ACPI_CAST_PTR(struct
                                                                  acpi_table_header
                                                                  *, &gbl_xsdt),
                                                    &address);
                        if (ACPI_FAILURE(status)) {
                                return (status);
                        }
                }

                /* Get RSDT from memory */

                if (gbl_rsdp.rsdt_physical_address) {
                        if (gbl_rsdt) {
                                free(gbl_rsdt);
                                gbl_rsdt = NULL;
                        }

                        status = osl_get_bios_table(ACPI_SIG_RSDT, 0,
                                                    ACPI_CAST_PTR(struct
                                                                  acpi_table_header
                                                                  *, &gbl_rsdt),
                                                    &address);
                        if (ACPI_FAILURE(status)) {
                                return (status);
                        }
                }

                /* Get FADT from memory */

                if (gbl_fadt) {
                        free(gbl_fadt);
                        gbl_fadt = NULL;
                }

                status = osl_get_bios_table(ACPI_SIG_FADT, 0,
                                            ACPI_CAST_PTR(struct
                                                          acpi_table_header *,
                                                          &gbl_fadt),
                                            &gbl_fadt_address);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                /* Add mandatory tables to global table list first */

                status = osl_add_table_to_list(ACPI_RSDP_NAME, 0);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                status = osl_add_table_to_list(ACPI_SIG_RSDT, 0);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                if (gbl_revision == 2) {
                        status = osl_add_table_to_list(ACPI_SIG_XSDT, 0);
                        if (ACPI_FAILURE(status)) {
                                return (status);
                        }
                }

                status = osl_add_table_to_list(ACPI_SIG_DSDT, 0);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                status = osl_add_table_to_list(ACPI_SIG_FACS, 0);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                /* Add all tables found in the memory */

                status = osl_list_bios_tables();
                if (ACPI_FAILURE(status)) {
                        return (status);
                }
        } else {
                /* Add all tables found in the static directory */

                status = osl_list_customized_tables(STATIC_TABLE_DIR);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }
        }

        if (gbl_dump_dynamic_tables) {

                /* Add all dynamically loaded tables in the dynamic directory */

                status = osl_list_customized_tables(DYNAMIC_TABLE_DIR);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }
        }

        gbl_table_list_initialized = TRUE;
        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    osl_list_bios_tables
 *
 * PARAMETERS:  None
 *
 * RETURN:      Status; Table list is initialized if AE_OK.
 *
 * DESCRIPTION: Add ACPI tables to the table list from memory.
 *
 * NOTE:        This works on Linux as table customization does not modify the
 *              addresses stored in RSDP/RSDT/XSDT/FADT.
 *
 *****************************************************************************/

static acpi_status osl_list_bios_tables(void)
{
        struct acpi_table_header *mapped_table = NULL;
        u8 *table_data;
        u8 number_of_tables;
        u8 item_size;
        acpi_physical_address table_address = 0;
        acpi_status status = AE_OK;
        u32 i;

        if (osl_can_use_xsdt()) {
                item_size = sizeof(u64);
                table_data =
                    ACPI_CAST8(gbl_xsdt) + sizeof(struct acpi_table_header);
                number_of_tables =
                    (u8)((gbl_xsdt->header.length -
                          sizeof(struct acpi_table_header))
                         / item_size);
        } else {                /* Use RSDT if XSDT is not available */

                item_size = sizeof(u32);
                table_data =
                    ACPI_CAST8(gbl_rsdt) + sizeof(struct acpi_table_header);
                number_of_tables =
                    (u8)((gbl_rsdt->header.length -
                          sizeof(struct acpi_table_header))
                         / item_size);
        }

        /* Search RSDT/XSDT for the requested table */

        for (i = 0; i < number_of_tables; ++i, table_data += item_size) {
                if (osl_can_use_xsdt()) {
                        table_address =
                            (acpi_physical_address)(*ACPI_CAST64(table_data));
                } else {
                        table_address =
                            (acpi_physical_address)(*ACPI_CAST32(table_data));
                }

                /* Skip NULL entries in RSDT/XSDT */

                if (table_address == 0) {
                        continue;
                }

                status = osl_map_table(table_address, NULL, &mapped_table);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                osl_add_table_to_list(mapped_table->signature, 0);
                osl_unmap_table(mapped_table);
        }

        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    osl_get_bios_table
 *
 * PARAMETERS:  signature       - ACPI Signature for common table. Must be
 *                                a null terminated 4-character string.
 *              instance        - Multiple table support for SSDT/UEFI (0...n)
 *                                Must be 0 for other tables.
 *              table           - Where a pointer to the table is returned
 *              address         - Where the table physical address is returned
 *
 * RETURN:      Status; Table buffer and physical address returned if AE_OK.
 *              AE_LIMIT: Instance is beyond valid limit
 *              AE_NOT_FOUND: A table with the signature was not found
 *
 * DESCRIPTION: Get a BIOS provided ACPI table
 *
 * NOTE:        Assumes the input signature is uppercase.
 *
 *****************************************************************************/

static acpi_status
osl_get_bios_table(char *signature,
                   u32 instance,
                   struct acpi_table_header **table,
                   acpi_physical_address *address)
{
        struct acpi_table_header *local_table = NULL;
        struct acpi_table_header *mapped_table = NULL;
        u8 *table_data;
        u8 number_of_tables;
        u8 item_size;
        u32 current_instance = 0;
        acpi_physical_address table_address;
        acpi_physical_address first_table_address = 0;
        u32 table_length = 0;
        acpi_status status = AE_OK;
        u32 i;

        /* Handle special tables whose addresses are not in RSDT/XSDT */

        if (ACPI_COMPARE_NAMESEG(signature, ACPI_RSDP_NAME) ||
            ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_RSDT) ||
            ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_XSDT) ||
            ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_DSDT) ||
            ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_FACS)) {

find_next_instance:

                table_address = 0;

                /*
                 * Get the appropriate address, either 32-bit or 64-bit. Be very
                 * careful about the FADT length and validate table addresses.
                 * Note: The 64-bit addresses have priority.
                 */
                if (ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_DSDT)) {
                        if (current_instance < 2) {
                                if ((gbl_fadt->header.length >=
                                     MIN_FADT_FOR_XDSDT) && gbl_fadt->Xdsdt
                                    && current_instance == 0) {
                                        table_address =
                                            (acpi_physical_address)gbl_fadt->
                                            Xdsdt;
                                } else
                                    if ((gbl_fadt->header.length >=
                                         MIN_FADT_FOR_DSDT)
                                        && gbl_fadt->dsdt !=
                                        first_table_address) {
                                        table_address =
                                            (acpi_physical_address)gbl_fadt->
                                            dsdt;
                                }
                        }
                } else if (ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_FACS)) {
                        if (current_instance < 2) {
                                if ((gbl_fadt->header.length >=
                                     MIN_FADT_FOR_XFACS) && gbl_fadt->Xfacs
                                    && current_instance == 0) {
                                        table_address =
                                            (acpi_physical_address)gbl_fadt->
                                            Xfacs;
                                } else
                                    if ((gbl_fadt->header.length >=
                                         MIN_FADT_FOR_FACS)
                                        && gbl_fadt->facs !=
                                        first_table_address) {
                                        table_address =
                                            (acpi_physical_address)gbl_fadt->
                                            facs;
                                }
                        }
                } else if (ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_XSDT)) {
                        if (!gbl_revision) {
                                return (AE_BAD_SIGNATURE);
                        }
                        if (current_instance == 0) {
                                table_address =
                                    (acpi_physical_address)gbl_rsdp.
                                    xsdt_physical_address;
                        }
                } else if (ACPI_COMPARE_NAMESEG(signature, ACPI_SIG_RSDT)) {
                        if (current_instance == 0) {
                                table_address =
                                    (acpi_physical_address)gbl_rsdp.
                                    rsdt_physical_address;
                        }
                } else {
                        if (current_instance == 0) {
                                table_address =
                                    (acpi_physical_address)gbl_rsdp_address;
                                signature = ACPI_SIG_RSDP;
                        }
                }

                if (table_address == 0) {
                        goto exit_find_table;
                }

                /* Now we can get the requested special table */

                status = osl_map_table(table_address, signature, &mapped_table);
                if (ACPI_FAILURE(status)) {
                        return (status);
                }

                table_length = ap_get_table_length(mapped_table);
                if (first_table_address == 0) {
                        first_table_address = table_address;
                }

                /* Match table instance */

                if (current_instance != instance) {
                        osl_unmap_table(mapped_table);
                        mapped_table = NULL;
                        current_instance++;
                        goto find_next_instance;
                }
        } else {                /* Case for a normal ACPI table */

                if (osl_can_use_xsdt()) {
                        item_size = sizeof(u64);
                        table_data =
                            ACPI_CAST8(gbl_xsdt) +
                            sizeof(struct acpi_table_header);
                        number_of_tables =
                            (u8)((gbl_xsdt->header.length -
                                  sizeof(struct acpi_table_header))
                                 / item_size);
                } else {        /* Use RSDT if XSDT is not available */

                        item_size = sizeof(u32);
                        table_data =
                            ACPI_CAST8(gbl_rsdt) +
                            sizeof(struct acpi_table_header);
                        number_of_tables =
                            (u8)((gbl_rsdt->header.length -
                                  sizeof(struct acpi_table_header))
                                 / item_size);
                }

                /* Search RSDT/XSDT for the requested table */

                for (i = 0; i < number_of_tables; ++i, table_data += item_size) {
                        if (osl_can_use_xsdt()) {
                                table_address =
                                    (acpi_physical_address)(*ACPI_CAST64
                                                            (table_data));
                        } else {
                                table_address =
                                    (acpi_physical_address)(*ACPI_CAST32
                                                            (table_data));
                        }

                        /* Skip NULL entries in RSDT/XSDT */

                        if (table_address == 0) {
                                continue;
                        }

                        status =
                            osl_map_table(table_address, NULL, &mapped_table);
                        if (ACPI_FAILURE(status)) {
                                return (status);
                        }
                        table_length = mapped_table->length;

                        /* Does this table match the requested signature? */

                        if (!ACPI_COMPARE_NAMESEG
                            (mapped_table->signature, signature)) {
                                osl_unmap_table(mapped_table);
                                mapped_table = NULL;
                                continue;
                        }

                        /* Match table instance (for SSDT/UEFI tables) */

                        if (current_instance != instance) {
                                osl_unmap_table(mapped_table);
                                mapped_table = NULL;
                                current_instance++;
                                continue;
                        }

                        break;
                }
        }

exit_find_table:

        if (!mapped_table) {
                return (AE_LIMIT);
        }

        if (table_length == 0) {
                status = AE_BAD_HEADER;
                goto exit;
        }

        /* Copy table to local buffer and return it */

        local_table = calloc(1, table_length);
        if (!local_table) {
                status = AE_NO_MEMORY;
                goto exit;
        }

        memcpy(local_table, mapped_table, table_length);
        *address = table_address;
        *table = local_table;

exit:
        osl_unmap_table(mapped_table);
        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    osl_list_customized_tables
 *
 * PARAMETERS:  directory           - Directory that contains the tables
 *
 * RETURN:      Status; Table list is initialized if AE_OK.
 *
 * DESCRIPTION: Add ACPI tables to the table list from a directory.
 *
 *****************************************************************************/

static acpi_status osl_list_customized_tables(char *directory)
{
        void *table_dir;
        u32 instance;
        char temp_name[ACPI_NAMESEG_SIZE] ACPI_NONSTRING;
        char *filename;
        acpi_status status = AE_OK;

        /* Open the requested directory */

        table_dir = acpi_os_open_directory(directory, "*", REQUEST_FILE_ONLY);
        if (!table_dir) {
                return (osl_get_last_status(AE_NOT_FOUND));
        }

        /* Examine all entries in this directory */

        while ((filename = acpi_os_get_next_filename(table_dir))) {

                /* Extract table name and instance number */

                status =
                    osl_table_name_from_file(filename, temp_name, &instance);

                /* Ignore meaningless files */

                if (ACPI_FAILURE(status)) {
                        continue;
                }

                /* Add new info node to global table list */

                status = osl_add_table_to_list(temp_name, instance);
                if (ACPI_FAILURE(status)) {
                        break;
                }
        }

        acpi_os_close_directory(table_dir);
        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    osl_map_table
 *
 * PARAMETERS:  address             - Address of the table in memory
 *              signature           - Optional ACPI Signature for desired table.
 *                                    Null terminated 4-character string.
 *              table               - Where a pointer to the mapped table is
 *                                    returned
 *
 * RETURN:      Status; Mapped table is returned if AE_OK.
 *              AE_NOT_FOUND: A valid table was not found at the address
 *
 * DESCRIPTION: Map entire ACPI table into caller's address space.
 *
 *****************************************************************************/

static acpi_status
osl_map_table(acpi_size address,
              char *signature, struct acpi_table_header **table)
{
        struct acpi_table_header *mapped_table;
        u32 length;

        if (!address) {
                return (AE_BAD_ADDRESS);
        }

        /*
         * Map the header so we can get the table length.
         * Use sizeof (struct acpi_table_header) as:
         * 1. it is bigger than 24 to include RSDP->Length
         * 2. it is smaller than sizeof (struct acpi_table_rsdp)
         */
        mapped_table =
            acpi_os_map_memory(address, sizeof(struct acpi_table_header));
        if (!mapped_table) {
                fprintf(stderr, "Could not map table header at 0x%8.8X%8.8X\n",
                        ACPI_FORMAT_UINT64(address));
                return (osl_get_last_status(AE_BAD_ADDRESS));
        }

        /* If specified, signature must match */

        if (signature) {
                if (ACPI_VALIDATE_RSDP_SIG(signature)) {
                        if (!ACPI_VALIDATE_RSDP_SIG(mapped_table->signature)) {
                                acpi_os_unmap_memory(mapped_table,
                                                     sizeof(struct
                                                            acpi_table_header));
                                return (AE_BAD_SIGNATURE);
                        }
                } else
                    if (!ACPI_COMPARE_NAMESEG
                        (signature, mapped_table->signature)) {
                        acpi_os_unmap_memory(mapped_table,
                                             sizeof(struct acpi_table_header));
                        return (AE_BAD_SIGNATURE);
                }
        }

        /* Map the entire table */

        length = ap_get_table_length(mapped_table);
        acpi_os_unmap_memory(mapped_table, sizeof(struct acpi_table_header));
        if (length == 0) {
                return (AE_BAD_HEADER);
        }

        mapped_table = acpi_os_map_memory(address, length);
        if (!mapped_table) {
                fprintf(stderr,
                        "Could not map table at 0x%8.8X%8.8X length %8.8X\n",
                        ACPI_FORMAT_UINT64(address), length);
                return (osl_get_last_status(AE_INVALID_TABLE_LENGTH));
        }

        (void)ap_is_valid_checksum(mapped_table);

        *table = mapped_table;
        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    osl_unmap_table
 *
 * PARAMETERS:  table               - A pointer to the mapped table
 *
 * RETURN:      None
 *
 * DESCRIPTION: Unmap entire ACPI table.
 *
 *****************************************************************************/

static void osl_unmap_table(struct acpi_table_header *table)
{
        if (table) {
                acpi_os_unmap_memory(table, ap_get_table_length(table));
        }
}

/******************************************************************************
 *
 * FUNCTION:    osl_table_name_from_file
 *
 * PARAMETERS:  filename            - File that contains the desired table
 *              signature           - Pointer to 4-character buffer to store
 *                                    extracted table signature.
 *              instance            - Pointer to integer to store extracted
 *                                    table instance number.
 *
 * RETURN:      Status; Table name is extracted if AE_OK.
 *
 * DESCRIPTION: Extract table signature and instance number from a table file
 *              name.
 *
 *****************************************************************************/

static acpi_status
osl_table_name_from_file(char *filename, char *signature, u32 *instance)
{

        /* Ignore meaningless files */

        if (strlen(filename) < ACPI_NAMESEG_SIZE) {
                return (AE_BAD_SIGNATURE);
        }

        /* Extract instance number */

        if (isdigit((int)filename[ACPI_NAMESEG_SIZE])) {
                sscanf(&filename[ACPI_NAMESEG_SIZE], "%u", instance);
        } else if (strlen(filename) != ACPI_NAMESEG_SIZE) {
                return (AE_BAD_SIGNATURE);
        } else {
                *instance = 0;
        }

        /* Extract signature */

        ACPI_COPY_NAMESEG(signature, filename);
        return (AE_OK);
}

/******************************************************************************
 *
 * FUNCTION:    osl_read_table_from_file
 *
 * PARAMETERS:  filename            - File that contains the desired table
 *              file_offset         - Offset of the table in file
 *              table               - Where a pointer to the table is returned
 *
 * RETURN:      Status; Table buffer is returned if AE_OK.
 *
 * DESCRIPTION: Read a ACPI table from a file.
 *
 *****************************************************************************/

static acpi_status
osl_read_table_from_file(char *filename,
                         acpi_size file_offset,
                         struct acpi_table_header **table)
{
        FILE *table_file;
        struct acpi_table_header header;
        struct acpi_table_header *local_table = NULL;
        u32 table_length;
        s32 count;
        acpi_status status = AE_OK;

        /* Open the file */

        table_file = fopen(filename, "rb");
        if (table_file == NULL) {
                fprintf(stderr, "Could not open table file: %s\n", filename);
                return (osl_get_last_status(AE_NOT_FOUND));
        }

        fseek(table_file, file_offset, SEEK_SET);

        /* Read the Table header to get the table length */

        count = fread(&header, 1, sizeof(struct acpi_table_header), table_file);
        if (count != sizeof(struct acpi_table_header)) {
                fprintf(stderr, "Could not read table header: %s\n", filename);
                status = AE_BAD_HEADER;
                goto exit;
        }

#ifdef ACPI_OBSOLETE_FUNCTIONS

        /* If signature is specified, it must match the table */

        if (signature) {
                if (ACPI_VALIDATE_RSDP_SIG(signature)) {
                        if (!ACPI_VALIDATE_RSDP_SIG(header.signature)) {
                                fprintf(stderr,
                                        "Incorrect RSDP signature: found %8.8s\n",
                                        header.signature);
                                status = AE_BAD_SIGNATURE;
                                goto exit;
                        }
                } else if (!ACPI_COMPARE_NAMESEG(signature, header.signature)) {
                        fprintf(stderr,
                                "Incorrect signature: Expecting %4.4s, found %4.4s\n",
                                signature, header.signature);
                        status = AE_BAD_SIGNATURE;
                        goto exit;
                }
        }
#endif

        table_length = ap_get_table_length(&header);
        if (table_length == 0) {
                status = AE_BAD_HEADER;
                goto exit;
        }

        /* Read the entire table into a local buffer */

        local_table = calloc(1, table_length);
        if (!local_table) {
                fprintf(stderr,
                        "%4.4s: Could not allocate buffer for table of length %X\n",
                        header.signature, table_length);
                status = AE_NO_MEMORY;
                goto exit;
        }

        fseek(table_file, file_offset, SEEK_SET);

        count = fread(local_table, 1, table_length, table_file);
        if (count != table_length) {
                fprintf(stderr, "%4.4s: Could not read table content\n",
                        header.signature);
                status = AE_INVALID_TABLE_LENGTH;
                goto exit;
        }

        /* Validate checksum */

        (void)ap_is_valid_checksum(local_table);

exit:
        fclose(table_file);
        *table = local_table;
        return (status);
}

/******************************************************************************
 *
 * FUNCTION:    osl_get_customized_table
 *
 * PARAMETERS:  pathname        - Directory to find Linux customized table
 *              signature       - ACPI Signature for desired table. Must be
 *                                a null terminated 4-character string.
 *              instance        - Multiple table support for SSDT/UEFI (0...n)
 *                                Must be 0 for other tables.
 *              table           - Where a pointer to the table is returned
 *              address         - Where the table physical address is returned
 *
 * RETURN:      Status; Table buffer is returned if AE_OK.
 *              AE_LIMIT: Instance is beyond valid limit
 *              AE_NOT_FOUND: A table with the signature was not found
 *
 * DESCRIPTION: Get an OS customized table.
 *
 *****************************************************************************/

static acpi_status
osl_get_customized_table(char *pathname,
                         char *signature,
                         u32 instance,
                         struct acpi_table_header **table,
                         acpi_physical_address *address)
{
        void *table_dir;
        u32 current_instance = 0;
        char temp_name[ACPI_NAMESEG_SIZE] ACPI_NONSTRING;
        char table_filename[PATH_MAX];
        char *filename;
        acpi_status status;

        /* Open the directory for customized tables */

        table_dir = acpi_os_open_directory(pathname, "*", REQUEST_FILE_ONLY);
        if (!table_dir) {
                return (osl_get_last_status(AE_NOT_FOUND));
        }

        /* Attempt to find the table in the directory */

        while ((filename = acpi_os_get_next_filename(table_dir))) {

                /* Ignore meaningless files */

                if (!ACPI_COMPARE_NAMESEG(filename, signature)) {
                        continue;
                }

                /* Extract table name and instance number */

                status =
                    osl_table_name_from_file(filename, temp_name,
                                             &current_instance);

                /* Ignore meaningless files */

                if (ACPI_FAILURE(status) || current_instance != instance) {
                        continue;
                }

                /* Create the table pathname */

                if (instance != 0) {
                        sprintf(table_filename, "%s/%4.4s%d", pathname,
                                temp_name, instance);
                } else {
                        sprintf(table_filename, "%s/%4.4s", pathname,
                                temp_name);
                }
                break;
        }

        acpi_os_close_directory(table_dir);

        if (!filename) {
                return (AE_LIMIT);
        }

        /* There is no physical address saved for customized tables, use zero */

        *address = 0;
        status = osl_read_table_from_file(table_filename, 0, table);

        return (status);
}