Commit | Line | Data |
---|---|---|
9dc96664 HY |
1 | /* |
2 | * APEI Hardware Error Souce Table support | |
3 | * | |
4 | * HEST describes error sources in detail; communicates operational | |
5 | * parameters (i.e. severity levels, masking bits, and threshold | |
6 | * values) to Linux as necessary. It also allows the BIOS to report | |
7 | * non-standard error sources to Linux (for example, chipset-specific | |
8 | * error registers). | |
9 | * | |
10 | * For more information about HEST, please refer to ACPI Specification | |
11 | * version 4.0, section 17.3.2. | |
12 | * | |
13 | * Copyright 2009 Intel Corp. | |
14 | * Author: Huang Ying <ying.huang@intel.com> | |
15 | * | |
16 | * This program is free software; you can redistribute it and/or | |
17 | * modify it under the terms of the GNU General Public License version | |
18 | * 2 as published by the Free Software Foundation; | |
19 | * | |
20 | * This program is distributed in the hope that it will be useful, | |
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
23 | * GNU General Public License for more details. | |
24 | * | |
25 | * You should have received a copy of the GNU General Public License | |
26 | * along with this program; if not, write to the Free Software | |
27 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
28 | */ | |
29 | ||
30 | #include <linux/kernel.h> | |
31 | #include <linux/module.h> | |
32 | #include <linux/init.h> | |
33 | #include <linux/acpi.h> | |
34 | #include <linux/kdebug.h> | |
35 | #include <linux/highmem.h> | |
36 | #include <linux/io.h> | |
7ad6e943 | 37 | #include <linux/platform_device.h> |
9dc96664 | 38 | #include <acpi/apei.h> |
c3d1fb56 | 39 | #include <asm/mce.h> |
9dc96664 HY |
40 | |
41 | #include "apei-internal.h" | |
42 | ||
43 | #define HEST_PFX "HEST: " | |
44 | ||
90ab5ee9 | 45 | bool hest_disable; |
9dc96664 HY |
46 | EXPORT_SYMBOL_GPL(hest_disable); |
47 | ||
48 | /* HEST table parsing */ | |
49 | ||
bec4f22a | 50 | static struct acpi_table_hest *__read_mostly hest_tab; |
9dc96664 | 51 | |
bec4f22a | 52 | static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { |
9dc96664 HY |
53 | [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ |
54 | [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, | |
55 | [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi), | |
56 | [ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root), | |
57 | [ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer), | |
58 | [ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge), | |
59 | [ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic), | |
60 | }; | |
61 | ||
62 | static int hest_esrc_len(struct acpi_hest_header *hest_hdr) | |
63 | { | |
64 | u16 hest_type = hest_hdr->type; | |
65 | int len; | |
66 | ||
67 | if (hest_type >= ACPI_HEST_TYPE_RESERVED) | |
68 | return 0; | |
69 | ||
70 | len = hest_esrc_len_tab[hest_type]; | |
71 | ||
72 | if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) { | |
73 | struct acpi_hest_ia_corrected *cmc; | |
74 | cmc = (struct acpi_hest_ia_corrected *)hest_hdr; | |
75 | len = sizeof(*cmc) + cmc->num_hardware_banks * | |
76 | sizeof(struct acpi_hest_ia_error_bank); | |
77 | } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) { | |
78 | struct acpi_hest_ia_machine_check *mc; | |
79 | mc = (struct acpi_hest_ia_machine_check *)hest_hdr; | |
80 | len = sizeof(*mc) + mc->num_hardware_banks * | |
81 | sizeof(struct acpi_hest_ia_error_bank); | |
82 | } | |
83 | BUG_ON(len == -1); | |
84 | ||
85 | return len; | |
86 | }; | |
87 | ||
88 | int apei_hest_parse(apei_hest_func_t func, void *data) | |
89 | { | |
90 | struct acpi_hest_header *hest_hdr; | |
91 | int i, rc, len; | |
92 | ||
a84363d6 | 93 | if (hest_disable || !hest_tab) |
9dc96664 HY |
94 | return -EINVAL; |
95 | ||
96 | hest_hdr = (struct acpi_hest_header *)(hest_tab + 1); | |
97 | for (i = 0; i < hest_tab->error_source_count; i++) { | |
98 | len = hest_esrc_len(hest_hdr); | |
99 | if (!len) { | |
100 | pr_warning(FW_WARN HEST_PFX | |
101 | "Unknown or unused hardware error source " | |
102 | "type: %d for hardware error source: %d.\n", | |
103 | hest_hdr->type, hest_hdr->source_id); | |
104 | return -EINVAL; | |
105 | } | |
106 | if ((void *)hest_hdr + len > | |
107 | (void *)hest_tab + hest_tab->header.length) { | |
108 | pr_warning(FW_BUG HEST_PFX | |
109 | "Table contents overflow for hardware error source: %d.\n", | |
110 | hest_hdr->source_id); | |
111 | return -EINVAL; | |
112 | } | |
113 | ||
114 | rc = func(hest_hdr, data); | |
115 | if (rc) | |
116 | return rc; | |
117 | ||
118 | hest_hdr = (void *)hest_hdr + len; | |
119 | } | |
120 | ||
121 | return 0; | |
122 | } | |
123 | EXPORT_SYMBOL_GPL(apei_hest_parse); | |
124 | ||
c3d1fb56 NR |
125 | /* |
126 | * Check if firmware advertises firmware first mode. We need FF bit to be set | |
127 | * along with a set of MC banks which work in FF mode. | |
128 | */ | |
129 | static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data) | |
130 | { | |
7781544e | 131 | #ifdef CONFIG_X86_MCE |
c3d1fb56 NR |
132 | int i; |
133 | struct acpi_hest_ia_corrected *cmc; | |
134 | struct acpi_hest_ia_error_bank *mc_bank; | |
135 | ||
136 | if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) | |
137 | return 0; | |
138 | ||
139 | cmc = (struct acpi_hest_ia_corrected *)hest_hdr; | |
140 | if (!cmc->enabled) | |
141 | return 0; | |
142 | ||
143 | /* | |
144 | * We expect HEST to provide a list of MC banks that report errors | |
145 | * in firmware first mode. Otherwise, return non-zero value to | |
146 | * indicate that we are done parsing HEST. | |
147 | */ | |
148 | if (!(cmc->flags & ACPI_HEST_FIRMWARE_FIRST) || !cmc->num_hardware_banks) | |
149 | return 1; | |
150 | ||
151 | pr_info(HEST_PFX "Enabling Firmware First mode for corrected errors.\n"); | |
152 | ||
153 | mc_bank = (struct acpi_hest_ia_error_bank *)(cmc + 1); | |
154 | for (i = 0; i < cmc->num_hardware_banks; i++, mc_bank++) | |
155 | mce_disable_bank(mc_bank->bank_number); | |
7781544e | 156 | #endif |
c3d1fb56 NR |
157 | return 1; |
158 | } | |
159 | ||
7ad6e943 HY |
160 | struct ghes_arr { |
161 | struct platform_device **ghes_devs; | |
162 | unsigned int count; | |
163 | }; | |
164 | ||
bec4f22a | 165 | static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) |
7ad6e943 HY |
166 | { |
167 | int *count = data; | |
168 | ||
169 | if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR) | |
170 | (*count)++; | |
171 | return 0; | |
172 | } | |
173 | ||
bec4f22a | 174 | static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) |
7ad6e943 | 175 | { |
7ad6e943 HY |
176 | struct platform_device *ghes_dev; |
177 | struct ghes_arr *ghes_arr = data; | |
4d2b2956 | 178 | int rc, i; |
7ad6e943 HY |
179 | |
180 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) | |
181 | return 0; | |
1dd6b20e JD |
182 | |
183 | if (!((struct acpi_hest_generic *)hest_hdr)->enabled) | |
7ad6e943 | 184 | return 0; |
4d2b2956 HY |
185 | for (i = 0; i < ghes_arr->count; i++) { |
186 | struct acpi_hest_header *hdr; | |
187 | ghes_dev = ghes_arr->ghes_devs[i]; | |
188 | hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data; | |
189 | if (hdr->source_id == hest_hdr->source_id) { | |
190 | pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n", | |
191 | hdr->source_id); | |
192 | return -EIO; | |
193 | } | |
194 | } | |
7ad6e943 HY |
195 | ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id); |
196 | if (!ghes_dev) | |
197 | return -ENOMEM; | |
1dd6b20e JD |
198 | |
199 | rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *)); | |
200 | if (rc) | |
201 | goto err; | |
202 | ||
7ad6e943 HY |
203 | rc = platform_device_add(ghes_dev); |
204 | if (rc) | |
205 | goto err; | |
206 | ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev; | |
207 | ||
208 | return 0; | |
209 | err: | |
210 | platform_device_put(ghes_dev); | |
211 | return rc; | |
212 | } | |
213 | ||
bec4f22a | 214 | static int __init hest_ghes_dev_register(unsigned int ghes_count) |
7ad6e943 HY |
215 | { |
216 | int rc, i; | |
217 | struct ghes_arr ghes_arr; | |
218 | ||
219 | ghes_arr.count = 0; | |
220 | ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL); | |
221 | if (!ghes_arr.ghes_devs) | |
222 | return -ENOMEM; | |
223 | ||
224 | rc = apei_hest_parse(hest_parse_ghes, &ghes_arr); | |
225 | if (rc) | |
226 | goto err; | |
227 | out: | |
228 | kfree(ghes_arr.ghes_devs); | |
229 | return rc; | |
230 | err: | |
231 | for (i = 0; i < ghes_arr.count; i++) | |
232 | platform_device_unregister(ghes_arr.ghes_devs[i]); | |
233 | goto out; | |
234 | } | |
235 | ||
9dc96664 HY |
236 | static int __init setup_hest_disable(char *str) |
237 | { | |
238 | hest_disable = 1; | |
239 | return 0; | |
240 | } | |
241 | ||
242 | __setup("hest_disable", setup_hest_disable); | |
243 | ||
415e12b2 | 244 | void __init acpi_hest_init(void) |
9dc96664 HY |
245 | { |
246 | acpi_status status; | |
247 | int rc = -ENODEV; | |
7ad6e943 | 248 | unsigned int ghes_count = 0; |
9dc96664 | 249 | |
9dc96664 | 250 | if (hest_disable) { |
415e12b2 RW |
251 | pr_info(HEST_PFX "Table parsing disabled.\n"); |
252 | return; | |
9dc96664 HY |
253 | } |
254 | ||
255 | status = acpi_get_table(ACPI_SIG_HEST, 0, | |
256 | (struct acpi_table_header **)&hest_tab); | |
ad686154 | 257 | if (status == AE_NOT_FOUND) |
9dc96664 | 258 | goto err; |
ad686154 | 259 | else if (ACPI_FAILURE(status)) { |
9dc96664 HY |
260 | const char *msg = acpi_format_exception(status); |
261 | pr_err(HEST_PFX "Failed to get table, %s\n", msg); | |
262 | rc = -EINVAL; | |
263 | goto err; | |
264 | } | |
265 | ||
9ad95879 NR |
266 | if (!acpi_disable_cmcff) |
267 | apei_hest_parse(hest_parse_cmc, NULL); | |
c3d1fb56 | 268 | |
b6a95016 HY |
269 | if (!ghes_disable) { |
270 | rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count); | |
271 | if (rc) | |
272 | goto err; | |
273 | rc = hest_ghes_dev_register(ghes_count); | |
274 | if (rc) | |
275 | goto err; | |
415e12b2 | 276 | } |
9dc96664 | 277 | |
b6a95016 HY |
278 | pr_info(HEST_PFX "Table parsing has been initialized.\n"); |
279 | return; | |
9dc96664 HY |
280 | err: |
281 | hest_disable = 1; | |
9dc96664 | 282 | } |