diff options
author | Huang Ying <ying.huang@intel.com> | 2010-05-18 02:35:14 -0400 |
---|---|---|
committer | Len Brown <len.brown@intel.com> | 2010-05-19 22:35:29 -0400 |
commit | e40213450b53157967a1f83eda50e9a941c13a08 (patch) | |
tree | 4655490175a52a911e8a5cde44d5f221b4603d49 /drivers/acpi | |
parent | 9dc966641677795f4d6b0a9ba630d6a3a3e24a57 (diff) |
ACPI, APEI, EINJ support
EINJ provides a hardware error injection mechanism, this is useful for
debugging and testing of other APEI and RAS features.
Signed-off-by: Huang Ying <ying.huang@intel.com>
Signed-off-by: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/apei/Kconfig | 8 | ||||
-rw-r--r-- | drivers/acpi/apei/Makefile | 1 | ||||
-rw-r--r-- | drivers/acpi/apei/einj.c | 486 |
3 files changed, 495 insertions, 0 deletions
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index d61c851533c9..5f0a41c2bc62 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig | |||
@@ -6,3 +6,11 @@ config ACPI_APEI | |||
6 | to the operating system. This improves NMI handling | 6 | to the operating system. This improves NMI handling |
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 | |||
10 | config ACPI_APEI_EINJ | ||
11 | tristate "APEI Error INJection (EINJ)" | ||
12 | depends on ACPI_APEI && DEBUG_FS | ||
13 | help | ||
14 | EINJ provides a hardware error injection mechanism, it is | ||
15 | mainly used for debugging and testing the other parts of | ||
16 | APEI and some other RAS features. | ||
diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile index a8832929835f..fea86a9c3c2b 100644 --- a/drivers/acpi/apei/Makefile +++ b/drivers/acpi/apei/Makefile | |||
@@ -1,3 +1,4 @@ | |||
1 | obj-$(CONFIG_ACPI_APEI) += apei.o | 1 | obj-$(CONFIG_ACPI_APEI) += apei.o |
2 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o | ||
2 | 3 | ||
3 | apei-y := apei-base.o hest.o | 4 | apei-y := apei-base.o hest.o |
diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c new file mode 100644 index 000000000000..4ccebd8f930a --- /dev/null +++ b/drivers/acpi/apei/einj.c | |||
@@ -0,0 +1,486 @@ | |||
1 | /* | ||
2 | * APEI Error INJection support | ||
3 | * | ||
4 | * EINJ provides a hardware error injection mechanism, this is useful | ||
5 | * for debugging and testing of other APEI and RAS features. | ||
6 | * | ||
7 | * For more information about EINJ, please refer to ACPI Specification | ||
8 | * version 4.0, section 17.5. | ||
9 | * | ||
10 | * Copyright 2009 Intel Corp. | ||
11 | * Author: Huang Ying <ying.huang@intel.com> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License version | ||
15 | * 2 as published by the Free Software Foundation. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/io.h> | ||
31 | #include <linux/debugfs.h> | ||
32 | #include <linux/seq_file.h> | ||
33 | #include <linux/nmi.h> | ||
34 | #include <linux/delay.h> | ||
35 | #include <acpi/acpi.h> | ||
36 | |||
37 | #include "apei-internal.h" | ||
38 | |||
39 | #define EINJ_PFX "EINJ: " | ||
40 | |||
41 | #define SPIN_UNIT 100 /* 100ns */ | ||
42 | /* Firmware should respond within 1 miliseconds */ | ||
43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | ||
44 | |||
45 | #define EINJ_OP_BUSY 0x1 | ||
46 | #define EINJ_STATUS_SUCCESS 0x0 | ||
47 | #define EINJ_STATUS_FAIL 0x1 | ||
48 | #define EINJ_STATUS_INVAL 0x2 | ||
49 | |||
50 | #define EINJ_TAB_ENTRY(tab) \ | ||
51 | ((struct acpi_whea_header *)((char *)(tab) + \ | ||
52 | sizeof(struct acpi_table_einj))) | ||
53 | |||
54 | static struct acpi_table_einj *einj_tab; | ||
55 | |||
56 | static struct apei_resources einj_resources; | ||
57 | |||
58 | static struct apei_exec_ins_type einj_ins_type[] = { | ||
59 | [ACPI_EINJ_READ_REGISTER] = { | ||
60 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
61 | .run = apei_exec_read_register, | ||
62 | }, | ||
63 | [ACPI_EINJ_READ_REGISTER_VALUE] = { | ||
64 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
65 | .run = apei_exec_read_register_value, | ||
66 | }, | ||
67 | [ACPI_EINJ_WRITE_REGISTER] = { | ||
68 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
69 | .run = apei_exec_write_register, | ||
70 | }, | ||
71 | [ACPI_EINJ_WRITE_REGISTER_VALUE] = { | ||
72 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
73 | .run = apei_exec_write_register_value, | ||
74 | }, | ||
75 | [ACPI_EINJ_NOOP] = { | ||
76 | .flags = 0, | ||
77 | .run = apei_exec_noop, | ||
78 | }, | ||
79 | }; | ||
80 | |||
81 | /* | ||
82 | * Prevent EINJ interpreter to run simultaneously, because the | ||
83 | * corresponding firmware implementation may not work properly when | ||
84 | * invoked simultaneously. | ||
85 | */ | ||
86 | static DEFINE_MUTEX(einj_mutex); | ||
87 | |||
88 | static void einj_exec_ctx_init(struct apei_exec_context *ctx) | ||
89 | { | ||
90 | apei_exec_ctx_init(ctx, einj_ins_type, ARRAY_SIZE(einj_ins_type), | ||
91 | EINJ_TAB_ENTRY(einj_tab), einj_tab->entries); | ||
92 | } | ||
93 | |||
94 | static int __einj_get_available_error_type(u32 *type) | ||
95 | { | ||
96 | struct apei_exec_context ctx; | ||
97 | int rc; | ||
98 | |||
99 | einj_exec_ctx_init(&ctx); | ||
100 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_ERROR_TYPE); | ||
101 | if (rc) | ||
102 | return rc; | ||
103 | *type = apei_exec_ctx_get_output(&ctx); | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | /* Get error injection capabilities of the platform */ | ||
109 | static int einj_get_available_error_type(u32 *type) | ||
110 | { | ||
111 | int rc; | ||
112 | |||
113 | mutex_lock(&einj_mutex); | ||
114 | rc = __einj_get_available_error_type(type); | ||
115 | mutex_unlock(&einj_mutex); | ||
116 | |||
117 | return rc; | ||
118 | } | ||
119 | |||
120 | static int einj_timedout(u64 *t) | ||
121 | { | ||
122 | if ((s64)*t < SPIN_UNIT) { | ||
123 | pr_warning(FW_WARN EINJ_PFX | ||
124 | "Firmware does not respond in time\n"); | ||
125 | return 1; | ||
126 | } | ||
127 | *t -= SPIN_UNIT; | ||
128 | ndelay(SPIN_UNIT); | ||
129 | touch_nmi_watchdog(); | ||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | /* do sanity check to trigger table */ | ||
134 | static int einj_check_trigger_header(struct acpi_einj_trigger *trigger_tab) | ||
135 | { | ||
136 | if (trigger_tab->header_size != sizeof(struct acpi_einj_trigger)) | ||
137 | return -EINVAL; | ||
138 | if (trigger_tab->table_size > PAGE_SIZE || | ||
139 | trigger_tab->table_size <= trigger_tab->header_size) | ||
140 | return -EINVAL; | ||
141 | if (trigger_tab->entry_count != | ||
142 | (trigger_tab->table_size - trigger_tab->header_size) / | ||
143 | sizeof(struct acpi_einj_entry)) | ||
144 | return -EINVAL; | ||
145 | |||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | /* Execute instructions in trigger error action table */ | ||
150 | static int __einj_error_trigger(u64 trigger_paddr) | ||
151 | { | ||
152 | struct acpi_einj_trigger *trigger_tab = NULL; | ||
153 | struct apei_exec_context trigger_ctx; | ||
154 | struct apei_resources trigger_resources; | ||
155 | struct acpi_whea_header *trigger_entry; | ||
156 | struct resource *r; | ||
157 | u32 table_size; | ||
158 | int rc = -EIO; | ||
159 | |||
160 | r = request_mem_region(trigger_paddr, sizeof(*trigger_tab), | ||
161 | "APEI EINJ Trigger Table"); | ||
162 | if (!r) { | ||
163 | pr_err(EINJ_PFX | ||
164 | "Can not request iomem region <%016llx-%016llx> for Trigger table.\n", | ||
165 | (unsigned long long)trigger_paddr, | ||
166 | (unsigned long long)trigger_paddr+sizeof(*trigger_tab)); | ||
167 | goto out; | ||
168 | } | ||
169 | trigger_tab = ioremap_cache(trigger_paddr, sizeof(*trigger_tab)); | ||
170 | if (!trigger_tab) { | ||
171 | pr_err(EINJ_PFX "Failed to map trigger table!\n"); | ||
172 | goto out_rel_header; | ||
173 | } | ||
174 | rc = einj_check_trigger_header(trigger_tab); | ||
175 | if (rc) { | ||
176 | pr_warning(FW_BUG EINJ_PFX | ||
177 | "The trigger error action table is invalid\n"); | ||
178 | goto out_rel_header; | ||
179 | } | ||
180 | rc = -EIO; | ||
181 | table_size = trigger_tab->table_size; | ||
182 | r = request_mem_region(trigger_paddr + sizeof(*trigger_tab), | ||
183 | table_size - sizeof(*trigger_tab), | ||
184 | "APEI EINJ Trigger Table"); | ||
185 | if (!r) { | ||
186 | pr_err(EINJ_PFX | ||
187 | "Can not request iomem region <%016llx-%016llx> for Trigger Table Entry.\n", | ||
188 | (unsigned long long)trigger_paddr+sizeof(*trigger_tab), | ||
189 | (unsigned long long)trigger_paddr + table_size); | ||
190 | goto out_rel_header; | ||
191 | } | ||
192 | iounmap(trigger_tab); | ||
193 | trigger_tab = ioremap_cache(trigger_paddr, table_size); | ||
194 | if (!trigger_tab) { | ||
195 | pr_err(EINJ_PFX "Failed to map trigger table!\n"); | ||
196 | goto out_rel_entry; | ||
197 | } | ||
198 | trigger_entry = (struct acpi_whea_header *) | ||
199 | ((char *)trigger_tab + sizeof(struct acpi_einj_trigger)); | ||
200 | apei_resources_init(&trigger_resources); | ||
201 | apei_exec_ctx_init(&trigger_ctx, einj_ins_type, | ||
202 | ARRAY_SIZE(einj_ins_type), | ||
203 | trigger_entry, trigger_tab->entry_count); | ||
204 | rc = apei_exec_collect_resources(&trigger_ctx, &trigger_resources); | ||
205 | if (rc) | ||
206 | goto out_fini; | ||
207 | rc = apei_resources_sub(&trigger_resources, &einj_resources); | ||
208 | if (rc) | ||
209 | goto out_fini; | ||
210 | rc = apei_resources_request(&trigger_resources, "APEI EINJ Trigger"); | ||
211 | if (rc) | ||
212 | goto out_fini; | ||
213 | rc = apei_exec_pre_map_gars(&trigger_ctx); | ||
214 | if (rc) | ||
215 | goto out_release; | ||
216 | |||
217 | rc = apei_exec_run(&trigger_ctx, ACPI_EINJ_TRIGGER_ERROR); | ||
218 | |||
219 | apei_exec_post_unmap_gars(&trigger_ctx); | ||
220 | out_release: | ||
221 | apei_resources_release(&trigger_resources); | ||
222 | out_fini: | ||
223 | apei_resources_fini(&trigger_resources); | ||
224 | out_rel_entry: | ||
225 | release_mem_region(trigger_paddr + sizeof(*trigger_tab), | ||
226 | table_size - sizeof(*trigger_tab)); | ||
227 | out_rel_header: | ||
228 | release_mem_region(trigger_paddr, sizeof(*trigger_tab)); | ||
229 | out: | ||
230 | if (trigger_tab) | ||
231 | iounmap(trigger_tab); | ||
232 | |||
233 | return rc; | ||
234 | } | ||
235 | |||
236 | static int __einj_error_inject(u32 type) | ||
237 | { | ||
238 | struct apei_exec_context ctx; | ||
239 | u64 val, trigger_paddr, timeout = FIRMWARE_TIMEOUT; | ||
240 | int rc; | ||
241 | |||
242 | einj_exec_ctx_init(&ctx); | ||
243 | |||
244 | rc = apei_exec_run(&ctx, ACPI_EINJ_BEGIN_OPERATION); | ||
245 | if (rc) | ||
246 | return rc; | ||
247 | apei_exec_ctx_set_input(&ctx, type); | ||
248 | rc = apei_exec_run(&ctx, ACPI_EINJ_SET_ERROR_TYPE); | ||
249 | if (rc) | ||
250 | return rc; | ||
251 | rc = apei_exec_run(&ctx, ACPI_EINJ_EXECUTE_OPERATION); | ||
252 | if (rc) | ||
253 | return rc; | ||
254 | for (;;) { | ||
255 | rc = apei_exec_run(&ctx, ACPI_EINJ_CHECK_BUSY_STATUS); | ||
256 | if (rc) | ||
257 | return rc; | ||
258 | val = apei_exec_ctx_get_output(&ctx); | ||
259 | if (!(val & EINJ_OP_BUSY)) | ||
260 | break; | ||
261 | if (einj_timedout(&timeout)) | ||
262 | return -EIO; | ||
263 | } | ||
264 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_COMMAND_STATUS); | ||
265 | if (rc) | ||
266 | return rc; | ||
267 | val = apei_exec_ctx_get_output(&ctx); | ||
268 | if (val != EINJ_STATUS_SUCCESS) | ||
269 | return -EBUSY; | ||
270 | |||
271 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_TRIGGER_TABLE); | ||
272 | if (rc) | ||
273 | return rc; | ||
274 | trigger_paddr = apei_exec_ctx_get_output(&ctx); | ||
275 | rc = __einj_error_trigger(trigger_paddr); | ||
276 | if (rc) | ||
277 | return rc; | ||
278 | rc = apei_exec_run(&ctx, ACPI_EINJ_END_OPERATION); | ||
279 | |||
280 | return rc; | ||
281 | } | ||
282 | |||
283 | /* Inject the specified hardware error */ | ||
284 | static int einj_error_inject(u32 type) | ||
285 | { | ||
286 | int rc; | ||
287 | |||
288 | mutex_lock(&einj_mutex); | ||
289 | rc = __einj_error_inject(type); | ||
290 | mutex_unlock(&einj_mutex); | ||
291 | |||
292 | return rc; | ||
293 | } | ||
294 | |||
295 | static u32 error_type; | ||
296 | static struct dentry *einj_debug_dir; | ||
297 | |||
298 | static int available_error_type_show(struct seq_file *m, void *v) | ||
299 | { | ||
300 | int rc; | ||
301 | u32 available_error_type = 0; | ||
302 | |||
303 | rc = einj_get_available_error_type(&available_error_type); | ||
304 | if (rc) | ||
305 | return rc; | ||
306 | if (available_error_type & 0x0001) | ||
307 | seq_printf(m, "0x00000001\tProcessor Correctable\n"); | ||
308 | if (available_error_type & 0x0002) | ||
309 | seq_printf(m, "0x00000002\tProcessor Uncorrectable non-fatal\n"); | ||
310 | if (available_error_type & 0x0004) | ||
311 | seq_printf(m, "0x00000004\tProcessor Uncorrectable fatal\n"); | ||
312 | if (available_error_type & 0x0008) | ||
313 | seq_printf(m, "0x00000008\tMemory Correctable\n"); | ||
314 | if (available_error_type & 0x0010) | ||
315 | seq_printf(m, "0x00000010\tMemory Uncorrectable non-fatal\n"); | ||
316 | if (available_error_type & 0x0020) | ||
317 | seq_printf(m, "0x00000020\tMemory Uncorrectable fatal\n"); | ||
318 | if (available_error_type & 0x0040) | ||
319 | seq_printf(m, "0x00000040\tPCI Express Correctable\n"); | ||
320 | if (available_error_type & 0x0080) | ||
321 | seq_printf(m, "0x00000080\tPCI Express Uncorrectable non-fatal\n"); | ||
322 | if (available_error_type & 0x0100) | ||
323 | seq_printf(m, "0x00000100\tPCI Express Uncorrectable fatal\n"); | ||
324 | if (available_error_type & 0x0200) | ||
325 | seq_printf(m, "0x00000200\tPlatform Correctable\n"); | ||
326 | if (available_error_type & 0x0400) | ||
327 | seq_printf(m, "0x00000400\tPlatform Uncorrectable non-fatal\n"); | ||
328 | if (available_error_type & 0x0800) | ||
329 | seq_printf(m, "0x00000800\tPlatform Uncorrectable fatal\n"); | ||
330 | |||
331 | return 0; | ||
332 | } | ||
333 | |||
334 | static int available_error_type_open(struct inode *inode, struct file *file) | ||
335 | { | ||
336 | return single_open(file, available_error_type_show, NULL); | ||
337 | } | ||
338 | |||
339 | static const struct file_operations available_error_type_fops = { | ||
340 | .open = available_error_type_open, | ||
341 | .read = seq_read, | ||
342 | .llseek = seq_lseek, | ||
343 | .release = single_release, | ||
344 | }; | ||
345 | |||
346 | static int error_type_get(void *data, u64 *val) | ||
347 | { | ||
348 | *val = error_type; | ||
349 | |||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | static int error_type_set(void *data, u64 val) | ||
354 | { | ||
355 | int rc; | ||
356 | u32 available_error_type = 0; | ||
357 | |||
358 | /* Only one error type can be specified */ | ||
359 | if (val & (val - 1)) | ||
360 | return -EINVAL; | ||
361 | rc = einj_get_available_error_type(&available_error_type); | ||
362 | if (rc) | ||
363 | return rc; | ||
364 | if (!(val & available_error_type)) | ||
365 | return -EINVAL; | ||
366 | error_type = val; | ||
367 | |||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | DEFINE_SIMPLE_ATTRIBUTE(error_type_fops, error_type_get, | ||
372 | error_type_set, "0x%llx\n"); | ||
373 | |||
374 | static int error_inject_set(void *data, u64 val) | ||
375 | { | ||
376 | if (!error_type) | ||
377 | return -EINVAL; | ||
378 | |||
379 | return einj_error_inject(error_type); | ||
380 | } | ||
381 | |||
382 | DEFINE_SIMPLE_ATTRIBUTE(error_inject_fops, NULL, | ||
383 | error_inject_set, "%llu\n"); | ||
384 | |||
385 | static int einj_check_table(struct acpi_table_einj *einj_tab) | ||
386 | { | ||
387 | if (einj_tab->header_length != sizeof(struct acpi_table_einj)) | ||
388 | return -EINVAL; | ||
389 | if (einj_tab->header.length < sizeof(struct acpi_table_einj)) | ||
390 | return -EINVAL; | ||
391 | if (einj_tab->entries != | ||
392 | (einj_tab->header.length - sizeof(struct acpi_table_einj)) / | ||
393 | sizeof(struct acpi_einj_entry)) | ||
394 | return -EINVAL; | ||
395 | |||
396 | return 0; | ||
397 | } | ||
398 | |||
399 | static int __init einj_init(void) | ||
400 | { | ||
401 | int rc; | ||
402 | acpi_status status; | ||
403 | struct dentry *fentry; | ||
404 | struct apei_exec_context ctx; | ||
405 | |||
406 | if (acpi_disabled) | ||
407 | return -ENODEV; | ||
408 | |||
409 | status = acpi_get_table(ACPI_SIG_EINJ, 0, | ||
410 | (struct acpi_table_header **)&einj_tab); | ||
411 | if (status == AE_NOT_FOUND) { | ||
412 | pr_info(EINJ_PFX "Table is not found!\n"); | ||
413 | return -ENODEV; | ||
414 | } else if (ACPI_FAILURE(status)) { | ||
415 | const char *msg = acpi_format_exception(status); | ||
416 | pr_err(EINJ_PFX "Failed to get table, %s\n", msg); | ||
417 | return -EINVAL; | ||
418 | } | ||
419 | |||
420 | rc = einj_check_table(einj_tab); | ||
421 | if (rc) { | ||
422 | pr_warning(FW_BUG EINJ_PFX "EINJ table is invalid\n"); | ||
423 | return -EINVAL; | ||
424 | } | ||
425 | |||
426 | rc = -ENOMEM; | ||
427 | einj_debug_dir = debugfs_create_dir("einj", apei_get_debugfs_dir()); | ||
428 | if (!einj_debug_dir) | ||
429 | goto err_cleanup; | ||
430 | fentry = debugfs_create_file("available_error_type", S_IRUSR, | ||
431 | einj_debug_dir, NULL, | ||
432 | &available_error_type_fops); | ||
433 | if (!fentry) | ||
434 | goto err_cleanup; | ||
435 | fentry = debugfs_create_file("error_type", S_IRUSR | S_IWUSR, | ||
436 | einj_debug_dir, NULL, &error_type_fops); | ||
437 | if (!fentry) | ||
438 | goto err_cleanup; | ||
439 | fentry = debugfs_create_file("error_inject", S_IWUSR, | ||
440 | einj_debug_dir, NULL, &error_inject_fops); | ||
441 | if (!fentry) | ||
442 | goto err_cleanup; | ||
443 | |||
444 | apei_resources_init(&einj_resources); | ||
445 | einj_exec_ctx_init(&ctx); | ||
446 | rc = apei_exec_collect_resources(&ctx, &einj_resources); | ||
447 | if (rc) | ||
448 | goto err_fini; | ||
449 | rc = apei_resources_request(&einj_resources, "APEI EINJ"); | ||
450 | if (rc) | ||
451 | goto err_fini; | ||
452 | rc = apei_exec_pre_map_gars(&ctx); | ||
453 | if (rc) | ||
454 | goto err_release; | ||
455 | |||
456 | pr_info(EINJ_PFX "Error INJection is initialized.\n"); | ||
457 | |||
458 | return 0; | ||
459 | |||
460 | err_release: | ||
461 | apei_resources_release(&einj_resources); | ||
462 | err_fini: | ||
463 | apei_resources_fini(&einj_resources); | ||
464 | err_cleanup: | ||
465 | debugfs_remove_recursive(einj_debug_dir); | ||
466 | |||
467 | return rc; | ||
468 | } | ||
469 | |||
470 | static void __exit einj_exit(void) | ||
471 | { | ||
472 | struct apei_exec_context ctx; | ||
473 | |||
474 | einj_exec_ctx_init(&ctx); | ||
475 | apei_exec_post_unmap_gars(&ctx); | ||
476 | apei_resources_release(&einj_resources); | ||
477 | apei_resources_fini(&einj_resources); | ||
478 | debugfs_remove_recursive(einj_debug_dir); | ||
479 | } | ||
480 | |||
481 | module_init(einj_init); | ||
482 | module_exit(einj_exit); | ||
483 | |||
484 | MODULE_AUTHOR("Huang Ying"); | ||
485 | MODULE_DESCRIPTION("APEI Error INJection support"); | ||
486 | MODULE_LICENSE("GPL"); | ||