1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * fw_tables.c - Parsing support for ACPI and ACPI-like tables provided by |
4 | * platform or device firmware |
5 | * |
6 | * Copyright (C) 2001 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> |
7 | * Copyright (C) 2023 Intel Corp. |
8 | */ |
9 | #include <linux/errno.h> |
10 | #include <linux/fw_table.h> |
11 | #include <linux/init.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/string.h> |
14 | #include <linux/types.h> |
15 | |
16 | enum acpi_subtable_type { |
17 | ACPI_SUBTABLE_COMMON, |
18 | ACPI_SUBTABLE_HMAT, |
19 | ACPI_SUBTABLE_PRMT, |
20 | ACPI_SUBTABLE_CEDT, |
21 | }; |
22 | |
23 | struct acpi_subtable_entry { |
24 | union acpi_subtable_headers *hdr; |
25 | enum acpi_subtable_type type; |
26 | }; |
27 | |
28 | static unsigned long __init_or_acpilib |
29 | acpi_get_entry_type(struct acpi_subtable_entry *entry) |
30 | { |
31 | switch (entry->type) { |
32 | case ACPI_SUBTABLE_COMMON: |
33 | return entry->hdr->common.type; |
34 | case ACPI_SUBTABLE_HMAT: |
35 | return entry->hdr->hmat.type; |
36 | case ACPI_SUBTABLE_PRMT: |
37 | return 0; |
38 | case ACPI_SUBTABLE_CEDT: |
39 | return entry->hdr->cedt.type; |
40 | } |
41 | return 0; |
42 | } |
43 | |
44 | static unsigned long __init_or_acpilib |
45 | acpi_get_entry_length(struct acpi_subtable_entry *entry) |
46 | { |
47 | switch (entry->type) { |
48 | case ACPI_SUBTABLE_COMMON: |
49 | return entry->hdr->common.length; |
50 | case ACPI_SUBTABLE_HMAT: |
51 | return entry->hdr->hmat.length; |
52 | case ACPI_SUBTABLE_PRMT: |
53 | return entry->hdr->prmt.length; |
54 | case ACPI_SUBTABLE_CEDT: |
55 | return entry->hdr->cedt.length; |
56 | } |
57 | return 0; |
58 | } |
59 | |
60 | static unsigned long __init_or_acpilib |
61 | (struct acpi_subtable_entry *entry) |
62 | { |
63 | switch (entry->type) { |
64 | case ACPI_SUBTABLE_COMMON: |
65 | return sizeof(entry->hdr->common); |
66 | case ACPI_SUBTABLE_HMAT: |
67 | return sizeof(entry->hdr->hmat); |
68 | case ACPI_SUBTABLE_PRMT: |
69 | return sizeof(entry->hdr->prmt); |
70 | case ACPI_SUBTABLE_CEDT: |
71 | return sizeof(entry->hdr->cedt); |
72 | } |
73 | return 0; |
74 | } |
75 | |
76 | static enum acpi_subtable_type __init_or_acpilib |
77 | acpi_get_subtable_type(char *id) |
78 | { |
79 | if (strncmp(id, ACPI_SIG_HMAT, 4) == 0) |
80 | return ACPI_SUBTABLE_HMAT; |
81 | if (strncmp(id, ACPI_SIG_PRMT, 4) == 0) |
82 | return ACPI_SUBTABLE_PRMT; |
83 | if (strncmp(id, ACPI_SIG_CEDT, 4) == 0) |
84 | return ACPI_SUBTABLE_CEDT; |
85 | return ACPI_SUBTABLE_COMMON; |
86 | } |
87 | |
88 | static __init_or_acpilib bool has_handler(struct acpi_subtable_proc *proc) |
89 | { |
90 | return proc->handler || proc->handler_arg; |
91 | } |
92 | |
93 | static __init_or_acpilib int call_handler(struct acpi_subtable_proc *proc, |
94 | union acpi_subtable_headers *hdr, |
95 | unsigned long end) |
96 | { |
97 | if (proc->handler) |
98 | return proc->handler(hdr, end); |
99 | if (proc->handler_arg) |
100 | return proc->handler_arg(hdr, proc->arg, end); |
101 | return -EINVAL; |
102 | } |
103 | |
104 | /** |
105 | * acpi_parse_entries_array - for each proc_num find a suitable subtable |
106 | * |
107 | * @id: table id (for debugging purposes) |
108 | * @table_size: size of the root table |
109 | * @table_header: where does the table start? |
110 | * @proc: array of acpi_subtable_proc struct containing entry id |
111 | * and associated handler with it |
112 | * @proc_num: how big proc is? |
113 | * @max_entries: how many entries can we process? |
114 | * |
115 | * For each proc_num find a subtable with proc->id and run proc->handler |
116 | * on it. Assumption is that there's only single handler for particular |
117 | * entry id. |
118 | * |
119 | * The table_size is not the size of the complete ACPI table (the length |
120 | * field in the header struct), but only the size of the root table; i.e., |
121 | * the offset from the very first byte of the complete ACPI table, to the |
122 | * first byte of the very first subtable. |
123 | * |
124 | * On success returns sum of all matching entries for all proc handlers. |
125 | * Otherwise, -ENODEV or -EINVAL is returned. |
126 | */ |
127 | int __init_or_acpilib |
128 | acpi_parse_entries_array(char *id, unsigned long table_size, |
129 | struct acpi_table_header *, |
130 | struct acpi_subtable_proc *proc, |
131 | int proc_num, unsigned int max_entries) |
132 | { |
133 | unsigned long table_end, subtable_len, entry_len; |
134 | struct acpi_subtable_entry entry; |
135 | int count = 0; |
136 | int errs = 0; |
137 | int i; |
138 | |
139 | table_end = (unsigned long)table_header + table_header->length; |
140 | |
141 | /* Parse all entries looking for a match. */ |
142 | |
143 | entry.type = acpi_get_subtable_type(id); |
144 | entry.hdr = (union acpi_subtable_headers *) |
145 | ((unsigned long)table_header + table_size); |
146 | subtable_len = acpi_get_subtable_header_length(entry: &entry); |
147 | |
148 | while (((unsigned long)entry.hdr) + subtable_len < table_end) { |
149 | if (max_entries && count >= max_entries) |
150 | break; |
151 | |
152 | for (i = 0; i < proc_num; i++) { |
153 | if (acpi_get_entry_type(entry: &entry) != proc[i].id) |
154 | continue; |
155 | if (!has_handler(proc: &proc[i]) || |
156 | (!errs && |
157 | call_handler(proc: &proc[i], hdr: entry.hdr, end: table_end))) { |
158 | errs++; |
159 | continue; |
160 | } |
161 | |
162 | proc[i].count++; |
163 | break; |
164 | } |
165 | if (i != proc_num) |
166 | count++; |
167 | |
168 | /* |
169 | * If entry->length is 0, break from this loop to avoid |
170 | * infinite loop. |
171 | */ |
172 | entry_len = acpi_get_entry_length(entry: &entry); |
173 | if (entry_len == 0) { |
174 | pr_err("[%4.4s:0x%02x] Invalid zero length\n" , id, proc->id); |
175 | return -EINVAL; |
176 | } |
177 | |
178 | entry.hdr = (union acpi_subtable_headers *) |
179 | ((unsigned long)entry.hdr + entry_len); |
180 | } |
181 | |
182 | if (max_entries && count > max_entries) { |
183 | pr_warn("[%4.4s:0x%02x] found the maximum %i entries\n" , |
184 | id, proc->id, count); |
185 | } |
186 | |
187 | return errs ? -EINVAL : count; |
188 | } |
189 | |