]>
Commit | Line | Data |
---|---|---|
a3e2acc5 HY |
1 | /* |
2 | * APEI Boot Error Record Table (BERT) support | |
3 | * | |
4 | * Copyright 2011 Intel Corp. | |
5 | * Author: Huang Ying <ying.huang@intel.com> | |
6 | * | |
7 | * Under normal circumstances, when a hardware error occurs, the error | |
8 | * handler receives control and processes the error. This gives OSPM a | |
9 | * chance to process the error condition, report it, and optionally attempt | |
10 | * recovery. In some cases, the system is unable to process an error. | |
11 | * For example, system firmware or a management controller may choose to | |
12 | * reset the system or the system might experience an uncontrolled crash | |
13 | * or reset.The boot error source is used to report unhandled errors that | |
14 | * occurred in a previous boot. This mechanism is described in the BERT | |
15 | * table. | |
16 | * | |
17 | * For more information about BERT, please refer to ACPI Specification | |
18 | * version 4.0, section 17.3.1 | |
19 | * | |
20 | * This file is licensed under GPLv2. | |
21 | * | |
22 | */ | |
23 | ||
24 | #include <linux/kernel.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/init.h> | |
27 | #include <linux/acpi.h> | |
28 | #include <linux/io.h> | |
29 | ||
30 | #include "apei-internal.h" | |
31 | ||
32 | #undef pr_fmt | |
33 | #define pr_fmt(fmt) "BERT: " fmt | |
34 | ||
35 | static int bert_disable; | |
36 | ||
37 | static void __init bert_print_all(struct acpi_bert_region *region, | |
38 | unsigned int region_len) | |
39 | { | |
40 | struct acpi_hest_generic_status *estatus = | |
41 | (struct acpi_hest_generic_status *)region; | |
42 | int remain = region_len; | |
43 | u32 estatus_len; | |
44 | ||
45 | if (!estatus->block_status) | |
46 | return; | |
47 | ||
48 | while (remain > sizeof(struct acpi_bert_region)) { | |
49 | if (cper_estatus_check(estatus)) { | |
50 | pr_err(FW_BUG "Invalid error record.\n"); | |
51 | return; | |
52 | } | |
53 | ||
54 | estatus_len = cper_estatus_len(estatus); | |
55 | if (remain < estatus_len) { | |
56 | pr_err(FW_BUG "Truncated status block (length: %u).\n", | |
57 | estatus_len); | |
58 | return; | |
59 | } | |
60 | ||
61 | pr_info_once("Error records from previous boot:\n"); | |
62 | ||
63 | cper_estatus_print(KERN_INFO HW_ERR, estatus); | |
64 | ||
65 | /* | |
66 | * Because the boot error source is "one-time polled" type, | |
67 | * clear Block Status of current Generic Error Status Block, | |
68 | * once it's printed. | |
69 | */ | |
70 | estatus->block_status = 0; | |
71 | ||
72 | estatus = (void *)estatus + estatus_len; | |
73 | /* No more error records. */ | |
74 | if (!estatus->block_status) | |
75 | return; | |
76 | ||
77 | remain -= estatus_len; | |
78 | } | |
79 | } | |
80 | ||
81 | static int __init setup_bert_disable(char *str) | |
82 | { | |
83 | bert_disable = 1; | |
84 | ||
85 | return 0; | |
86 | } | |
87 | __setup("bert_disable", setup_bert_disable); | |
88 | ||
89 | static int __init bert_check_table(struct acpi_table_bert *bert_tab) | |
90 | { | |
91 | if (bert_tab->header.length < sizeof(struct acpi_table_bert) || | |
92 | bert_tab->region_length < sizeof(struct acpi_bert_region)) | |
93 | return -EINVAL; | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | static int __init bert_init(void) | |
99 | { | |
41139ac3 | 100 | struct apei_resources bert_resources; |
a3e2acc5 HY |
101 | struct acpi_bert_region *boot_error_region; |
102 | struct acpi_table_bert *bert_tab; | |
103 | unsigned int region_len; | |
104 | acpi_status status; | |
105 | int rc = 0; | |
106 | ||
107 | if (acpi_disabled) | |
108 | return 0; | |
109 | ||
110 | if (bert_disable) { | |
111 | pr_info("Boot Error Record Table support is disabled.\n"); | |
112 | return 0; | |
113 | } | |
114 | ||
115 | status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab); | |
116 | if (status == AE_NOT_FOUND) | |
117 | return 0; | |
118 | ||
119 | if (ACPI_FAILURE(status)) { | |
120 | pr_err("get table failed, %s.\n", acpi_format_exception(status)); | |
121 | return -EINVAL; | |
122 | } | |
123 | ||
124 | rc = bert_check_table(bert_tab); | |
125 | if (rc) { | |
126 | pr_err(FW_BUG "table invalid.\n"); | |
127 | return rc; | |
128 | } | |
129 | ||
130 | region_len = bert_tab->region_length; | |
41139ac3 HY |
131 | apei_resources_init(&bert_resources); |
132 | rc = apei_resources_add(&bert_resources, bert_tab->address, | |
133 | region_len, true); | |
134 | if (rc) | |
135 | return rc; | |
136 | rc = apei_resources_request(&bert_resources, "APEI BERT"); | |
137 | if (rc) | |
138 | goto out_fini; | |
a3e2acc5 HY |
139 | boot_error_region = ioremap_cache(bert_tab->address, region_len); |
140 | if (boot_error_region) { | |
141 | bert_print_all(boot_error_region, region_len); | |
142 | iounmap(boot_error_region); | |
143 | } else { | |
144 | rc = -ENOMEM; | |
145 | } | |
146 | ||
41139ac3 HY |
147 | apei_resources_release(&bert_resources); |
148 | out_fini: | |
149 | apei_resources_fini(&bert_resources); | |
a3e2acc5 HY |
150 | |
151 | return rc; | |
152 | } | |
153 | ||
154 | late_initcall(bert_init); |