// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/******************************************************************************
 *
 * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables
 *
 * Copyright (C) 2000 - 2023, 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];

} 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];
	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];
	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);
}
