| // 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 <paul.s.diefenbaugh@intel.com> |
| * Copyright (C) 2023 Intel Corp. |
| */ |
| #include <linux/errno.h> |
| #include <linux/acpi.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/types.h> |
| #include <linux/fw_table.h> |
| |
| 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); |