Commit | Line | Data |
---|---|---|
a103f466 DJ |
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> | |
35732699 | 10 | #include <linux/acpi.h> |
a103f466 DJ |
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 | acpi_get_subtable_header_length(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 *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); | |
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) != proc[i].id) | |
154 | continue; | |
155 | if (!has_handler(&proc[i]) || | |
156 | (!errs && | |
157 | call_handler(&proc[i], entry.hdr, 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); | |
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 | } |