// SPDX-License-Identifier: GPL-2.0-or-later /* * fw_tables.c - Parsing support for ACPI and ACPI-like tables provided by * platform or device firmware * * Copyright (C) 2001 Paul Diefenbaugh * Copyright (C) 2023 Intel Corp. */ #include #include #include #include #include #include #include enum acpi_subtable_type { ACPI_SUBTABLE_COMMON, ACPI_SUBTABLE_HMAT, ACPI_SUBTABLE_PRMT, ACPI_SUBTABLE_CEDT, CDAT_SUBTABLE, }; struct acpi_subtable_entry { union acpi_subtable_headers *hdr; enum acpi_subtable_type type; }; static unsigned long __init_or_fwtbl_lib acpi_get_entry_type(struct acpi_subtable_entry *entry) { switch (entry->type) { case ACPI_SUBTABLE_COMMON: return entry->hdr->common.type; case ACPI_SUBTABLE_HMAT: return entry->hdr->hmat.type; case ACPI_SUBTABLE_PRMT: return 0; case ACPI_SUBTABLE_CEDT: return entry->hdr->cedt.type; case CDAT_SUBTABLE: return entry->hdr->cdat.type; } return 0; } static unsigned long __init_or_fwtbl_lib acpi_get_entry_length(struct acpi_subtable_entry *entry) { switch (entry->type) { case ACPI_SUBTABLE_COMMON: return entry->hdr->common.length; case ACPI_SUBTABLE_HMAT: return entry->hdr->hmat.length; case ACPI_SUBTABLE_PRMT: return entry->hdr->prmt.length; case ACPI_SUBTABLE_CEDT: return entry->hdr->cedt.length; case CDAT_SUBTABLE: { __le16 length = (__force __le16)entry->hdr->cdat.length; return le16_to_cpu(length); } } return 0; } static unsigned long __init_or_fwtbl_lib acpi_get_subtable_header_length(struct acpi_subtable_entry *entry) { switch (entry->type) { case ACPI_SUBTABLE_COMMON: return sizeof(entry->hdr->common); case ACPI_SUBTABLE_HMAT: return sizeof(entry->hdr->hmat); case ACPI_SUBTABLE_PRMT: return sizeof(entry->hdr->prmt); case ACPI_SUBTABLE_CEDT: return sizeof(entry->hdr->cedt); case CDAT_SUBTABLE: return sizeof(entry->hdr->cdat); } return 0; } static enum acpi_subtable_type __init_or_fwtbl_lib acpi_get_subtable_type(char *id) { if (strncmp(id, ACPI_SIG_HMAT, 4) == 0) return ACPI_SUBTABLE_HMAT; if (strncmp(id, ACPI_SIG_PRMT, 4) == 0) return ACPI_SUBTABLE_PRMT; if (strncmp(id, ACPI_SIG_CEDT, 4) == 0) return ACPI_SUBTABLE_CEDT; if (strncmp(id, ACPI_SIG_CDAT, 4) == 0) return CDAT_SUBTABLE; return ACPI_SUBTABLE_COMMON; } static unsigned long __init_or_fwtbl_lib acpi_table_get_length(enum acpi_subtable_type type, union fw_table_header *header) { if (type == CDAT_SUBTABLE) { __le32 length = (__force __le32)header->cdat.length; return le32_to_cpu(length); } return header->acpi.length; } static __init_or_fwtbl_lib int call_handler(struct acpi_subtable_proc *proc, union acpi_subtable_headers *hdr, unsigned long end) { if (proc->handler) return proc->handler(hdr, end); if (proc->handler_arg) return proc->handler_arg(hdr, proc->arg, end); return -EINVAL; } /** * acpi_parse_entries_array - for each proc_num find a suitable subtable * * @id: table id (for debugging purposes) * @table_size: size of the root table * @table_header: where does the table start? * @proc: array of acpi_subtable_proc struct containing entry id * and associated handler with it * @proc_num: how big proc is? * @max_entries: how many entries can we process? * * For each proc_num find a subtable with proc->id and run proc->handler * on it. Assumption is that there's only single handler for particular * entry id. * * The table_size is not the size of the complete ACPI table (the length * field in the header struct), but only the size of the root table; i.e., * the offset from the very first byte of the complete ACPI table, to the * first byte of the very first subtable. * * On success returns sum of all matching entries for all proc handlers. * Otherwise, -ENODEV or -EINVAL is returned. */ int __init_or_fwtbl_lib acpi_parse_entries_array(char *id, unsigned long table_size, union fw_table_header *table_header, struct acpi_subtable_proc *proc, int proc_num, unsigned int max_entries) { unsigned long table_end, subtable_len, entry_len; struct acpi_subtable_entry entry; enum acpi_subtable_type type; int count = 0; int i; type = acpi_get_subtable_type(id); table_end = (unsigned long)table_header + acpi_table_get_length(type, table_header); /* Parse all entries looking for a match. */ entry.type = type; entry.hdr = (union acpi_subtable_headers *) ((unsigned long)table_header + table_size); subtable_len = acpi_get_subtable_header_length(&entry); while (((unsigned long)entry.hdr) + subtable_len < table_end) { for (i = 0; i < proc_num; i++) { if (acpi_get_entry_type(&entry) != proc[i].id) continue; if (!max_entries || count < max_entries) if (call_handler(&proc[i], entry.hdr, table_end)) return -EINVAL; proc[i].count++; count++; break; } /* * If entry->length is 0, break from this loop to avoid * infinite loop. */ entry_len = acpi_get_entry_length(&entry); if (entry_len == 0) { pr_err("[%4.4s:0x%02x] Invalid zero length\n", id, proc->id); return -EINVAL; } entry.hdr = (union acpi_subtable_headers *) ((unsigned long)entry.hdr + entry_len); } if (max_entries && count > max_entries) { pr_warn("[%4.4s:0x%02x] ignored %i entries of %i found\n", id, proc->id, count - max_entries, count); } return count; } int __init_or_fwtbl_lib cdat_table_parse(enum acpi_cdat_type type, acpi_tbl_entry_handler_arg handler_arg, void *arg, struct acpi_table_cdat *table_header) { struct acpi_subtable_proc proc = { .id = type, .handler_arg = handler_arg, .arg = arg, }; if (!table_header) return -EINVAL; return acpi_parse_entries_array(ACPI_SIG_CDAT, sizeof(struct acpi_table_cdat), (union fw_table_header *)table_header, &proc, 1, 0); } EXPORT_SYMBOL_FWTBL_LIB(cdat_table_parse);