diff options
-rw-r--r-- | arch/x86/include/asm/mce.h | 8 | ||||
-rw-r--r-- | arch/x86/kernel/cpu/mcheck/Makefile | 2 | ||||
-rw-r--r-- | arch/x86/kernel/cpu/mcheck/mce-apei.c | 52 | ||||
-rw-r--r-- | drivers/acpi/apei/Kconfig | 14 | ||||
-rw-r--r-- | drivers/acpi/apei/Makefile | 1 | ||||
-rw-r--r-- | drivers/acpi/apei/ghes.c | 427 |
6 files changed, 504 insertions, 0 deletions
diff --git a/arch/x86/include/asm/mce.h b/arch/x86/include/asm/mce.h index 6c3fdd631ed3..f32a4301c4d4 100644 --- a/arch/x86/include/asm/mce.h +++ b/arch/x86/include/asm/mce.h | |||
@@ -225,5 +225,13 @@ extern void mcheck_intel_therm_init(void); | |||
225 | static inline void mcheck_intel_therm_init(void) { } | 225 | static inline void mcheck_intel_therm_init(void) { } |
226 | #endif | 226 | #endif |
227 | 227 | ||
228 | /* | ||
229 | * Used by APEI to report memory error via /dev/mcelog | ||
230 | */ | ||
231 | |||
232 | struct cper_sec_mem_err; | ||
233 | extern void apei_mce_report_mem_error(int corrected, | ||
234 | struct cper_sec_mem_err *mem_err); | ||
235 | |||
228 | #endif /* __KERNEL__ */ | 236 | #endif /* __KERNEL__ */ |
229 | #endif /* _ASM_X86_MCE_H */ | 237 | #endif /* _ASM_X86_MCE_H */ |
diff --git a/arch/x86/kernel/cpu/mcheck/Makefile b/arch/x86/kernel/cpu/mcheck/Makefile index 4ac6d48fe11b..bb34b03af252 100644 --- a/arch/x86/kernel/cpu/mcheck/Makefile +++ b/arch/x86/kernel/cpu/mcheck/Makefile | |||
@@ -7,3 +7,5 @@ obj-$(CONFIG_X86_MCE_THRESHOLD) += threshold.o | |||
7 | obj-$(CONFIG_X86_MCE_INJECT) += mce-inject.o | 7 | obj-$(CONFIG_X86_MCE_INJECT) += mce-inject.o |
8 | 8 | ||
9 | obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o | 9 | obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o |
10 | |||
11 | obj-$(CONFIG_ACPI_APEI) += mce-apei.o | ||
diff --git a/arch/x86/kernel/cpu/mcheck/mce-apei.c b/arch/x86/kernel/cpu/mcheck/mce-apei.c new file mode 100644 index 000000000000..4eccd1fadb14 --- /dev/null +++ b/arch/x86/kernel/cpu/mcheck/mce-apei.c | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * Bridge between MCE and APEI | ||
3 | * | ||
4 | * On some machine, corrected memory errors are reported via APEI | ||
5 | * generic hardware error source (GHES) instead of corrected Machine | ||
6 | * Check. These corrected memory errors can be reported to user space | ||
7 | * through /dev/mcelog via faking a corrected Machine Check, so that | ||
8 | * the error memory page can be offlined by /sbin/mcelog if the error | ||
9 | * count for one page is beyond the threshold. | ||
10 | * | ||
11 | * Copyright 2010 Intel Corp. | ||
12 | * Author: Huang Ying <ying.huang@intel.com> | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or | ||
15 | * modify it under the terms of the GNU General Public License version | ||
16 | * 2 as published by the Free Software Foundation. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | * | ||
23 | * You should have received a copy of the GNU General Public License | ||
24 | * along with this program; if not, write to the Free Software | ||
25 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
26 | */ | ||
27 | |||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/acpi.h> | ||
30 | #include <linux/cper.h> | ||
31 | #include <acpi/apei.h> | ||
32 | #include <asm/mce.h> | ||
33 | |||
34 | #include "mce-internal.h" | ||
35 | |||
36 | void apei_mce_report_mem_error(int corrected, struct cper_sec_mem_err *mem_err) | ||
37 | { | ||
38 | struct mce m; | ||
39 | |||
40 | /* Only corrected MC is reported */ | ||
41 | if (!corrected) | ||
42 | return; | ||
43 | |||
44 | mce_setup(&m); | ||
45 | m.bank = 1; | ||
46 | /* Fake a memory read corrected error with unknown channel */ | ||
47 | m.status = MCI_STATUS_VAL | MCI_STATUS_EN | MCI_STATUS_ADDRV | 0x9f; | ||
48 | m.addr = mem_err->physical_addr; | ||
49 | mce_log(&m); | ||
50 | mce_notify_irq(); | ||
51 | } | ||
52 | EXPORT_SYMBOL_GPL(apei_mce_report_mem_error); | ||
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index 5f0a41c2bc62..f8c668f27b5a 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig | |||
@@ -7,6 +7,20 @@ config ACPI_APEI | |||
7 | especially. In addition it supports error serialization and | 7 | especially. In addition it supports error serialization and |
8 | error injection. | 8 | error injection. |
9 | 9 | ||
10 | config ACPI_APEI_GHES | ||
11 | tristate "APEI Generic Hardware Error Source" | ||
12 | depends on ACPI_APEI && X86 | ||
13 | select ACPI_HED | ||
14 | help | ||
15 | Generic Hardware Error Source provides a way to report | ||
16 | platform hardware errors (such as that from chipset). It | ||
17 | works in so called "Firmware First" mode, that is, hardware | ||
18 | errors are reported to firmware firstly, then reported to | ||
19 | Linux by firmware. This way, some non-standard hardware | ||
20 | error registers or non-standard hardware link can be checked | ||
21 | by firmware to produce more valuable hardware error | ||
22 | information for Linux. | ||
23 | |||
10 | config ACPI_APEI_EINJ | 24 | config ACPI_APEI_EINJ |
11 | tristate "APEI Error INJection (EINJ)" | 25 | tristate "APEI Error INJection (EINJ)" |
12 | depends on ACPI_APEI && DEBUG_FS | 26 | depends on ACPI_APEI && DEBUG_FS |
diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile index fef963ec5362..41c61db4c51c 100644 --- a/drivers/acpi/apei/Makefile +++ b/drivers/acpi/apei/Makefile | |||
@@ -1,4 +1,5 @@ | |||
1 | obj-$(CONFIG_ACPI_APEI) += apei.o | 1 | obj-$(CONFIG_ACPI_APEI) += apei.o |
2 | obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o | ||
2 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o | 3 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o |
3 | 4 | ||
4 | apei-y := apei-base.o hest.o cper.o | 5 | apei-y := apei-base.o hest.o cper.o |
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c new file mode 100644 index 000000000000..fd0cc016a099 --- /dev/null +++ b/drivers/acpi/apei/ghes.c | |||
@@ -0,0 +1,427 @@ | |||
1 | /* | ||
2 | * APEI Generic Hardware Error Source support | ||
3 | * | ||
4 | * Generic Hardware Error Source provides a way to report platform | ||
5 | * hardware errors (such as that from chipset). It works in so called | ||
6 | * "Firmware First" mode, that is, hardware errors are reported to | ||
7 | * firmware firstly, then reported to Linux by firmware. This way, | ||
8 | * some non-standard hardware error registers or non-standard hardware | ||
9 | * link can be checked by firmware to produce more hardware error | ||
10 | * information for Linux. | ||
11 | * | ||
12 | * For more information about Generic Hardware Error Source, please | ||
13 | * refer to ACPI Specification version 4.0, section 17.3.2.6 | ||
14 | * | ||
15 | * Now, only SCI notification type and memory errors are | ||
16 | * supported. More notification type and hardware error type will be | ||
17 | * added later. | ||
18 | * | ||
19 | * Copyright 2010 Intel Corp. | ||
20 | * Author: Huang Ying <ying.huang@intel.com> | ||
21 | * | ||
22 | * This program is free software; you can redistribute it and/or | ||
23 | * modify it under the terms of the GNU General Public License version | ||
24 | * 2 as published by the Free Software Foundation; | ||
25 | * | ||
26 | * This program is distributed in the hope that it will be useful, | ||
27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
29 | * GNU General Public License for more details. | ||
30 | * | ||
31 | * You should have received a copy of the GNU General Public License | ||
32 | * along with this program; if not, write to the Free Software | ||
33 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
34 | */ | ||
35 | |||
36 | #include <linux/kernel.h> | ||
37 | #include <linux/module.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/acpi.h> | ||
40 | #include <linux/io.h> | ||
41 | #include <linux/interrupt.h> | ||
42 | #include <linux/cper.h> | ||
43 | #include <linux/kdebug.h> | ||
44 | #include <acpi/apei.h> | ||
45 | #include <acpi/atomicio.h> | ||
46 | #include <acpi/hed.h> | ||
47 | #include <asm/mce.h> | ||
48 | |||
49 | #include "apei-internal.h" | ||
50 | |||
51 | #define GHES_PFX "GHES: " | ||
52 | |||
53 | #define GHES_ESTATUS_MAX_SIZE 65536 | ||
54 | |||
55 | /* | ||
56 | * One struct ghes is created for each generic hardware error | ||
57 | * source. | ||
58 | * | ||
59 | * It provides the context for APEI hardware error timer/IRQ/SCI/NMI | ||
60 | * handler. Handler for one generic hardware error source is only | ||
61 | * triggered after the previous one is done. So handler can uses | ||
62 | * struct ghes without locking. | ||
63 | * | ||
64 | * estatus: memory buffer for error status block, allocated during | ||
65 | * HEST parsing. | ||
66 | */ | ||
67 | #define GHES_TO_CLEAR 0x0001 | ||
68 | |||
69 | struct ghes { | ||
70 | struct acpi_hest_generic *generic; | ||
71 | struct acpi_hest_generic_status *estatus; | ||
72 | struct list_head list; | ||
73 | u64 buffer_paddr; | ||
74 | unsigned long flags; | ||
75 | }; | ||
76 | |||
77 | /* | ||
78 | * Error source lists, one list for each notification method. The | ||
79 | * members in lists are struct ghes. | ||
80 | * | ||
81 | * The list members are only added in HEST parsing and deleted during | ||
82 | * module_exit, that is, single-threaded. So no lock is needed for | ||
83 | * that. | ||
84 | * | ||
85 | * But the mutual exclusion is needed between members adding/deleting | ||
86 | * and timer/IRQ/SCI/NMI handler, which may traverse the list. RCU is | ||
87 | * used for that. | ||
88 | */ | ||
89 | static LIST_HEAD(ghes_sci); | ||
90 | |||
91 | static struct ghes *ghes_new(struct acpi_hest_generic *generic) | ||
92 | { | ||
93 | struct ghes *ghes; | ||
94 | unsigned int error_block_length; | ||
95 | int rc; | ||
96 | |||
97 | ghes = kzalloc(sizeof(*ghes), GFP_KERNEL); | ||
98 | if (!ghes) | ||
99 | return ERR_PTR(-ENOMEM); | ||
100 | ghes->generic = generic; | ||
101 | INIT_LIST_HEAD(&ghes->list); | ||
102 | rc = acpi_pre_map_gar(&generic->error_status_address); | ||
103 | if (rc) | ||
104 | goto err_free; | ||
105 | error_block_length = generic->error_block_length; | ||
106 | if (error_block_length > GHES_ESTATUS_MAX_SIZE) { | ||
107 | pr_warning(FW_WARN GHES_PFX | ||
108 | "Error status block length is too long: %u for " | ||
109 | "generic hardware error source: %d.\n", | ||
110 | error_block_length, generic->header.source_id); | ||
111 | error_block_length = GHES_ESTATUS_MAX_SIZE; | ||
112 | } | ||
113 | ghes->estatus = kmalloc(error_block_length, GFP_KERNEL); | ||
114 | if (!ghes->estatus) { | ||
115 | rc = -ENOMEM; | ||
116 | goto err_unmap; | ||
117 | } | ||
118 | |||
119 | return ghes; | ||
120 | |||
121 | err_unmap: | ||
122 | acpi_post_unmap_gar(&generic->error_status_address); | ||
123 | err_free: | ||
124 | kfree(ghes); | ||
125 | return ERR_PTR(rc); | ||
126 | } | ||
127 | |||
128 | static void ghes_fini(struct ghes *ghes) | ||
129 | { | ||
130 | kfree(ghes->estatus); | ||
131 | acpi_post_unmap_gar(&ghes->generic->error_status_address); | ||
132 | } | ||
133 | |||
134 | enum { | ||
135 | GHES_SER_NO = 0x0, | ||
136 | GHES_SER_CORRECTED = 0x1, | ||
137 | GHES_SER_RECOVERABLE = 0x2, | ||
138 | GHES_SER_PANIC = 0x3, | ||
139 | }; | ||
140 | |||
141 | static inline int ghes_severity(int severity) | ||
142 | { | ||
143 | switch (severity) { | ||
144 | case CPER_SER_INFORMATIONAL: | ||
145 | return GHES_SER_NO; | ||
146 | case CPER_SER_CORRECTED: | ||
147 | return GHES_SER_CORRECTED; | ||
148 | case CPER_SER_RECOVERABLE: | ||
149 | return GHES_SER_RECOVERABLE; | ||
150 | case CPER_SER_FATAL: | ||
151 | return GHES_SER_PANIC; | ||
152 | default: | ||
153 | /* Unkown, go panic */ | ||
154 | return GHES_SER_PANIC; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | /* SCI handler run in work queue, so ioremap can be used here */ | ||
159 | static int ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, | ||
160 | int from_phys) | ||
161 | { | ||
162 | void *vaddr; | ||
163 | |||
164 | vaddr = ioremap_cache(paddr, len); | ||
165 | if (!vaddr) | ||
166 | return -ENOMEM; | ||
167 | if (from_phys) | ||
168 | memcpy(buffer, vaddr, len); | ||
169 | else | ||
170 | memcpy(vaddr, buffer, len); | ||
171 | iounmap(vaddr); | ||
172 | |||
173 | return 0; | ||
174 | } | ||
175 | |||
176 | static int ghes_read_estatus(struct ghes *ghes, int silent) | ||
177 | { | ||
178 | struct acpi_hest_generic *g = ghes->generic; | ||
179 | u64 buf_paddr; | ||
180 | u32 len; | ||
181 | int rc; | ||
182 | |||
183 | rc = acpi_atomic_read(&buf_paddr, &g->error_status_address); | ||
184 | if (rc) { | ||
185 | if (!silent && printk_ratelimit()) | ||
186 | pr_warning(FW_WARN GHES_PFX | ||
187 | "Failed to read error status block address for hardware error source: %d.\n", | ||
188 | g->header.source_id); | ||
189 | return -EIO; | ||
190 | } | ||
191 | if (!buf_paddr) | ||
192 | return -ENOENT; | ||
193 | |||
194 | rc = ghes_copy_tofrom_phys(ghes->estatus, buf_paddr, | ||
195 | sizeof(*ghes->estatus), 1); | ||
196 | if (rc) | ||
197 | return rc; | ||
198 | if (!ghes->estatus->block_status) | ||
199 | return -ENOENT; | ||
200 | |||
201 | ghes->buffer_paddr = buf_paddr; | ||
202 | ghes->flags |= GHES_TO_CLEAR; | ||
203 | |||
204 | rc = -EIO; | ||
205 | len = apei_estatus_len(ghes->estatus); | ||
206 | if (len < sizeof(*ghes->estatus)) | ||
207 | goto err_read_block; | ||
208 | if (len > ghes->generic->error_block_length) | ||
209 | goto err_read_block; | ||
210 | if (apei_estatus_check_header(ghes->estatus)) | ||
211 | goto err_read_block; | ||
212 | rc = ghes_copy_tofrom_phys(ghes->estatus + 1, | ||
213 | buf_paddr + sizeof(*ghes->estatus), | ||
214 | len - sizeof(*ghes->estatus), 1); | ||
215 | if (rc) | ||
216 | return rc; | ||
217 | if (apei_estatus_check(ghes->estatus)) | ||
218 | goto err_read_block; | ||
219 | rc = 0; | ||
220 | |||
221 | err_read_block: | ||
222 | if (rc && !silent) | ||
223 | pr_warning(FW_WARN GHES_PFX | ||
224 | "Failed to read error status block!\n"); | ||
225 | return rc; | ||
226 | } | ||
227 | |||
228 | static void ghes_clear_estatus(struct ghes *ghes) | ||
229 | { | ||
230 | ghes->estatus->block_status = 0; | ||
231 | if (!(ghes->flags & GHES_TO_CLEAR)) | ||
232 | return; | ||
233 | ghes_copy_tofrom_phys(ghes->estatus, ghes->buffer_paddr, | ||
234 | sizeof(ghes->estatus->block_status), 0); | ||
235 | ghes->flags &= ~GHES_TO_CLEAR; | ||
236 | } | ||
237 | |||
238 | static void ghes_do_proc(struct ghes *ghes) | ||
239 | { | ||
240 | int ser, processed = 0; | ||
241 | struct acpi_hest_generic_data *gdata; | ||
242 | |||
243 | ser = ghes_severity(ghes->estatus->error_severity); | ||
244 | apei_estatus_for_each_section(ghes->estatus, gdata) { | ||
245 | #ifdef CONFIG_X86_MCE | ||
246 | if (!uuid_le_cmp(*(uuid_le *)gdata->section_type, | ||
247 | CPER_SEC_PLATFORM_MEM)) { | ||
248 | apei_mce_report_mem_error( | ||
249 | ser == GHES_SER_CORRECTED, | ||
250 | (struct cper_sec_mem_err *)(gdata+1)); | ||
251 | processed = 1; | ||
252 | } | ||
253 | #endif | ||
254 | } | ||
255 | |||
256 | if (!processed && printk_ratelimit()) | ||
257 | pr_warning(GHES_PFX | ||
258 | "Unknown error record from generic hardware error source: %d\n", | ||
259 | ghes->generic->header.source_id); | ||
260 | } | ||
261 | |||
262 | static int ghes_proc(struct ghes *ghes) | ||
263 | { | ||
264 | int rc; | ||
265 | |||
266 | rc = ghes_read_estatus(ghes, 0); | ||
267 | if (rc) | ||
268 | goto out; | ||
269 | ghes_do_proc(ghes); | ||
270 | |||
271 | out: | ||
272 | ghes_clear_estatus(ghes); | ||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int ghes_notify_sci(struct notifier_block *this, | ||
277 | unsigned long event, void *data) | ||
278 | { | ||
279 | struct ghes *ghes; | ||
280 | int ret = NOTIFY_DONE; | ||
281 | |||
282 | rcu_read_lock(); | ||
283 | list_for_each_entry_rcu(ghes, &ghes_sci, list) { | ||
284 | if (!ghes_proc(ghes)) | ||
285 | ret = NOTIFY_OK; | ||
286 | } | ||
287 | rcu_read_unlock(); | ||
288 | |||
289 | return ret; | ||
290 | } | ||
291 | |||
292 | static struct notifier_block ghes_notifier_sci = { | ||
293 | .notifier_call = ghes_notify_sci, | ||
294 | }; | ||
295 | |||
296 | static int hest_ghes_parse(struct acpi_hest_header *hest_hdr, void *data) | ||
297 | { | ||
298 | struct acpi_hest_generic *generic; | ||
299 | struct ghes *ghes = NULL; | ||
300 | int rc = 0; | ||
301 | |||
302 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) | ||
303 | return 0; | ||
304 | |||
305 | generic = (struct acpi_hest_generic *)hest_hdr; | ||
306 | if (!generic->enabled) | ||
307 | return 0; | ||
308 | |||
309 | if (generic->error_block_length < | ||
310 | sizeof(struct acpi_hest_generic_status)) { | ||
311 | pr_warning(FW_BUG GHES_PFX | ||
312 | "Invalid error block length: %u for generic hardware error source: %d\n", | ||
313 | generic->error_block_length, | ||
314 | generic->header.source_id); | ||
315 | goto err; | ||
316 | } | ||
317 | if (generic->records_to_preallocate == 0) { | ||
318 | pr_warning(FW_BUG GHES_PFX | ||
319 | "Invalid records to preallocate: %u for generic hardware error source: %d\n", | ||
320 | generic->records_to_preallocate, | ||
321 | generic->header.source_id); | ||
322 | goto err; | ||
323 | } | ||
324 | ghes = ghes_new(generic); | ||
325 | if (IS_ERR(ghes)) { | ||
326 | rc = PTR_ERR(ghes); | ||
327 | ghes = NULL; | ||
328 | goto err; | ||
329 | } | ||
330 | switch (generic->notify.type) { | ||
331 | case ACPI_HEST_NOTIFY_POLLED: | ||
332 | pr_warning(GHES_PFX | ||
333 | "Generic hardware error source: %d notified via POLL is not supported!\n", | ||
334 | generic->header.source_id); | ||
335 | break; | ||
336 | case ACPI_HEST_NOTIFY_EXTERNAL: | ||
337 | case ACPI_HEST_NOTIFY_LOCAL: | ||
338 | pr_warning(GHES_PFX | ||
339 | "Generic hardware error source: %d notified via IRQ is not supported!\n", | ||
340 | generic->header.source_id); | ||
341 | break; | ||
342 | case ACPI_HEST_NOTIFY_SCI: | ||
343 | if (list_empty(&ghes_sci)) | ||
344 | register_acpi_hed_notifier(&ghes_notifier_sci); | ||
345 | list_add_rcu(&ghes->list, &ghes_sci); | ||
346 | break; | ||
347 | case ACPI_HEST_NOTIFY_NMI: | ||
348 | pr_warning(GHES_PFX | ||
349 | "Generic hardware error source: %d notified via NMI is not supported!\n", | ||
350 | generic->header.source_id); | ||
351 | break; | ||
352 | default: | ||
353 | pr_warning(FW_WARN GHES_PFX | ||
354 | "Unknown notification type: %u for generic hardware error source: %d\n", | ||
355 | generic->notify.type, generic->header.source_id); | ||
356 | break; | ||
357 | } | ||
358 | |||
359 | return 0; | ||
360 | err: | ||
361 | if (ghes) | ||
362 | ghes_fini(ghes); | ||
363 | return rc; | ||
364 | } | ||
365 | |||
366 | static void ghes_cleanup(void) | ||
367 | { | ||
368 | struct ghes *ghes, *nghes; | ||
369 | |||
370 | if (!list_empty(&ghes_sci)) | ||
371 | unregister_acpi_hed_notifier(&ghes_notifier_sci); | ||
372 | |||
373 | synchronize_rcu(); | ||
374 | |||
375 | list_for_each_entry_safe(ghes, nghes, &ghes_sci, list) { | ||
376 | list_del(&ghes->list); | ||
377 | ghes_fini(ghes); | ||
378 | kfree(ghes); | ||
379 | } | ||
380 | } | ||
381 | |||
382 | static int __init ghes_init(void) | ||
383 | { | ||
384 | int rc; | ||
385 | |||
386 | if (acpi_disabled) | ||
387 | return -ENODEV; | ||
388 | |||
389 | if (hest_disable) { | ||
390 | pr_info(GHES_PFX "HEST is not enabled!\n"); | ||
391 | return -EINVAL; | ||
392 | } | ||
393 | |||
394 | rc = apei_hest_parse(hest_ghes_parse, NULL); | ||
395 | if (rc) { | ||
396 | pr_err(GHES_PFX | ||
397 | "Error during parsing HEST generic hardware error sources.\n"); | ||
398 | goto err_cleanup; | ||
399 | } | ||
400 | |||
401 | if (list_empty(&ghes_sci)) { | ||
402 | pr_info(GHES_PFX | ||
403 | "No functional generic hardware error sources.\n"); | ||
404 | rc = -ENODEV; | ||
405 | goto err_cleanup; | ||
406 | } | ||
407 | |||
408 | pr_info(GHES_PFX | ||
409 | "Generic Hardware Error Source support is initialized.\n"); | ||
410 | |||
411 | return 0; | ||
412 | err_cleanup: | ||
413 | ghes_cleanup(); | ||
414 | return rc; | ||
415 | } | ||
416 | |||
417 | static void __exit ghes_exit(void) | ||
418 | { | ||
419 | ghes_cleanup(); | ||
420 | } | ||
421 | |||
422 | module_init(ghes_init); | ||
423 | module_exit(ghes_exit); | ||
424 | |||
425 | MODULE_AUTHOR("Huang Ying"); | ||
426 | MODULE_DESCRIPTION("APEI Generic Hardware Error Source support"); | ||
427 | MODULE_LICENSE("GPL"); | ||