1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * APEI Hardware Error Source Table support |
4 | * |
5 | * HEST describes error sources in detail; communicates operational |
6 | * parameters (i.e. severity levels, masking bits, and threshold |
7 | * values) to Linux as necessary. It also allows the BIOS to report |
8 | * non-standard error sources to Linux (for example, chipset-specific |
9 | * error registers). |
10 | * |
11 | * For more information about HEST, please refer to ACPI Specification |
12 | * version 4.0, section 17.3.2. |
13 | * |
14 | * Copyright 2009 Intel Corp. |
15 | * Author: Huang Ying <ying.huang@intel.com> |
16 | */ |
17 | |
18 | #include <linux/kernel.h> |
19 | #include <linux/module.h> |
20 | #include <linux/init.h> |
21 | #include <linux/acpi.h> |
22 | #include <linux/kdebug.h> |
23 | #include <linux/highmem.h> |
24 | #include <linux/io.h> |
25 | #include <linux/platform_device.h> |
26 | #include <acpi/apei.h> |
27 | #include <acpi/ghes.h> |
28 | |
29 | #include "apei-internal.h" |
30 | |
31 | #define HEST_PFX "HEST: " |
32 | |
33 | int hest_disable; |
34 | EXPORT_SYMBOL_GPL(hest_disable); |
35 | |
36 | /* HEST table parsing */ |
37 | |
38 | static struct acpi_table_hest *__read_mostly hest_tab; |
39 | |
40 | static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { |
41 | [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ |
42 | [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, |
43 | [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi), |
44 | [ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root), |
45 | [ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer), |
46 | [ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge), |
47 | [ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic), |
48 | [ACPI_HEST_TYPE_GENERIC_ERROR_V2] = sizeof(struct acpi_hest_generic_v2), |
49 | [ACPI_HEST_TYPE_IA32_DEFERRED_CHECK] = -1, |
50 | }; |
51 | |
52 | static inline bool is_generic_error(struct acpi_hest_header *hest_hdr) |
53 | { |
54 | return hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR || |
55 | hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR_V2; |
56 | } |
57 | |
58 | static int hest_esrc_len(struct acpi_hest_header *hest_hdr) |
59 | { |
60 | u16 hest_type = hest_hdr->type; |
61 | int len; |
62 | |
63 | if (hest_type >= ACPI_HEST_TYPE_RESERVED) |
64 | return 0; |
65 | |
66 | len = hest_esrc_len_tab[hest_type]; |
67 | |
68 | if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) { |
69 | struct acpi_hest_ia_corrected *cmc; |
70 | cmc = (struct acpi_hest_ia_corrected *)hest_hdr; |
71 | len = sizeof(*cmc) + cmc->num_hardware_banks * |
72 | sizeof(struct acpi_hest_ia_error_bank); |
73 | } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) { |
74 | struct acpi_hest_ia_machine_check *mc; |
75 | mc = (struct acpi_hest_ia_machine_check *)hest_hdr; |
76 | len = sizeof(*mc) + mc->num_hardware_banks * |
77 | sizeof(struct acpi_hest_ia_error_bank); |
78 | } else if (hest_type == ACPI_HEST_TYPE_IA32_DEFERRED_CHECK) { |
79 | struct acpi_hest_ia_deferred_check *mc; |
80 | mc = (struct acpi_hest_ia_deferred_check *)hest_hdr; |
81 | len = sizeof(*mc) + mc->num_hardware_banks * |
82 | sizeof(struct acpi_hest_ia_error_bank); |
83 | } |
84 | BUG_ON(len == -1); |
85 | |
86 | return len; |
87 | }; |
88 | |
89 | typedef int (*apei_hest_func_t)(struct acpi_hest_header *hest_hdr, void *data); |
90 | |
91 | static int apei_hest_parse(apei_hest_func_t func, void *data) |
92 | { |
93 | struct acpi_hest_header *hest_hdr; |
94 | int i, rc, len; |
95 | |
96 | if (hest_disable || !hest_tab) |
97 | return -EINVAL; |
98 | |
99 | hest_hdr = (struct acpi_hest_header *)(hest_tab + 1); |
100 | for (i = 0; i < hest_tab->error_source_count; i++) { |
101 | len = hest_esrc_len(hest_hdr); |
102 | if (!len) { |
103 | pr_warn(FW_WARN HEST_PFX |
104 | "Unknown or unused hardware error source " |
105 | "type: %d for hardware error source: %d.\n" , |
106 | hest_hdr->type, hest_hdr->source_id); |
107 | return -EINVAL; |
108 | } |
109 | if ((void *)hest_hdr + len > |
110 | (void *)hest_tab + hest_tab->header.length) { |
111 | pr_warn(FW_BUG HEST_PFX |
112 | "Table contents overflow for hardware error source: %d.\n" , |
113 | hest_hdr->source_id); |
114 | return -EINVAL; |
115 | } |
116 | |
117 | rc = func(hest_hdr, data); |
118 | if (rc) |
119 | return rc; |
120 | |
121 | hest_hdr = (void *)hest_hdr + len; |
122 | } |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | /* |
128 | * Check if firmware advertises firmware first mode. We need FF bit to be set |
129 | * along with a set of MC banks which work in FF mode. |
130 | */ |
131 | static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data) |
132 | { |
133 | if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) |
134 | return 0; |
135 | |
136 | if (!acpi_disable_cmcff) |
137 | return !arch_apei_enable_cmcff(hest_hdr, data); |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | struct ghes_arr { |
143 | struct platform_device **ghes_devs; |
144 | unsigned int count; |
145 | }; |
146 | |
147 | static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) |
148 | { |
149 | int *count = data; |
150 | |
151 | if (is_generic_error(hest_hdr)) |
152 | (*count)++; |
153 | return 0; |
154 | } |
155 | |
156 | static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) |
157 | { |
158 | struct platform_device *ghes_dev; |
159 | struct ghes_arr *ghes_arr = data; |
160 | int rc, i; |
161 | |
162 | if (!is_generic_error(hest_hdr)) |
163 | return 0; |
164 | |
165 | if (!((struct acpi_hest_generic *)hest_hdr)->enabled) |
166 | return 0; |
167 | for (i = 0; i < ghes_arr->count; i++) { |
168 | struct acpi_hest_header *hdr; |
169 | ghes_dev = ghes_arr->ghes_devs[i]; |
170 | hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data; |
171 | if (hdr->source_id == hest_hdr->source_id) { |
172 | pr_warn(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n" , |
173 | hdr->source_id); |
174 | return -EIO; |
175 | } |
176 | } |
177 | ghes_dev = platform_device_alloc(name: "GHES" , id: hest_hdr->source_id); |
178 | if (!ghes_dev) |
179 | return -ENOMEM; |
180 | |
181 | rc = platform_device_add_data(pdev: ghes_dev, data: &hest_hdr, size: sizeof(void *)); |
182 | if (rc) |
183 | goto err; |
184 | |
185 | rc = platform_device_add(pdev: ghes_dev); |
186 | if (rc) |
187 | goto err; |
188 | ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev; |
189 | |
190 | return 0; |
191 | err: |
192 | platform_device_put(pdev: ghes_dev); |
193 | return rc; |
194 | } |
195 | |
196 | static int __init hest_ghes_dev_register(unsigned int ghes_count) |
197 | { |
198 | int rc, i; |
199 | struct ghes_arr ghes_arr; |
200 | |
201 | ghes_arr.count = 0; |
202 | ghes_arr.ghes_devs = kmalloc_array(n: ghes_count, size: sizeof(void *), |
203 | GFP_KERNEL); |
204 | if (!ghes_arr.ghes_devs) |
205 | return -ENOMEM; |
206 | |
207 | rc = apei_hest_parse(func: hest_parse_ghes, data: &ghes_arr); |
208 | if (rc) |
209 | goto err; |
210 | |
211 | rc = ghes_estatus_pool_init(num_ghes: ghes_count); |
212 | if (rc) |
213 | goto err; |
214 | |
215 | out: |
216 | kfree(objp: ghes_arr.ghes_devs); |
217 | return rc; |
218 | err: |
219 | for (i = 0; i < ghes_arr.count; i++) |
220 | platform_device_unregister(ghes_arr.ghes_devs[i]); |
221 | goto out; |
222 | } |
223 | |
224 | static int __init setup_hest_disable(char *str) |
225 | { |
226 | hest_disable = HEST_DISABLED; |
227 | return 1; |
228 | } |
229 | |
230 | __setup("hest_disable" , setup_hest_disable); |
231 | |
232 | void __init acpi_hest_init(void) |
233 | { |
234 | acpi_status status; |
235 | int rc; |
236 | unsigned int ghes_count = 0; |
237 | |
238 | if (hest_disable) { |
239 | pr_info(HEST_PFX "Table parsing disabled.\n" ); |
240 | return; |
241 | } |
242 | |
243 | status = acpi_get_table(ACPI_SIG_HEST, instance: 0, |
244 | out_table: (struct acpi_table_header **)&hest_tab); |
245 | if (status == AE_NOT_FOUND) { |
246 | hest_disable = HEST_NOT_FOUND; |
247 | return; |
248 | } else if (ACPI_FAILURE(status)) { |
249 | const char *msg = acpi_format_exception(exception: status); |
250 | pr_err(HEST_PFX "Failed to get table, %s\n" , msg); |
251 | hest_disable = HEST_DISABLED; |
252 | return; |
253 | } |
254 | |
255 | rc = apei_hest_parse(func: hest_parse_cmc, NULL); |
256 | if (rc) |
257 | goto err; |
258 | |
259 | if (!ghes_disable) { |
260 | rc = apei_hest_parse(func: hest_parse_ghes_count, data: &ghes_count); |
261 | if (rc) |
262 | goto err; |
263 | |
264 | if (ghes_count) |
265 | rc = hest_ghes_dev_register(ghes_count); |
266 | if (rc) |
267 | goto err; |
268 | } |
269 | |
270 | pr_info(HEST_PFX "Table parsing has been initialized.\n" ); |
271 | return; |
272 | err: |
273 | hest_disable = HEST_DISABLED; |
274 | acpi_put_table(table: (struct acpi_table_header *)hest_tab); |
275 | } |
276 | |