Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
9dc96664 HY |
2 | /* |
3 | * APEI Hardware Error Souce 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> | |
9dc96664 HY |
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> | |
7ad6e943 | 25 | #include <linux/platform_device.h> |
9dc96664 | 26 | #include <acpi/apei.h> |
e147133a | 27 | #include <acpi/ghes.h> |
9dc96664 HY |
28 | |
29 | #include "apei-internal.h" | |
30 | ||
31 | #define HEST_PFX "HEST: " | |
32 | ||
e931d0da | 33 | int hest_disable; |
9dc96664 HY |
34 | EXPORT_SYMBOL_GPL(hest_disable); |
35 | ||
36 | /* HEST table parsing */ | |
37 | ||
bec4f22a | 38 | static struct acpi_table_hest *__read_mostly hest_tab; |
9dc96664 | 39 | |
bec4f22a | 40 | static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { |
9dc96664 HY |
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), | |
42aa5604 | 48 | [ACPI_HEST_TYPE_GENERIC_ERROR_V2] = sizeof(struct acpi_hest_generic_v2), |
f3355298 | 49 | [ACPI_HEST_TYPE_IA32_DEFERRED_CHECK] = -1, |
9dc96664 HY |
50 | }; |
51 | ||
52 | static int hest_esrc_len(struct acpi_hest_header *hest_hdr) | |
53 | { | |
54 | u16 hest_type = hest_hdr->type; | |
55 | int len; | |
56 | ||
57 | if (hest_type >= ACPI_HEST_TYPE_RESERVED) | |
58 | return 0; | |
59 | ||
60 | len = hest_esrc_len_tab[hest_type]; | |
61 | ||
62 | if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) { | |
63 | struct acpi_hest_ia_corrected *cmc; | |
64 | cmc = (struct acpi_hest_ia_corrected *)hest_hdr; | |
65 | len = sizeof(*cmc) + cmc->num_hardware_banks * | |
66 | sizeof(struct acpi_hest_ia_error_bank); | |
67 | } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) { | |
68 | struct acpi_hest_ia_machine_check *mc; | |
69 | mc = (struct acpi_hest_ia_machine_check *)hest_hdr; | |
70 | len = sizeof(*mc) + mc->num_hardware_banks * | |
71 | sizeof(struct acpi_hest_ia_error_bank); | |
f3355298 YG |
72 | } else if (hest_type == ACPI_HEST_TYPE_IA32_DEFERRED_CHECK) { |
73 | struct acpi_hest_ia_deferred_check *mc; | |
74 | mc = (struct acpi_hest_ia_deferred_check *)hest_hdr; | |
75 | len = sizeof(*mc) + mc->num_hardware_banks * | |
76 | sizeof(struct acpi_hest_ia_error_bank); | |
9dc96664 HY |
77 | } |
78 | BUG_ON(len == -1); | |
79 | ||
80 | return len; | |
81 | }; | |
82 | ||
83 | int apei_hest_parse(apei_hest_func_t func, void *data) | |
84 | { | |
85 | struct acpi_hest_header *hest_hdr; | |
86 | int i, rc, len; | |
87 | ||
a84363d6 | 88 | if (hest_disable || !hest_tab) |
9dc96664 HY |
89 | return -EINVAL; |
90 | ||
91 | hest_hdr = (struct acpi_hest_header *)(hest_tab + 1); | |
92 | for (i = 0; i < hest_tab->error_source_count; i++) { | |
93 | len = hest_esrc_len(hest_hdr); | |
94 | if (!len) { | |
95 | pr_warning(FW_WARN HEST_PFX | |
96 | "Unknown or unused hardware error source " | |
97 | "type: %d for hardware error source: %d.\n", | |
98 | hest_hdr->type, hest_hdr->source_id); | |
99 | return -EINVAL; | |
100 | } | |
101 | if ((void *)hest_hdr + len > | |
102 | (void *)hest_tab + hest_tab->header.length) { | |
103 | pr_warning(FW_BUG HEST_PFX | |
104 | "Table contents overflow for hardware error source: %d.\n", | |
105 | hest_hdr->source_id); | |
106 | return -EINVAL; | |
107 | } | |
108 | ||
109 | rc = func(hest_hdr, data); | |
110 | if (rc) | |
111 | return rc; | |
112 | ||
113 | hest_hdr = (void *)hest_hdr + len; | |
114 | } | |
115 | ||
116 | return 0; | |
117 | } | |
118 | EXPORT_SYMBOL_GPL(apei_hest_parse); | |
119 | ||
c3d1fb56 NR |
120 | /* |
121 | * Check if firmware advertises firmware first mode. We need FF bit to be set | |
122 | * along with a set of MC banks which work in FF mode. | |
123 | */ | |
124 | static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data) | |
125 | { | |
9f9a35a7 TN |
126 | if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) |
127 | return 0; | |
128 | ||
129 | if (!acpi_disable_cmcff) | |
130 | return !arch_apei_enable_cmcff(hest_hdr, data); | |
131 | ||
132 | return 0; | |
c3d1fb56 NR |
133 | } |
134 | ||
7ad6e943 HY |
135 | struct ghes_arr { |
136 | struct platform_device **ghes_devs; | |
137 | unsigned int count; | |
138 | }; | |
139 | ||
bec4f22a | 140 | static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) |
7ad6e943 HY |
141 | { |
142 | int *count = data; | |
143 | ||
42aa5604 TB |
144 | if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR || |
145 | hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR_V2) | |
7ad6e943 HY |
146 | (*count)++; |
147 | return 0; | |
148 | } | |
149 | ||
bec4f22a | 150 | static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) |
7ad6e943 | 151 | { |
7ad6e943 HY |
152 | struct platform_device *ghes_dev; |
153 | struct ghes_arr *ghes_arr = data; | |
4d2b2956 | 154 | int rc, i; |
7ad6e943 | 155 | |
42aa5604 TB |
156 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR && |
157 | hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR_V2) | |
7ad6e943 | 158 | return 0; |
1dd6b20e JD |
159 | |
160 | if (!((struct acpi_hest_generic *)hest_hdr)->enabled) | |
7ad6e943 | 161 | return 0; |
4d2b2956 HY |
162 | for (i = 0; i < ghes_arr->count; i++) { |
163 | struct acpi_hest_header *hdr; | |
164 | ghes_dev = ghes_arr->ghes_devs[i]; | |
165 | hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data; | |
166 | if (hdr->source_id == hest_hdr->source_id) { | |
167 | pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n", | |
168 | hdr->source_id); | |
169 | return -EIO; | |
170 | } | |
171 | } | |
7ad6e943 HY |
172 | ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id); |
173 | if (!ghes_dev) | |
174 | return -ENOMEM; | |
1dd6b20e JD |
175 | |
176 | rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *)); | |
177 | if (rc) | |
178 | goto err; | |
179 | ||
7ad6e943 HY |
180 | rc = platform_device_add(ghes_dev); |
181 | if (rc) | |
182 | goto err; | |
183 | ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev; | |
184 | ||
185 | return 0; | |
186 | err: | |
187 | platform_device_put(ghes_dev); | |
188 | return rc; | |
189 | } | |
190 | ||
bec4f22a | 191 | static int __init hest_ghes_dev_register(unsigned int ghes_count) |
7ad6e943 HY |
192 | { |
193 | int rc, i; | |
194 | struct ghes_arr ghes_arr; | |
195 | ||
196 | ghes_arr.count = 0; | |
6da2ec56 KC |
197 | ghes_arr.ghes_devs = kmalloc_array(ghes_count, sizeof(void *), |
198 | GFP_KERNEL); | |
7ad6e943 HY |
199 | if (!ghes_arr.ghes_devs) |
200 | return -ENOMEM; | |
201 | ||
202 | rc = apei_hest_parse(hest_parse_ghes, &ghes_arr); | |
203 | if (rc) | |
204 | goto err; | |
e147133a | 205 | |
fb7be08f | 206 | rc = ghes_estatus_pool_init(ghes_count); |
e147133a JM |
207 | if (rc) |
208 | goto err; | |
209 | ||
7ad6e943 HY |
210 | out: |
211 | kfree(ghes_arr.ghes_devs); | |
212 | return rc; | |
213 | err: | |
214 | for (i = 0; i < ghes_arr.count; i++) | |
215 | platform_device_unregister(ghes_arr.ghes_devs[i]); | |
216 | goto out; | |
217 | } | |
218 | ||
9dc96664 HY |
219 | static int __init setup_hest_disable(char *str) |
220 | { | |
e931d0da | 221 | hest_disable = HEST_DISABLED; |
9dc96664 HY |
222 | return 0; |
223 | } | |
224 | ||
225 | __setup("hest_disable", setup_hest_disable); | |
226 | ||
415e12b2 | 227 | void __init acpi_hest_init(void) |
9dc96664 HY |
228 | { |
229 | acpi_status status; | |
230 | int rc = -ENODEV; | |
7ad6e943 | 231 | unsigned int ghes_count = 0; |
9dc96664 | 232 | |
9dc96664 | 233 | if (hest_disable) { |
415e12b2 RW |
234 | pr_info(HEST_PFX "Table parsing disabled.\n"); |
235 | return; | |
9dc96664 HY |
236 | } |
237 | ||
238 | status = acpi_get_table(ACPI_SIG_HEST, 0, | |
239 | (struct acpi_table_header **)&hest_tab); | |
e931d0da PA |
240 | if (status == AE_NOT_FOUND) { |
241 | hest_disable = HEST_NOT_FOUND; | |
242 | return; | |
243 | } else if (ACPI_FAILURE(status)) { | |
9dc96664 HY |
244 | const char *msg = acpi_format_exception(status); |
245 | pr_err(HEST_PFX "Failed to get table, %s\n", msg); | |
246 | rc = -EINVAL; | |
247 | goto err; | |
248 | } | |
249 | ||
9f9a35a7 TN |
250 | rc = apei_hest_parse(hest_parse_cmc, NULL); |
251 | if (rc) | |
252 | goto err; | |
c3d1fb56 | 253 | |
b6a95016 HY |
254 | if (!ghes_disable) { |
255 | rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count); | |
256 | if (rc) | |
257 | goto err; | |
e147133a JM |
258 | |
259 | if (ghes_count) | |
260 | rc = hest_ghes_dev_register(ghes_count); | |
b6a95016 HY |
261 | if (rc) |
262 | goto err; | |
415e12b2 | 263 | } |
9dc96664 | 264 | |
b6a95016 HY |
265 | pr_info(HEST_PFX "Table parsing has been initialized.\n"); |
266 | return; | |
9dc96664 | 267 | err: |
e931d0da | 268 | hest_disable = HEST_DISABLED; |
9dc96664 | 269 | } |