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/acpi.h> |
11 | #include <linux/init.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/string.h> |
14 | #include <linux/types.h> |
15 | #include <linux/fw_table.h> |
16 | |
17 | enum acpi_subtable_type { |
18 | ACPI_SUBTABLE_COMMON, |
19 | ACPI_SUBTABLE_HMAT, |
20 | ACPI_SUBTABLE_PRMT, |
21 | ACPI_SUBTABLE_CEDT, |
22 | CDAT_SUBTABLE, |
23 | }; |
24 | |
25 | struct acpi_subtable_entry { |
26 | union acpi_subtable_headers *hdr; |
27 | enum acpi_subtable_type type; |
28 | }; |
29 | |
30 | static unsigned long __init_or_fwtbl_lib |
31 | acpi_get_entry_type(struct acpi_subtable_entry *entry) |
32 | { |
33 | switch (entry->type) { |
34 | case ACPI_SUBTABLE_COMMON: |
35 | return entry->hdr->common.type; |
36 | case ACPI_SUBTABLE_HMAT: |
37 | return entry->hdr->hmat.type; |
38 | case ACPI_SUBTABLE_PRMT: |
39 | return 0; |
40 | case ACPI_SUBTABLE_CEDT: |
41 | return entry->hdr->cedt.type; |
42 | case CDAT_SUBTABLE: |
43 | return entry->hdr->cdat.type; |
44 | } |
45 | return 0; |
46 | } |
47 | |
48 | static unsigned long __init_or_fwtbl_lib |
49 | acpi_get_entry_length(struct acpi_subtable_entry *entry) |
50 | { |
51 | switch (entry->type) { |
52 | case ACPI_SUBTABLE_COMMON: |
53 | return entry->hdr->common.length; |
54 | case ACPI_SUBTABLE_HMAT: |
55 | return entry->hdr->hmat.length; |
56 | case ACPI_SUBTABLE_PRMT: |
57 | return entry->hdr->prmt.length; |
58 | case ACPI_SUBTABLE_CEDT: |
59 | return entry->hdr->cedt.length; |
60 | case CDAT_SUBTABLE: { |
61 | __le16 length = (__force __le16)entry->hdr->cdat.length; |
62 | |
63 | return le16_to_cpu(length); |
64 | } |
65 | } |
66 | return 0; |
67 | } |
68 | |
69 | static unsigned long __init_or_fwtbl_lib |
70 | (struct acpi_subtable_entry *entry) |
71 | { |
72 | switch (entry->type) { |
73 | case ACPI_SUBTABLE_COMMON: |
74 | return sizeof(entry->hdr->common); |
75 | case ACPI_SUBTABLE_HMAT: |
76 | return sizeof(entry->hdr->hmat); |
77 | case ACPI_SUBTABLE_PRMT: |
78 | return sizeof(entry->hdr->prmt); |
79 | case ACPI_SUBTABLE_CEDT: |
80 | return sizeof(entry->hdr->cedt); |
81 | case CDAT_SUBTABLE: |
82 | return sizeof(entry->hdr->cdat); |
83 | } |
84 | return 0; |
85 | } |
86 | |
87 | static enum acpi_subtable_type __init_or_fwtbl_lib |
88 | acpi_get_subtable_type(char *id) |
89 | { |
90 | if (strncmp(id, ACPI_SIG_HMAT, 4) == 0) |
91 | return ACPI_SUBTABLE_HMAT; |
92 | if (strncmp(id, ACPI_SIG_PRMT, 4) == 0) |
93 | return ACPI_SUBTABLE_PRMT; |
94 | if (strncmp(id, ACPI_SIG_CEDT, 4) == 0) |
95 | return ACPI_SUBTABLE_CEDT; |
96 | if (strncmp(id, ACPI_SIG_CDAT, 4) == 0) |
97 | return CDAT_SUBTABLE; |
98 | return ACPI_SUBTABLE_COMMON; |
99 | } |
100 | |
101 | static unsigned long __init_or_fwtbl_lib |
102 | acpi_table_get_length(enum acpi_subtable_type type, |
103 | union fw_table_header *) |
104 | { |
105 | if (type == CDAT_SUBTABLE) { |
106 | __le32 length = (__force __le32)header->cdat.length; |
107 | |
108 | return le32_to_cpu(length); |
109 | } |
110 | |
111 | return header->acpi.length; |
112 | } |
113 | |
114 | static __init_or_fwtbl_lib int call_handler(struct acpi_subtable_proc *proc, |
115 | union acpi_subtable_headers *hdr, |
116 | unsigned long end) |
117 | { |
118 | if (proc->handler) |
119 | return proc->handler(hdr, end); |
120 | if (proc->handler_arg) |
121 | return proc->handler_arg(hdr, proc->arg, end); |
122 | return -EINVAL; |
123 | } |
124 | |
125 | /** |
126 | * acpi_parse_entries_array - for each proc_num find a suitable subtable |
127 | * |
128 | * @id: table id (for debugging purposes) |
129 | * @table_size: size of the root table |
130 | * @max_length: maximum size of the table (ignore if 0) |
131 | * @table_header: where does the table start? |
132 | * @proc: array of acpi_subtable_proc struct containing entry id |
133 | * and associated handler with it |
134 | * @proc_num: how big proc is? |
135 | * @max_entries: how many entries can we process? |
136 | * |
137 | * For each proc_num find a subtable with proc->id and run proc->handler |
138 | * on it. Assumption is that there's only single handler for particular |
139 | * entry id. |
140 | * |
141 | * The table_size is not the size of the complete ACPI table (the length |
142 | * field in the header struct), but only the size of the root table; i.e., |
143 | * the offset from the very first byte of the complete ACPI table, to the |
144 | * first byte of the very first subtable. |
145 | * |
146 | * On success returns sum of all matching entries for all proc handlers. |
147 | * Otherwise, -ENODEV or -EINVAL is returned. |
148 | */ |
149 | int __init_or_fwtbl_lib |
150 | acpi_parse_entries_array(char *id, unsigned long table_size, |
151 | union fw_table_header *, |
152 | unsigned long max_length, |
153 | struct acpi_subtable_proc *proc, |
154 | int proc_num, unsigned int max_entries) |
155 | { |
156 | unsigned long table_len, table_end, subtable_len, entry_len; |
157 | struct acpi_subtable_entry entry; |
158 | enum acpi_subtable_type type; |
159 | int count = 0; |
160 | int i; |
161 | |
162 | type = acpi_get_subtable_type(id); |
163 | table_len = acpi_table_get_length(type, header: table_header); |
164 | if (max_length && max_length < table_len) |
165 | table_len = max_length; |
166 | table_end = (unsigned long)table_header + table_len; |
167 | |
168 | /* Parse all entries looking for a match. */ |
169 | |
170 | entry.type = type; |
171 | entry.hdr = (union acpi_subtable_headers *) |
172 | ((unsigned long)table_header + table_size); |
173 | subtable_len = acpi_get_subtable_header_length(entry: &entry); |
174 | |
175 | while (((unsigned long)entry.hdr) + subtable_len < table_end) { |
176 | for (i = 0; i < proc_num; i++) { |
177 | if (acpi_get_entry_type(entry: &entry) != proc[i].id) |
178 | continue; |
179 | |
180 | if (!max_entries || count < max_entries) |
181 | if (call_handler(proc: &proc[i], hdr: entry.hdr, end: table_end)) |
182 | return -EINVAL; |
183 | |
184 | proc[i].count++; |
185 | count++; |
186 | break; |
187 | } |
188 | |
189 | /* |
190 | * If entry->length is 0, break from this loop to avoid |
191 | * infinite loop. |
192 | */ |
193 | entry_len = acpi_get_entry_length(entry: &entry); |
194 | if (entry_len == 0) { |
195 | pr_err("[%4.4s:0x%02x] Invalid zero length\n" , id, proc->id); |
196 | return -EINVAL; |
197 | } |
198 | |
199 | entry.hdr = (union acpi_subtable_headers *) |
200 | ((unsigned long)entry.hdr + entry_len); |
201 | } |
202 | |
203 | if (max_entries && count > max_entries) { |
204 | pr_warn("[%4.4s:0x%02x] ignored %i entries of %i found\n" , |
205 | id, proc->id, count - max_entries, count); |
206 | } |
207 | |
208 | return count; |
209 | } |
210 | |
211 | int __init_or_fwtbl_lib |
212 | cdat_table_parse(enum acpi_cdat_type type, |
213 | acpi_tbl_entry_handler_arg handler_arg, |
214 | void *arg, |
215 | struct acpi_table_cdat *, |
216 | unsigned long length) |
217 | { |
218 | struct acpi_subtable_proc proc = { |
219 | .id = type, |
220 | .handler_arg = handler_arg, |
221 | .arg = arg, |
222 | }; |
223 | |
224 | if (!table_header) |
225 | return -EINVAL; |
226 | |
227 | return acpi_parse_entries_array(ACPI_SIG_CDAT, |
228 | table_size: sizeof(struct acpi_table_cdat), |
229 | table_header: (union fw_table_header *)table_header, |
230 | max_length: length, proc: &proc, proc_num: 1, max_entries: 0); |
231 | } |
232 | EXPORT_SYMBOL_FWTBL_LIB(cdat_table_parse); |
233 | |