diff options
Diffstat (limited to 'drivers/acpi/apei')
-rw-r--r-- | drivers/acpi/apei/Kconfig | 9 | ||||
-rw-r--r-- | drivers/acpi/apei/apei-internal.h | 2 | ||||
-rw-r--r-- | drivers/acpi/apei/cper.c | 321 | ||||
-rw-r--r-- | drivers/acpi/apei/einj.c | 10 | ||||
-rw-r--r-- | drivers/acpi/apei/erst-dbg.c | 25 | ||||
-rw-r--r-- | drivers/acpi/apei/erst.c | 424 | ||||
-rw-r--r-- | drivers/acpi/apei/ghes.c | 433 | ||||
-rw-r--r-- | drivers/acpi/apei/hest.c | 48 |
8 files changed, 1102 insertions, 170 deletions
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index fca34ccfd294..f739a70b1c70 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig | |||
@@ -1,5 +1,7 @@ | |||
1 | config ACPI_APEI | 1 | config ACPI_APEI |
2 | bool "ACPI Platform Error Interface (APEI)" | 2 | bool "ACPI Platform Error Interface (APEI)" |
3 | select MISC_FILESYSTEMS | ||
4 | select PSTORE | ||
3 | depends on X86 | 5 | depends on X86 |
4 | help | 6 | help |
5 | APEI allows to report errors (for example from the chipset) | 7 | APEI allows to report errors (for example from the chipset) |
@@ -21,6 +23,13 @@ config ACPI_APEI_GHES | |||
21 | by firmware to produce more valuable hardware error | 23 | by firmware to produce more valuable hardware error |
22 | information for Linux. | 24 | information for Linux. |
23 | 25 | ||
26 | config ACPI_APEI_PCIEAER | ||
27 | bool "APEI PCIe AER logging/recovering support" | ||
28 | depends on ACPI_APEI && PCIEAER | ||
29 | help | ||
30 | PCIe AER errors may be reported via APEI firmware first mode. | ||
31 | Turn on this option to enable the corresponding support. | ||
32 | |||
24 | config ACPI_APEI_EINJ | 33 | config ACPI_APEI_EINJ |
25 | tristate "APEI Error INJection (EINJ)" | 34 | tristate "APEI Error INJection (EINJ)" |
26 | depends on ACPI_APEI && DEBUG_FS | 35 | depends on ACPI_APEI && DEBUG_FS |
diff --git a/drivers/acpi/apei/apei-internal.h b/drivers/acpi/apei/apei-internal.h index 18df1e940276..ef0581f2094d 100644 --- a/drivers/acpi/apei/apei-internal.h +++ b/drivers/acpi/apei/apei-internal.h | |||
@@ -109,6 +109,8 @@ static inline u32 apei_estatus_len(struct acpi_hest_generic_status *estatus) | |||
109 | return sizeof(*estatus) + estatus->data_length; | 109 | return sizeof(*estatus) + estatus->data_length; |
110 | } | 110 | } |
111 | 111 | ||
112 | void apei_estatus_print(const char *pfx, | ||
113 | const struct acpi_hest_generic_status *estatus); | ||
112 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus); | 114 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus); |
113 | int apei_estatus_check(const struct acpi_hest_generic_status *estatus); | 115 | int apei_estatus_check(const struct acpi_hest_generic_status *estatus); |
114 | #endif | 116 | #endif |
diff --git a/drivers/acpi/apei/cper.c b/drivers/acpi/apei/cper.c index f4cf2fc4c8c1..5d4189464d63 100644 --- a/drivers/acpi/apei/cper.c +++ b/drivers/acpi/apei/cper.c | |||
@@ -29,6 +29,7 @@ | |||
29 | #include <linux/time.h> | 29 | #include <linux/time.h> |
30 | #include <linux/cper.h> | 30 | #include <linux/cper.h> |
31 | #include <linux/acpi.h> | 31 | #include <linux/acpi.h> |
32 | #include <linux/aer.h> | ||
32 | 33 | ||
33 | /* | 34 | /* |
34 | * CPER record ID need to be unique even after reboot, because record | 35 | * CPER record ID need to be unique even after reboot, because record |
@@ -46,6 +47,326 @@ u64 cper_next_record_id(void) | |||
46 | } | 47 | } |
47 | EXPORT_SYMBOL_GPL(cper_next_record_id); | 48 | EXPORT_SYMBOL_GPL(cper_next_record_id); |
48 | 49 | ||
50 | static const char *cper_severity_strs[] = { | ||
51 | "recoverable", | ||
52 | "fatal", | ||
53 | "corrected", | ||
54 | "info", | ||
55 | }; | ||
56 | |||
57 | static const char *cper_severity_str(unsigned int severity) | ||
58 | { | ||
59 | return severity < ARRAY_SIZE(cper_severity_strs) ? | ||
60 | cper_severity_strs[severity] : "unknown"; | ||
61 | } | ||
62 | |||
63 | /* | ||
64 | * cper_print_bits - print strings for set bits | ||
65 | * @pfx: prefix for each line, including log level and prefix string | ||
66 | * @bits: bit mask | ||
67 | * @strs: string array, indexed by bit position | ||
68 | * @strs_size: size of the string array: @strs | ||
69 | * | ||
70 | * For each set bit in @bits, print the corresponding string in @strs. | ||
71 | * If the output length is longer than 80, multiple line will be | ||
72 | * printed, with @pfx is printed at the beginning of each line. | ||
73 | */ | ||
74 | void cper_print_bits(const char *pfx, unsigned int bits, | ||
75 | const char *strs[], unsigned int strs_size) | ||
76 | { | ||
77 | int i, len = 0; | ||
78 | const char *str; | ||
79 | char buf[84]; | ||
80 | |||
81 | for (i = 0; i < strs_size; i++) { | ||
82 | if (!(bits & (1U << i))) | ||
83 | continue; | ||
84 | str = strs[i]; | ||
85 | if (!str) | ||
86 | continue; | ||
87 | if (len && len + strlen(str) + 2 > 80) { | ||
88 | printk("%s\n", buf); | ||
89 | len = 0; | ||
90 | } | ||
91 | if (!len) | ||
92 | len = snprintf(buf, sizeof(buf), "%s%s", pfx, str); | ||
93 | else | ||
94 | len += snprintf(buf+len, sizeof(buf)-len, ", %s", str); | ||
95 | } | ||
96 | if (len) | ||
97 | printk("%s\n", buf); | ||
98 | } | ||
99 | |||
100 | static const char *cper_proc_type_strs[] = { | ||
101 | "IA32/X64", | ||
102 | "IA64", | ||
103 | }; | ||
104 | |||
105 | static const char *cper_proc_isa_strs[] = { | ||
106 | "IA32", | ||
107 | "IA64", | ||
108 | "X64", | ||
109 | }; | ||
110 | |||
111 | static const char *cper_proc_error_type_strs[] = { | ||
112 | "cache error", | ||
113 | "TLB error", | ||
114 | "bus error", | ||
115 | "micro-architectural error", | ||
116 | }; | ||
117 | |||
118 | static const char *cper_proc_op_strs[] = { | ||
119 | "unknown or generic", | ||
120 | "data read", | ||
121 | "data write", | ||
122 | "instruction execution", | ||
123 | }; | ||
124 | |||
125 | static const char *cper_proc_flag_strs[] = { | ||
126 | "restartable", | ||
127 | "precise IP", | ||
128 | "overflow", | ||
129 | "corrected", | ||
130 | }; | ||
131 | |||
132 | static void cper_print_proc_generic(const char *pfx, | ||
133 | const struct cper_sec_proc_generic *proc) | ||
134 | { | ||
135 | if (proc->validation_bits & CPER_PROC_VALID_TYPE) | ||
136 | printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type, | ||
137 | proc->proc_type < ARRAY_SIZE(cper_proc_type_strs) ? | ||
138 | cper_proc_type_strs[proc->proc_type] : "unknown"); | ||
139 | if (proc->validation_bits & CPER_PROC_VALID_ISA) | ||
140 | printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa, | ||
141 | proc->proc_isa < ARRAY_SIZE(cper_proc_isa_strs) ? | ||
142 | cper_proc_isa_strs[proc->proc_isa] : "unknown"); | ||
143 | if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { | ||
144 | printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type); | ||
145 | cper_print_bits(pfx, proc->proc_error_type, | ||
146 | cper_proc_error_type_strs, | ||
147 | ARRAY_SIZE(cper_proc_error_type_strs)); | ||
148 | } | ||
149 | if (proc->validation_bits & CPER_PROC_VALID_OPERATION) | ||
150 | printk("%s""operation: %d, %s\n", pfx, proc->operation, | ||
151 | proc->operation < ARRAY_SIZE(cper_proc_op_strs) ? | ||
152 | cper_proc_op_strs[proc->operation] : "unknown"); | ||
153 | if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { | ||
154 | printk("%s""flags: 0x%02x\n", pfx, proc->flags); | ||
155 | cper_print_bits(pfx, proc->flags, cper_proc_flag_strs, | ||
156 | ARRAY_SIZE(cper_proc_flag_strs)); | ||
157 | } | ||
158 | if (proc->validation_bits & CPER_PROC_VALID_LEVEL) | ||
159 | printk("%s""level: %d\n", pfx, proc->level); | ||
160 | if (proc->validation_bits & CPER_PROC_VALID_VERSION) | ||
161 | printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version); | ||
162 | if (proc->validation_bits & CPER_PROC_VALID_ID) | ||
163 | printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id); | ||
164 | if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) | ||
165 | printk("%s""target_address: 0x%016llx\n", | ||
166 | pfx, proc->target_addr); | ||
167 | if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) | ||
168 | printk("%s""requestor_id: 0x%016llx\n", | ||
169 | pfx, proc->requestor_id); | ||
170 | if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) | ||
171 | printk("%s""responder_id: 0x%016llx\n", | ||
172 | pfx, proc->responder_id); | ||
173 | if (proc->validation_bits & CPER_PROC_VALID_IP) | ||
174 | printk("%s""IP: 0x%016llx\n", pfx, proc->ip); | ||
175 | } | ||
176 | |||
177 | static const char *cper_mem_err_type_strs[] = { | ||
178 | "unknown", | ||
179 | "no error", | ||
180 | "single-bit ECC", | ||
181 | "multi-bit ECC", | ||
182 | "single-symbol chipkill ECC", | ||
183 | "multi-symbol chipkill ECC", | ||
184 | "master abort", | ||
185 | "target abort", | ||
186 | "parity error", | ||
187 | "watchdog timeout", | ||
188 | "invalid address", | ||
189 | "mirror Broken", | ||
190 | "memory sparing", | ||
191 | "scrub corrected error", | ||
192 | "scrub uncorrected error", | ||
193 | }; | ||
194 | |||
195 | static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem) | ||
196 | { | ||
197 | if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) | ||
198 | printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status); | ||
199 | if (mem->validation_bits & CPER_MEM_VALID_PHYSICAL_ADDRESS) | ||
200 | printk("%s""physical_address: 0x%016llx\n", | ||
201 | pfx, mem->physical_addr); | ||
202 | if (mem->validation_bits & CPER_MEM_VALID_PHYSICAL_ADDRESS_MASK) | ||
203 | printk("%s""physical_address_mask: 0x%016llx\n", | ||
204 | pfx, mem->physical_addr_mask); | ||
205 | if (mem->validation_bits & CPER_MEM_VALID_NODE) | ||
206 | printk("%s""node: %d\n", pfx, mem->node); | ||
207 | if (mem->validation_bits & CPER_MEM_VALID_CARD) | ||
208 | printk("%s""card: %d\n", pfx, mem->card); | ||
209 | if (mem->validation_bits & CPER_MEM_VALID_MODULE) | ||
210 | printk("%s""module: %d\n", pfx, mem->module); | ||
211 | if (mem->validation_bits & CPER_MEM_VALID_BANK) | ||
212 | printk("%s""bank: %d\n", pfx, mem->bank); | ||
213 | if (mem->validation_bits & CPER_MEM_VALID_DEVICE) | ||
214 | printk("%s""device: %d\n", pfx, mem->device); | ||
215 | if (mem->validation_bits & CPER_MEM_VALID_ROW) | ||
216 | printk("%s""row: %d\n", pfx, mem->row); | ||
217 | if (mem->validation_bits & CPER_MEM_VALID_COLUMN) | ||
218 | printk("%s""column: %d\n", pfx, mem->column); | ||
219 | if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) | ||
220 | printk("%s""bit_position: %d\n", pfx, mem->bit_pos); | ||
221 | if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) | ||
222 | printk("%s""requestor_id: 0x%016llx\n", pfx, mem->requestor_id); | ||
223 | if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) | ||
224 | printk("%s""responder_id: 0x%016llx\n", pfx, mem->responder_id); | ||
225 | if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) | ||
226 | printk("%s""target_id: 0x%016llx\n", pfx, mem->target_id); | ||
227 | if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { | ||
228 | u8 etype = mem->error_type; | ||
229 | printk("%s""error_type: %d, %s\n", pfx, etype, | ||
230 | etype < ARRAY_SIZE(cper_mem_err_type_strs) ? | ||
231 | cper_mem_err_type_strs[etype] : "unknown"); | ||
232 | } | ||
233 | } | ||
234 | |||
235 | static const char *cper_pcie_port_type_strs[] = { | ||
236 | "PCIe end point", | ||
237 | "legacy PCI end point", | ||
238 | "unknown", | ||
239 | "unknown", | ||
240 | "root port", | ||
241 | "upstream switch port", | ||
242 | "downstream switch port", | ||
243 | "PCIe to PCI/PCI-X bridge", | ||
244 | "PCI/PCI-X to PCIe bridge", | ||
245 | "root complex integrated endpoint device", | ||
246 | "root complex event collector", | ||
247 | }; | ||
248 | |||
249 | static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, | ||
250 | const struct acpi_hest_generic_data *gdata) | ||
251 | { | ||
252 | if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) | ||
253 | printk("%s""port_type: %d, %s\n", pfx, pcie->port_type, | ||
254 | pcie->port_type < ARRAY_SIZE(cper_pcie_port_type_strs) ? | ||
255 | cper_pcie_port_type_strs[pcie->port_type] : "unknown"); | ||
256 | if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) | ||
257 | printk("%s""version: %d.%d\n", pfx, | ||
258 | pcie->version.major, pcie->version.minor); | ||
259 | if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) | ||
260 | printk("%s""command: 0x%04x, status: 0x%04x\n", pfx, | ||
261 | pcie->command, pcie->status); | ||
262 | if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { | ||
263 | const __u8 *p; | ||
264 | printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx, | ||
265 | pcie->device_id.segment, pcie->device_id.bus, | ||
266 | pcie->device_id.device, pcie->device_id.function); | ||
267 | printk("%s""slot: %d\n", pfx, | ||
268 | pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); | ||
269 | printk("%s""secondary_bus: 0x%02x\n", pfx, | ||
270 | pcie->device_id.secondary_bus); | ||
271 | printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, | ||
272 | pcie->device_id.vendor_id, pcie->device_id.device_id); | ||
273 | p = pcie->device_id.class_code; | ||
274 | printk("%s""class_code: %02x%02x%02x\n", pfx, p[0], p[1], p[2]); | ||
275 | } | ||
276 | if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) | ||
277 | printk("%s""serial number: 0x%04x, 0x%04x\n", pfx, | ||
278 | pcie->serial_number.lower, pcie->serial_number.upper); | ||
279 | if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) | ||
280 | printk( | ||
281 | "%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n", | ||
282 | pfx, pcie->bridge.secondary_status, pcie->bridge.control); | ||
283 | #ifdef CONFIG_ACPI_APEI_PCIEAER | ||
284 | if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) { | ||
285 | struct aer_capability_regs *aer_regs = (void *)pcie->aer_info; | ||
286 | cper_print_aer(pfx, gdata->error_severity, aer_regs); | ||
287 | } | ||
288 | #endif | ||
289 | } | ||
290 | |||
291 | static const char *apei_estatus_section_flag_strs[] = { | ||
292 | "primary", | ||
293 | "containment warning", | ||
294 | "reset", | ||
295 | "threshold exceeded", | ||
296 | "resource not accessible", | ||
297 | "latent error", | ||
298 | }; | ||
299 | |||
300 | static void apei_estatus_print_section( | ||
301 | const char *pfx, const struct acpi_hest_generic_data *gdata, int sec_no) | ||
302 | { | ||
303 | uuid_le *sec_type = (uuid_le *)gdata->section_type; | ||
304 | __u16 severity; | ||
305 | |||
306 | severity = gdata->error_severity; | ||
307 | printk("%s""section: %d, severity: %d, %s\n", pfx, sec_no, severity, | ||
308 | cper_severity_str(severity)); | ||
309 | printk("%s""flags: 0x%02x\n", pfx, gdata->flags); | ||
310 | cper_print_bits(pfx, gdata->flags, apei_estatus_section_flag_strs, | ||
311 | ARRAY_SIZE(apei_estatus_section_flag_strs)); | ||
312 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) | ||
313 | printk("%s""fru_id: %pUl\n", pfx, (uuid_le *)gdata->fru_id); | ||
314 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) | ||
315 | printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text); | ||
316 | |||
317 | if (!uuid_le_cmp(*sec_type, CPER_SEC_PROC_GENERIC)) { | ||
318 | struct cper_sec_proc_generic *proc_err = (void *)(gdata + 1); | ||
319 | printk("%s""section_type: general processor error\n", pfx); | ||
320 | if (gdata->error_data_length >= sizeof(*proc_err)) | ||
321 | cper_print_proc_generic(pfx, proc_err); | ||
322 | else | ||
323 | goto err_section_too_small; | ||
324 | } else if (!uuid_le_cmp(*sec_type, CPER_SEC_PLATFORM_MEM)) { | ||
325 | struct cper_sec_mem_err *mem_err = (void *)(gdata + 1); | ||
326 | printk("%s""section_type: memory error\n", pfx); | ||
327 | if (gdata->error_data_length >= sizeof(*mem_err)) | ||
328 | cper_print_mem(pfx, mem_err); | ||
329 | else | ||
330 | goto err_section_too_small; | ||
331 | } else if (!uuid_le_cmp(*sec_type, CPER_SEC_PCIE)) { | ||
332 | struct cper_sec_pcie *pcie = (void *)(gdata + 1); | ||
333 | printk("%s""section_type: PCIe error\n", pfx); | ||
334 | if (gdata->error_data_length >= sizeof(*pcie)) | ||
335 | cper_print_pcie(pfx, pcie, gdata); | ||
336 | else | ||
337 | goto err_section_too_small; | ||
338 | } else | ||
339 | printk("%s""section type: unknown, %pUl\n", pfx, sec_type); | ||
340 | |||
341 | return; | ||
342 | |||
343 | err_section_too_small: | ||
344 | pr_err(FW_WARN "error section length is too small\n"); | ||
345 | } | ||
346 | |||
347 | void apei_estatus_print(const char *pfx, | ||
348 | const struct acpi_hest_generic_status *estatus) | ||
349 | { | ||
350 | struct acpi_hest_generic_data *gdata; | ||
351 | unsigned int data_len, gedata_len; | ||
352 | int sec_no = 0; | ||
353 | __u16 severity; | ||
354 | |||
355 | printk("%s""APEI generic hardware error status\n", pfx); | ||
356 | severity = estatus->error_severity; | ||
357 | printk("%s""severity: %d, %s\n", pfx, severity, | ||
358 | cper_severity_str(severity)); | ||
359 | data_len = estatus->data_length; | ||
360 | gdata = (struct acpi_hest_generic_data *)(estatus + 1); | ||
361 | while (data_len > sizeof(*gdata)) { | ||
362 | gedata_len = gdata->error_data_length; | ||
363 | apei_estatus_print_section(pfx, gdata, sec_no); | ||
364 | data_len -= gedata_len + sizeof(*gdata); | ||
365 | sec_no++; | ||
366 | } | ||
367 | } | ||
368 | EXPORT_SYMBOL_GPL(apei_estatus_print); | ||
369 | |||
49 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus) | 370 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus) |
50 | { | 371 | { |
51 | if (estatus->data_length && | 372 | if (estatus->data_length && |
diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c index cf29df69380b..f74b2ea11f21 100644 --- a/drivers/acpi/apei/einj.c +++ b/drivers/acpi/apei/einj.c | |||
@@ -39,7 +39,7 @@ | |||
39 | #define EINJ_PFX "EINJ: " | 39 | #define EINJ_PFX "EINJ: " |
40 | 40 | ||
41 | #define SPIN_UNIT 100 /* 100ns */ | 41 | #define SPIN_UNIT 100 /* 100ns */ |
42 | /* Firmware should respond within 1 miliseconds */ | 42 | /* Firmware should respond within 1 milliseconds */ |
43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | 43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) |
44 | 44 | ||
45 | /* | 45 | /* |
@@ -101,6 +101,14 @@ static DEFINE_MUTEX(einj_mutex); | |||
101 | 101 | ||
102 | static struct einj_parameter *einj_param; | 102 | static struct einj_parameter *einj_param; |
103 | 103 | ||
104 | #ifndef writeq | ||
105 | static inline void writeq(__u64 val, volatile void __iomem *addr) | ||
106 | { | ||
107 | writel(val, addr); | ||
108 | writel(val >> 32, addr+4); | ||
109 | } | ||
110 | #endif | ||
111 | |||
104 | static void einj_exec_ctx_init(struct apei_exec_context *ctx) | 112 | static void einj_exec_ctx_init(struct apei_exec_context *ctx) |
105 | { | 113 | { |
106 | apei_exec_ctx_init(ctx, einj_ins_type, ARRAY_SIZE(einj_ins_type), | 114 | apei_exec_ctx_init(ctx, einj_ins_type, ARRAY_SIZE(einj_ins_type), |
diff --git a/drivers/acpi/apei/erst-dbg.c b/drivers/acpi/apei/erst-dbg.c index da1228a9a544..a4cfb64c86a1 100644 --- a/drivers/acpi/apei/erst-dbg.c +++ b/drivers/acpi/apei/erst-dbg.c | |||
@@ -43,12 +43,27 @@ static DEFINE_MUTEX(erst_dbg_mutex); | |||
43 | 43 | ||
44 | static int erst_dbg_open(struct inode *inode, struct file *file) | 44 | static int erst_dbg_open(struct inode *inode, struct file *file) |
45 | { | 45 | { |
46 | int rc, *pos; | ||
47 | |||
46 | if (erst_disable) | 48 | if (erst_disable) |
47 | return -ENODEV; | 49 | return -ENODEV; |
48 | 50 | ||
51 | pos = (int *)&file->private_data; | ||
52 | |||
53 | rc = erst_get_record_id_begin(pos); | ||
54 | if (rc) | ||
55 | return rc; | ||
56 | |||
49 | return nonseekable_open(inode, file); | 57 | return nonseekable_open(inode, file); |
50 | } | 58 | } |
51 | 59 | ||
60 | static int erst_dbg_release(struct inode *inode, struct file *file) | ||
61 | { | ||
62 | erst_get_record_id_end(); | ||
63 | |||
64 | return 0; | ||
65 | } | ||
66 | |||
52 | static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | 67 | static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg) |
53 | { | 68 | { |
54 | int rc; | 69 | int rc; |
@@ -79,18 +94,20 @@ static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | |||
79 | static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf, | 94 | static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf, |
80 | size_t usize, loff_t *off) | 95 | size_t usize, loff_t *off) |
81 | { | 96 | { |
82 | int rc; | 97 | int rc, *pos; |
83 | ssize_t len = 0; | 98 | ssize_t len = 0; |
84 | u64 id; | 99 | u64 id; |
85 | 100 | ||
86 | if (*off != 0) | 101 | if (*off) |
87 | return -EINVAL; | 102 | return -EINVAL; |
88 | 103 | ||
89 | if (mutex_lock_interruptible(&erst_dbg_mutex) != 0) | 104 | if (mutex_lock_interruptible(&erst_dbg_mutex) != 0) |
90 | return -EINTR; | 105 | return -EINTR; |
91 | 106 | ||
107 | pos = (int *)&filp->private_data; | ||
108 | |||
92 | retry_next: | 109 | retry_next: |
93 | rc = erst_get_next_record_id(&id); | 110 | rc = erst_get_record_id_next(pos, &id); |
94 | if (rc) | 111 | if (rc) |
95 | goto out; | 112 | goto out; |
96 | /* no more record */ | 113 | /* no more record */ |
@@ -181,9 +198,11 @@ out: | |||
181 | static const struct file_operations erst_dbg_ops = { | 198 | static const struct file_operations erst_dbg_ops = { |
182 | .owner = THIS_MODULE, | 199 | .owner = THIS_MODULE, |
183 | .open = erst_dbg_open, | 200 | .open = erst_dbg_open, |
201 | .release = erst_dbg_release, | ||
184 | .read = erst_dbg_read, | 202 | .read = erst_dbg_read, |
185 | .write = erst_dbg_write, | 203 | .write = erst_dbg_write, |
186 | .unlocked_ioctl = erst_dbg_ioctl, | 204 | .unlocked_ioctl = erst_dbg_ioctl, |
205 | .llseek = no_llseek, | ||
187 | }; | 206 | }; |
188 | 207 | ||
189 | static struct miscdevice erst_dbg_dev = { | 208 | static struct miscdevice erst_dbg_dev = { |
diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index 1211c03149e8..e6cef8e1b534 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c | |||
@@ -34,6 +34,7 @@ | |||
34 | #include <linux/cper.h> | 34 | #include <linux/cper.h> |
35 | #include <linux/nmi.h> | 35 | #include <linux/nmi.h> |
36 | #include <linux/hardirq.h> | 36 | #include <linux/hardirq.h> |
37 | #include <linux/pstore.h> | ||
37 | #include <acpi/apei.h> | 38 | #include <acpi/apei.h> |
38 | 39 | ||
39 | #include "apei-internal.h" | 40 | #include "apei-internal.h" |
@@ -53,7 +54,7 @@ | |||
53 | sizeof(struct acpi_table_erst))) | 54 | sizeof(struct acpi_table_erst))) |
54 | 55 | ||
55 | #define SPIN_UNIT 100 /* 100ns */ | 56 | #define SPIN_UNIT 100 /* 100ns */ |
56 | /* Firmware should respond within 1 miliseconds */ | 57 | /* Firmware should respond within 1 milliseconds */ |
57 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | 58 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) |
58 | #define FIRMWARE_MAX_STALL 50 /* 50us */ | 59 | #define FIRMWARE_MAX_STALL 50 /* 50us */ |
59 | 60 | ||
@@ -86,7 +87,7 @@ static struct erst_erange { | |||
86 | * It is used to provide exclusive accessing for ERST Error Log | 87 | * It is used to provide exclusive accessing for ERST Error Log |
87 | * Address Range too. | 88 | * Address Range too. |
88 | */ | 89 | */ |
89 | static DEFINE_SPINLOCK(erst_lock); | 90 | static DEFINE_RAW_SPINLOCK(erst_lock); |
90 | 91 | ||
91 | static inline int erst_errno(int command_status) | 92 | static inline int erst_errno(int command_status) |
92 | { | 93 | { |
@@ -421,14 +422,30 @@ ssize_t erst_get_record_count(void) | |||
421 | if (erst_disable) | 422 | if (erst_disable) |
422 | return -ENODEV; | 423 | return -ENODEV; |
423 | 424 | ||
424 | spin_lock_irqsave(&erst_lock, flags); | 425 | raw_spin_lock_irqsave(&erst_lock, flags); |
425 | count = __erst_get_record_count(); | 426 | count = __erst_get_record_count(); |
426 | spin_unlock_irqrestore(&erst_lock, flags); | 427 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
427 | 428 | ||
428 | return count; | 429 | return count; |
429 | } | 430 | } |
430 | EXPORT_SYMBOL_GPL(erst_get_record_count); | 431 | EXPORT_SYMBOL_GPL(erst_get_record_count); |
431 | 432 | ||
433 | #define ERST_RECORD_ID_CACHE_SIZE_MIN 16 | ||
434 | #define ERST_RECORD_ID_CACHE_SIZE_MAX 1024 | ||
435 | |||
436 | struct erst_record_id_cache { | ||
437 | struct mutex lock; | ||
438 | u64 *entries; | ||
439 | int len; | ||
440 | int size; | ||
441 | int refcount; | ||
442 | }; | ||
443 | |||
444 | static struct erst_record_id_cache erst_record_id_cache = { | ||
445 | .lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock), | ||
446 | .refcount = 0, | ||
447 | }; | ||
448 | |||
432 | static int __erst_get_next_record_id(u64 *record_id) | 449 | static int __erst_get_next_record_id(u64 *record_id) |
433 | { | 450 | { |
434 | struct apei_exec_context ctx; | 451 | struct apei_exec_context ctx; |
@@ -443,26 +460,179 @@ static int __erst_get_next_record_id(u64 *record_id) | |||
443 | return 0; | 460 | return 0; |
444 | } | 461 | } |
445 | 462 | ||
463 | int erst_get_record_id_begin(int *pos) | ||
464 | { | ||
465 | int rc; | ||
466 | |||
467 | if (erst_disable) | ||
468 | return -ENODEV; | ||
469 | |||
470 | rc = mutex_lock_interruptible(&erst_record_id_cache.lock); | ||
471 | if (rc) | ||
472 | return rc; | ||
473 | erst_record_id_cache.refcount++; | ||
474 | mutex_unlock(&erst_record_id_cache.lock); | ||
475 | |||
476 | *pos = 0; | ||
477 | |||
478 | return 0; | ||
479 | } | ||
480 | EXPORT_SYMBOL_GPL(erst_get_record_id_begin); | ||
481 | |||
482 | /* erst_record_id_cache.lock must be held by caller */ | ||
483 | static int __erst_record_id_cache_add_one(void) | ||
484 | { | ||
485 | u64 id, prev_id, first_id; | ||
486 | int i, rc; | ||
487 | u64 *entries; | ||
488 | unsigned long flags; | ||
489 | |||
490 | id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID; | ||
491 | retry: | ||
492 | raw_spin_lock_irqsave(&erst_lock, flags); | ||
493 | rc = __erst_get_next_record_id(&id); | ||
494 | raw_spin_unlock_irqrestore(&erst_lock, flags); | ||
495 | if (rc == -ENOENT) | ||
496 | return 0; | ||
497 | if (rc) | ||
498 | return rc; | ||
499 | if (id == APEI_ERST_INVALID_RECORD_ID) | ||
500 | return 0; | ||
501 | /* can not skip current ID, or loop back to first ID */ | ||
502 | if (id == prev_id || id == first_id) | ||
503 | return 0; | ||
504 | if (first_id == APEI_ERST_INVALID_RECORD_ID) | ||
505 | first_id = id; | ||
506 | prev_id = id; | ||
507 | |||
508 | entries = erst_record_id_cache.entries; | ||
509 | for (i = 0; i < erst_record_id_cache.len; i++) { | ||
510 | if (entries[i] == id) | ||
511 | break; | ||
512 | } | ||
513 | /* record id already in cache, try next */ | ||
514 | if (i < erst_record_id_cache.len) | ||
515 | goto retry; | ||
516 | if (erst_record_id_cache.len >= erst_record_id_cache.size) { | ||
517 | int new_size, alloc_size; | ||
518 | u64 *new_entries; | ||
519 | |||
520 | new_size = erst_record_id_cache.size * 2; | ||
521 | new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN, | ||
522 | ERST_RECORD_ID_CACHE_SIZE_MAX); | ||
523 | if (new_size <= erst_record_id_cache.size) { | ||
524 | if (printk_ratelimit()) | ||
525 | pr_warning(FW_WARN ERST_PFX | ||
526 | "too many record ID!\n"); | ||
527 | return 0; | ||
528 | } | ||
529 | alloc_size = new_size * sizeof(entries[0]); | ||
530 | if (alloc_size < PAGE_SIZE) | ||
531 | new_entries = kmalloc(alloc_size, GFP_KERNEL); | ||
532 | else | ||
533 | new_entries = vmalloc(alloc_size); | ||
534 | if (!new_entries) | ||
535 | return -ENOMEM; | ||
536 | memcpy(new_entries, entries, | ||
537 | erst_record_id_cache.len * sizeof(entries[0])); | ||
538 | if (erst_record_id_cache.size < PAGE_SIZE) | ||
539 | kfree(entries); | ||
540 | else | ||
541 | vfree(entries); | ||
542 | erst_record_id_cache.entries = entries = new_entries; | ||
543 | erst_record_id_cache.size = new_size; | ||
544 | } | ||
545 | entries[i] = id; | ||
546 | erst_record_id_cache.len++; | ||
547 | |||
548 | return 1; | ||
549 | } | ||
550 | |||
446 | /* | 551 | /* |
447 | * Get the record ID of an existing error record on the persistent | 552 | * Get the record ID of an existing error record on the persistent |
448 | * storage. If there is no error record on the persistent storage, the | 553 | * storage. If there is no error record on the persistent storage, the |
449 | * returned record_id is APEI_ERST_INVALID_RECORD_ID. | 554 | * returned record_id is APEI_ERST_INVALID_RECORD_ID. |
450 | */ | 555 | */ |
451 | int erst_get_next_record_id(u64 *record_id) | 556 | int erst_get_record_id_next(int *pos, u64 *record_id) |
452 | { | 557 | { |
453 | int rc; | 558 | int rc = 0; |
454 | unsigned long flags; | 559 | u64 *entries; |
455 | 560 | ||
456 | if (erst_disable) | 561 | if (erst_disable) |
457 | return -ENODEV; | 562 | return -ENODEV; |
458 | 563 | ||
459 | spin_lock_irqsave(&erst_lock, flags); | 564 | /* must be enclosed by erst_get_record_id_begin/end */ |
460 | rc = __erst_get_next_record_id(record_id); | 565 | BUG_ON(!erst_record_id_cache.refcount); |
461 | spin_unlock_irqrestore(&erst_lock, flags); | 566 | BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len); |
567 | |||
568 | mutex_lock(&erst_record_id_cache.lock); | ||
569 | entries = erst_record_id_cache.entries; | ||
570 | for (; *pos < erst_record_id_cache.len; (*pos)++) | ||
571 | if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID) | ||
572 | break; | ||
573 | /* found next record id in cache */ | ||
574 | if (*pos < erst_record_id_cache.len) { | ||
575 | *record_id = entries[*pos]; | ||
576 | (*pos)++; | ||
577 | goto out_unlock; | ||
578 | } | ||
579 | |||
580 | /* Try to add one more record ID to cache */ | ||
581 | rc = __erst_record_id_cache_add_one(); | ||
582 | if (rc < 0) | ||
583 | goto out_unlock; | ||
584 | /* successfully add one new ID */ | ||
585 | if (rc == 1) { | ||
586 | *record_id = erst_record_id_cache.entries[*pos]; | ||
587 | (*pos)++; | ||
588 | rc = 0; | ||
589 | } else { | ||
590 | *pos = -1; | ||
591 | *record_id = APEI_ERST_INVALID_RECORD_ID; | ||
592 | } | ||
593 | out_unlock: | ||
594 | mutex_unlock(&erst_record_id_cache.lock); | ||
462 | 595 | ||
463 | return rc; | 596 | return rc; |
464 | } | 597 | } |
465 | EXPORT_SYMBOL_GPL(erst_get_next_record_id); | 598 | EXPORT_SYMBOL_GPL(erst_get_record_id_next); |
599 | |||
600 | /* erst_record_id_cache.lock must be held by caller */ | ||
601 | static void __erst_record_id_cache_compact(void) | ||
602 | { | ||
603 | int i, wpos = 0; | ||
604 | u64 *entries; | ||
605 | |||
606 | if (erst_record_id_cache.refcount) | ||
607 | return; | ||
608 | |||
609 | entries = erst_record_id_cache.entries; | ||
610 | for (i = 0; i < erst_record_id_cache.len; i++) { | ||
611 | if (entries[i] == APEI_ERST_INVALID_RECORD_ID) | ||
612 | continue; | ||
613 | if (wpos != i) | ||
614 | memcpy(&entries[wpos], &entries[i], sizeof(entries[i])); | ||
615 | wpos++; | ||
616 | } | ||
617 | erst_record_id_cache.len = wpos; | ||
618 | } | ||
619 | |||
620 | void erst_get_record_id_end(void) | ||
621 | { | ||
622 | /* | ||
623 | * erst_disable != 0 should be detected by invoker via the | ||
624 | * return value of erst_get_record_id_begin/next, so this | ||
625 | * function should not be called for erst_disable != 0. | ||
626 | */ | ||
627 | BUG_ON(erst_disable); | ||
628 | |||
629 | mutex_lock(&erst_record_id_cache.lock); | ||
630 | erst_record_id_cache.refcount--; | ||
631 | BUG_ON(erst_record_id_cache.refcount < 0); | ||
632 | __erst_record_id_cache_compact(); | ||
633 | mutex_unlock(&erst_record_id_cache.lock); | ||
634 | } | ||
635 | EXPORT_SYMBOL_GPL(erst_get_record_id_end); | ||
466 | 636 | ||
467 | static int __erst_write_to_storage(u64 offset) | 637 | static int __erst_write_to_storage(u64 offset) |
468 | { | 638 | { |
@@ -624,17 +794,17 @@ int erst_write(const struct cper_record_header *record) | |||
624 | return -EINVAL; | 794 | return -EINVAL; |
625 | 795 | ||
626 | if (erst_erange.attr & ERST_RANGE_NVRAM) { | 796 | if (erst_erange.attr & ERST_RANGE_NVRAM) { |
627 | if (!spin_trylock_irqsave(&erst_lock, flags)) | 797 | if (!raw_spin_trylock_irqsave(&erst_lock, flags)) |
628 | return -EBUSY; | 798 | return -EBUSY; |
629 | rc = __erst_write_to_nvram(record); | 799 | rc = __erst_write_to_nvram(record); |
630 | spin_unlock_irqrestore(&erst_lock, flags); | 800 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
631 | return rc; | 801 | return rc; |
632 | } | 802 | } |
633 | 803 | ||
634 | if (record->record_length > erst_erange.size) | 804 | if (record->record_length > erst_erange.size) |
635 | return -EINVAL; | 805 | return -EINVAL; |
636 | 806 | ||
637 | if (!spin_trylock_irqsave(&erst_lock, flags)) | 807 | if (!raw_spin_trylock_irqsave(&erst_lock, flags)) |
638 | return -EBUSY; | 808 | return -EBUSY; |
639 | memcpy(erst_erange.vaddr, record, record->record_length); | 809 | memcpy(erst_erange.vaddr, record, record->record_length); |
640 | rcd_erange = erst_erange.vaddr; | 810 | rcd_erange = erst_erange.vaddr; |
@@ -642,7 +812,7 @@ int erst_write(const struct cper_record_header *record) | |||
642 | memcpy(&rcd_erange->persistence_information, "ER", 2); | 812 | memcpy(&rcd_erange->persistence_information, "ER", 2); |
643 | 813 | ||
644 | rc = __erst_write_to_storage(0); | 814 | rc = __erst_write_to_storage(0); |
645 | spin_unlock_irqrestore(&erst_lock, flags); | 815 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
646 | 816 | ||
647 | return rc; | 817 | return rc; |
648 | } | 818 | } |
@@ -696,63 +866,41 @@ ssize_t erst_read(u64 record_id, struct cper_record_header *record, | |||
696 | if (erst_disable) | 866 | if (erst_disable) |
697 | return -ENODEV; | 867 | return -ENODEV; |
698 | 868 | ||
699 | spin_lock_irqsave(&erst_lock, flags); | 869 | raw_spin_lock_irqsave(&erst_lock, flags); |
700 | len = __erst_read(record_id, record, buflen); | 870 | len = __erst_read(record_id, record, buflen); |
701 | spin_unlock_irqrestore(&erst_lock, flags); | 871 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
702 | return len; | 872 | return len; |
703 | } | 873 | } |
704 | EXPORT_SYMBOL_GPL(erst_read); | 874 | EXPORT_SYMBOL_GPL(erst_read); |
705 | 875 | ||
706 | /* | ||
707 | * If return value > buflen, the buffer size is not big enough, | ||
708 | * else if return value = 0, there is no more record to read, | ||
709 | * else if return value < 0, something goes wrong, | ||
710 | * else everything is OK, and return value is record length | ||
711 | */ | ||
712 | ssize_t erst_read_next(struct cper_record_header *record, size_t buflen) | ||
713 | { | ||
714 | int rc; | ||
715 | ssize_t len; | ||
716 | unsigned long flags; | ||
717 | u64 record_id; | ||
718 | |||
719 | if (erst_disable) | ||
720 | return -ENODEV; | ||
721 | |||
722 | spin_lock_irqsave(&erst_lock, flags); | ||
723 | rc = __erst_get_next_record_id(&record_id); | ||
724 | if (rc) { | ||
725 | spin_unlock_irqrestore(&erst_lock, flags); | ||
726 | return rc; | ||
727 | } | ||
728 | /* no more record */ | ||
729 | if (record_id == APEI_ERST_INVALID_RECORD_ID) { | ||
730 | spin_unlock_irqrestore(&erst_lock, flags); | ||
731 | return 0; | ||
732 | } | ||
733 | |||
734 | len = __erst_read(record_id, record, buflen); | ||
735 | spin_unlock_irqrestore(&erst_lock, flags); | ||
736 | |||
737 | return len; | ||
738 | } | ||
739 | EXPORT_SYMBOL_GPL(erst_read_next); | ||
740 | |||
741 | int erst_clear(u64 record_id) | 876 | int erst_clear(u64 record_id) |
742 | { | 877 | { |
743 | int rc; | 878 | int rc, i; |
744 | unsigned long flags; | 879 | unsigned long flags; |
880 | u64 *entries; | ||
745 | 881 | ||
746 | if (erst_disable) | 882 | if (erst_disable) |
747 | return -ENODEV; | 883 | return -ENODEV; |
748 | 884 | ||
749 | spin_lock_irqsave(&erst_lock, flags); | 885 | rc = mutex_lock_interruptible(&erst_record_id_cache.lock); |
886 | if (rc) | ||
887 | return rc; | ||
888 | raw_spin_lock_irqsave(&erst_lock, flags); | ||
750 | if (erst_erange.attr & ERST_RANGE_NVRAM) | 889 | if (erst_erange.attr & ERST_RANGE_NVRAM) |
751 | rc = __erst_clear_from_nvram(record_id); | 890 | rc = __erst_clear_from_nvram(record_id); |
752 | else | 891 | else |
753 | rc = __erst_clear_from_storage(record_id); | 892 | rc = __erst_clear_from_storage(record_id); |
754 | spin_unlock_irqrestore(&erst_lock, flags); | 893 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
755 | 894 | if (rc) | |
895 | goto out; | ||
896 | entries = erst_record_id_cache.entries; | ||
897 | for (i = 0; i < erst_record_id_cache.len; i++) { | ||
898 | if (entries[i] == record_id) | ||
899 | entries[i] = APEI_ERST_INVALID_RECORD_ID; | ||
900 | } | ||
901 | __erst_record_id_cache_compact(); | ||
902 | out: | ||
903 | mutex_unlock(&erst_record_id_cache.lock); | ||
756 | return rc; | 904 | return rc; |
757 | } | 905 | } |
758 | EXPORT_SYMBOL_GPL(erst_clear); | 906 | EXPORT_SYMBOL_GPL(erst_clear); |
@@ -781,6 +929,157 @@ static int erst_check_table(struct acpi_table_erst *erst_tab) | |||
781 | return 0; | 929 | return 0; |
782 | } | 930 | } |
783 | 931 | ||
932 | static int erst_open_pstore(struct pstore_info *psi); | ||
933 | static int erst_close_pstore(struct pstore_info *psi); | ||
934 | static ssize_t erst_reader(u64 *id, enum pstore_type_id *type, | ||
935 | struct timespec *time); | ||
936 | static u64 erst_writer(enum pstore_type_id type, size_t size); | ||
937 | |||
938 | static struct pstore_info erst_info = { | ||
939 | .owner = THIS_MODULE, | ||
940 | .name = "erst", | ||
941 | .open = erst_open_pstore, | ||
942 | .close = erst_close_pstore, | ||
943 | .read = erst_reader, | ||
944 | .write = erst_writer, | ||
945 | .erase = erst_clear | ||
946 | }; | ||
947 | |||
948 | #define CPER_CREATOR_PSTORE \ | ||
949 | UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ | ||
950 | 0x64, 0x90, 0xb8, 0x9d) | ||
951 | #define CPER_SECTION_TYPE_DMESG \ | ||
952 | UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \ | ||
953 | 0x94, 0x19, 0xeb, 0x12) | ||
954 | #define CPER_SECTION_TYPE_MCE \ | ||
955 | UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ | ||
956 | 0x04, 0x4a, 0x38, 0xfc) | ||
957 | |||
958 | struct cper_pstore_record { | ||
959 | struct cper_record_header hdr; | ||
960 | struct cper_section_descriptor sec_hdr; | ||
961 | char data[]; | ||
962 | } __packed; | ||
963 | |||
964 | static int reader_pos; | ||
965 | |||
966 | static int erst_open_pstore(struct pstore_info *psi) | ||
967 | { | ||
968 | int rc; | ||
969 | |||
970 | if (erst_disable) | ||
971 | return -ENODEV; | ||
972 | |||
973 | rc = erst_get_record_id_begin(&reader_pos); | ||
974 | |||
975 | return rc; | ||
976 | } | ||
977 | |||
978 | static int erst_close_pstore(struct pstore_info *psi) | ||
979 | { | ||
980 | erst_get_record_id_end(); | ||
981 | |||
982 | return 0; | ||
983 | } | ||
984 | |||
985 | static ssize_t erst_reader(u64 *id, enum pstore_type_id *type, | ||
986 | struct timespec *time) | ||
987 | { | ||
988 | int rc; | ||
989 | ssize_t len = 0; | ||
990 | u64 record_id; | ||
991 | struct cper_pstore_record *rcd = (struct cper_pstore_record *) | ||
992 | (erst_info.buf - sizeof(*rcd)); | ||
993 | |||
994 | if (erst_disable) | ||
995 | return -ENODEV; | ||
996 | |||
997 | skip: | ||
998 | rc = erst_get_record_id_next(&reader_pos, &record_id); | ||
999 | if (rc) | ||
1000 | goto out; | ||
1001 | |||
1002 | /* no more record */ | ||
1003 | if (record_id == APEI_ERST_INVALID_RECORD_ID) { | ||
1004 | rc = -1; | ||
1005 | goto out; | ||
1006 | } | ||
1007 | |||
1008 | len = erst_read(record_id, &rcd->hdr, sizeof(*rcd) + | ||
1009 | erst_info.bufsize); | ||
1010 | /* The record may be cleared by others, try read next record */ | ||
1011 | if (len == -ENOENT) | ||
1012 | goto skip; | ||
1013 | else if (len < 0) { | ||
1014 | rc = -1; | ||
1015 | goto out; | ||
1016 | } | ||
1017 | if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0) | ||
1018 | goto skip; | ||
1019 | |||
1020 | *id = record_id; | ||
1021 | if (uuid_le_cmp(rcd->sec_hdr.section_type, | ||
1022 | CPER_SECTION_TYPE_DMESG) == 0) | ||
1023 | *type = PSTORE_TYPE_DMESG; | ||
1024 | else if (uuid_le_cmp(rcd->sec_hdr.section_type, | ||
1025 | CPER_SECTION_TYPE_MCE) == 0) | ||
1026 | *type = PSTORE_TYPE_MCE; | ||
1027 | else | ||
1028 | *type = PSTORE_TYPE_UNKNOWN; | ||
1029 | |||
1030 | if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP) | ||
1031 | time->tv_sec = rcd->hdr.timestamp; | ||
1032 | else | ||
1033 | time->tv_sec = 0; | ||
1034 | time->tv_nsec = 0; | ||
1035 | |||
1036 | out: | ||
1037 | return (rc < 0) ? rc : (len - sizeof(*rcd)); | ||
1038 | } | ||
1039 | |||
1040 | static u64 erst_writer(enum pstore_type_id type, size_t size) | ||
1041 | { | ||
1042 | struct cper_pstore_record *rcd = (struct cper_pstore_record *) | ||
1043 | (erst_info.buf - sizeof(*rcd)); | ||
1044 | |||
1045 | memset(rcd, 0, sizeof(*rcd)); | ||
1046 | memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE); | ||
1047 | rcd->hdr.revision = CPER_RECORD_REV; | ||
1048 | rcd->hdr.signature_end = CPER_SIG_END; | ||
1049 | rcd->hdr.section_count = 1; | ||
1050 | rcd->hdr.error_severity = CPER_SEV_FATAL; | ||
1051 | /* timestamp valid. platform_id, partition_id are invalid */ | ||
1052 | rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP; | ||
1053 | rcd->hdr.timestamp = get_seconds(); | ||
1054 | rcd->hdr.record_length = sizeof(*rcd) + size; | ||
1055 | rcd->hdr.creator_id = CPER_CREATOR_PSTORE; | ||
1056 | rcd->hdr.notification_type = CPER_NOTIFY_MCE; | ||
1057 | rcd->hdr.record_id = cper_next_record_id(); | ||
1058 | rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR; | ||
1059 | |||
1060 | rcd->sec_hdr.section_offset = sizeof(*rcd); | ||
1061 | rcd->sec_hdr.section_length = size; | ||
1062 | rcd->sec_hdr.revision = CPER_SEC_REV; | ||
1063 | /* fru_id and fru_text is invalid */ | ||
1064 | rcd->sec_hdr.validation_bits = 0; | ||
1065 | rcd->sec_hdr.flags = CPER_SEC_PRIMARY; | ||
1066 | switch (type) { | ||
1067 | case PSTORE_TYPE_DMESG: | ||
1068 | rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG; | ||
1069 | break; | ||
1070 | case PSTORE_TYPE_MCE: | ||
1071 | rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE; | ||
1072 | break; | ||
1073 | default: | ||
1074 | return -EINVAL; | ||
1075 | } | ||
1076 | rcd->sec_hdr.section_severity = CPER_SEV_FATAL; | ||
1077 | |||
1078 | erst_write(&rcd->hdr); | ||
1079 | |||
1080 | return rcd->hdr.record_id; | ||
1081 | } | ||
1082 | |||
784 | static int __init erst_init(void) | 1083 | static int __init erst_init(void) |
785 | { | 1084 | { |
786 | int rc = 0; | 1085 | int rc = 0; |
@@ -788,6 +1087,7 @@ static int __init erst_init(void) | |||
788 | struct apei_exec_context ctx; | 1087 | struct apei_exec_context ctx; |
789 | struct apei_resources erst_resources; | 1088 | struct apei_resources erst_resources; |
790 | struct resource *r; | 1089 | struct resource *r; |
1090 | char *buf; | ||
791 | 1091 | ||
792 | if (acpi_disabled) | 1092 | if (acpi_disabled) |
793 | goto err; | 1093 | goto err; |
@@ -854,6 +1154,18 @@ static int __init erst_init(void) | |||
854 | if (!erst_erange.vaddr) | 1154 | if (!erst_erange.vaddr) |
855 | goto err_release_erange; | 1155 | goto err_release_erange; |
856 | 1156 | ||
1157 | buf = kmalloc(erst_erange.size, GFP_KERNEL); | ||
1158 | mutex_init(&erst_info.buf_mutex); | ||
1159 | if (buf) { | ||
1160 | erst_info.buf = buf + sizeof(struct cper_pstore_record); | ||
1161 | erst_info.bufsize = erst_erange.size - | ||
1162 | sizeof(struct cper_pstore_record); | ||
1163 | if (pstore_register(&erst_info)) { | ||
1164 | pr_info(ERST_PFX "Could not register with persistent store\n"); | ||
1165 | kfree(buf); | ||
1166 | } | ||
1167 | } | ||
1168 | |||
857 | pr_info(ERST_PFX | 1169 | pr_info(ERST_PFX |
858 | "Error Record Serialization Table (ERST) support is initialized.\n"); | 1170 | "Error Record Serialization Table (ERST) support is initialized.\n"); |
859 | 1171 | ||
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 0d505e59214d..f703b2881153 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c | |||
@@ -12,10 +12,6 @@ | |||
12 | * For more information about Generic Hardware Error Source, please | 12 | * For more information about Generic Hardware Error Source, please |
13 | * refer to ACPI Specification version 4.0, section 17.3.2.6 | 13 | * refer to ACPI Specification version 4.0, section 17.3.2.6 |
14 | * | 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. | 15 | * Copyright 2010 Intel Corp. |
20 | * Author: Huang Ying <ying.huang@intel.com> | 16 | * Author: Huang Ying <ying.huang@intel.com> |
21 | * | 17 | * |
@@ -39,14 +35,18 @@ | |||
39 | #include <linux/acpi.h> | 35 | #include <linux/acpi.h> |
40 | #include <linux/io.h> | 36 | #include <linux/io.h> |
41 | #include <linux/interrupt.h> | 37 | #include <linux/interrupt.h> |
38 | #include <linux/timer.h> | ||
42 | #include <linux/cper.h> | 39 | #include <linux/cper.h> |
43 | #include <linux/kdebug.h> | 40 | #include <linux/kdebug.h> |
44 | #include <linux/platform_device.h> | 41 | #include <linux/platform_device.h> |
45 | #include <linux/mutex.h> | 42 | #include <linux/mutex.h> |
43 | #include <linux/ratelimit.h> | ||
44 | #include <linux/vmalloc.h> | ||
46 | #include <acpi/apei.h> | 45 | #include <acpi/apei.h> |
47 | #include <acpi/atomicio.h> | 46 | #include <acpi/atomicio.h> |
48 | #include <acpi/hed.h> | 47 | #include <acpi/hed.h> |
49 | #include <asm/mce.h> | 48 | #include <asm/mce.h> |
49 | #include <asm/tlbflush.h> | ||
50 | 50 | ||
51 | #include "apei-internal.h" | 51 | #include "apei-internal.h" |
52 | 52 | ||
@@ -55,42 +55,131 @@ | |||
55 | #define GHES_ESTATUS_MAX_SIZE 65536 | 55 | #define GHES_ESTATUS_MAX_SIZE 65536 |
56 | 56 | ||
57 | /* | 57 | /* |
58 | * One struct ghes is created for each generic hardware error | 58 | * One struct ghes is created for each generic hardware error source. |
59 | * source. | ||
60 | * | ||
61 | * It provides the context for APEI hardware error timer/IRQ/SCI/NMI | 59 | * It provides the context for APEI hardware error timer/IRQ/SCI/NMI |
62 | * handler. Handler for one generic hardware error source is only | 60 | * handler. |
63 | * triggered after the previous one is done. So handler can uses | ||
64 | * struct ghes without locking. | ||
65 | * | 61 | * |
66 | * estatus: memory buffer for error status block, allocated during | 62 | * estatus: memory buffer for error status block, allocated during |
67 | * HEST parsing. | 63 | * HEST parsing. |
68 | */ | 64 | */ |
69 | #define GHES_TO_CLEAR 0x0001 | 65 | #define GHES_TO_CLEAR 0x0001 |
66 | #define GHES_EXITING 0x0002 | ||
70 | 67 | ||
71 | struct ghes { | 68 | struct ghes { |
72 | struct acpi_hest_generic *generic; | 69 | struct acpi_hest_generic *generic; |
73 | struct acpi_hest_generic_status *estatus; | 70 | struct acpi_hest_generic_status *estatus; |
74 | struct list_head list; | ||
75 | u64 buffer_paddr; | 71 | u64 buffer_paddr; |
76 | unsigned long flags; | 72 | unsigned long flags; |
73 | union { | ||
74 | struct list_head list; | ||
75 | struct timer_list timer; | ||
76 | unsigned int irq; | ||
77 | }; | ||
77 | }; | 78 | }; |
78 | 79 | ||
80 | static int ghes_panic_timeout __read_mostly = 30; | ||
81 | |||
79 | /* | 82 | /* |
80 | * Error source lists, one list for each notification method. The | 83 | * All error sources notified with SCI shares one notifier function, |
81 | * members in lists are struct ghes. | 84 | * so they need to be linked and checked one by one. This is applied |
85 | * to NMI too. | ||
82 | * | 86 | * |
83 | * The list members are only added in HEST parsing and deleted during | 87 | * RCU is used for these lists, so ghes_list_mutex is only used for |
84 | * module_exit, that is, single-threaded. So no lock is needed for | 88 | * list changing, not for traversing. |
85 | * that. | ||
86 | * | ||
87 | * But the mutual exclusion is needed between members adding/deleting | ||
88 | * and timer/IRQ/SCI/NMI handler, which may traverse the list. RCU is | ||
89 | * used for that. | ||
90 | */ | 89 | */ |
91 | static LIST_HEAD(ghes_sci); | 90 | static LIST_HEAD(ghes_sci); |
91 | static LIST_HEAD(ghes_nmi); | ||
92 | static DEFINE_MUTEX(ghes_list_mutex); | 92 | static DEFINE_MUTEX(ghes_list_mutex); |
93 | 93 | ||
94 | /* | ||
95 | * NMI may be triggered on any CPU, so ghes_nmi_lock is used for | ||
96 | * mutual exclusion. | ||
97 | */ | ||
98 | static DEFINE_RAW_SPINLOCK(ghes_nmi_lock); | ||
99 | |||
100 | /* | ||
101 | * Because the memory area used to transfer hardware error information | ||
102 | * from BIOS to Linux can be determined only in NMI, IRQ or timer | ||
103 | * handler, but general ioremap can not be used in atomic context, so | ||
104 | * a special version of atomic ioremap is implemented for that. | ||
105 | */ | ||
106 | |||
107 | /* | ||
108 | * Two virtual pages are used, one for NMI context, the other for | ||
109 | * IRQ/PROCESS context | ||
110 | */ | ||
111 | #define GHES_IOREMAP_PAGES 2 | ||
112 | #define GHES_IOREMAP_NMI_PAGE(base) (base) | ||
113 | #define GHES_IOREMAP_IRQ_PAGE(base) ((base) + PAGE_SIZE) | ||
114 | |||
115 | /* virtual memory area for atomic ioremap */ | ||
116 | static struct vm_struct *ghes_ioremap_area; | ||
117 | /* | ||
118 | * These 2 spinlock is used to prevent atomic ioremap virtual memory | ||
119 | * area from being mapped simultaneously. | ||
120 | */ | ||
121 | static DEFINE_RAW_SPINLOCK(ghes_ioremap_lock_nmi); | ||
122 | static DEFINE_SPINLOCK(ghes_ioremap_lock_irq); | ||
123 | |||
124 | static int ghes_ioremap_init(void) | ||
125 | { | ||
126 | ghes_ioremap_area = __get_vm_area(PAGE_SIZE * GHES_IOREMAP_PAGES, | ||
127 | VM_IOREMAP, VMALLOC_START, VMALLOC_END); | ||
128 | if (!ghes_ioremap_area) { | ||
129 | pr_err(GHES_PFX "Failed to allocate virtual memory area for atomic ioremap.\n"); | ||
130 | return -ENOMEM; | ||
131 | } | ||
132 | |||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static void ghes_ioremap_exit(void) | ||
137 | { | ||
138 | free_vm_area(ghes_ioremap_area); | ||
139 | } | ||
140 | |||
141 | static void __iomem *ghes_ioremap_pfn_nmi(u64 pfn) | ||
142 | { | ||
143 | unsigned long vaddr; | ||
144 | |||
145 | vaddr = (unsigned long)GHES_IOREMAP_NMI_PAGE(ghes_ioremap_area->addr); | ||
146 | ioremap_page_range(vaddr, vaddr + PAGE_SIZE, | ||
147 | pfn << PAGE_SHIFT, PAGE_KERNEL); | ||
148 | |||
149 | return (void __iomem *)vaddr; | ||
150 | } | ||
151 | |||
152 | static void __iomem *ghes_ioremap_pfn_irq(u64 pfn) | ||
153 | { | ||
154 | unsigned long vaddr; | ||
155 | |||
156 | vaddr = (unsigned long)GHES_IOREMAP_IRQ_PAGE(ghes_ioremap_area->addr); | ||
157 | ioremap_page_range(vaddr, vaddr + PAGE_SIZE, | ||
158 | pfn << PAGE_SHIFT, PAGE_KERNEL); | ||
159 | |||
160 | return (void __iomem *)vaddr; | ||
161 | } | ||
162 | |||
163 | static void ghes_iounmap_nmi(void __iomem *vaddr_ptr) | ||
164 | { | ||
165 | unsigned long vaddr = (unsigned long __force)vaddr_ptr; | ||
166 | void *base = ghes_ioremap_area->addr; | ||
167 | |||
168 | BUG_ON(vaddr != (unsigned long)GHES_IOREMAP_NMI_PAGE(base)); | ||
169 | unmap_kernel_range_noflush(vaddr, PAGE_SIZE); | ||
170 | __flush_tlb_one(vaddr); | ||
171 | } | ||
172 | |||
173 | static void ghes_iounmap_irq(void __iomem *vaddr_ptr) | ||
174 | { | ||
175 | unsigned long vaddr = (unsigned long __force)vaddr_ptr; | ||
176 | void *base = ghes_ioremap_area->addr; | ||
177 | |||
178 | BUG_ON(vaddr != (unsigned long)GHES_IOREMAP_IRQ_PAGE(base)); | ||
179 | unmap_kernel_range_noflush(vaddr, PAGE_SIZE); | ||
180 | __flush_tlb_one(vaddr); | ||
181 | } | ||
182 | |||
94 | static struct ghes *ghes_new(struct acpi_hest_generic *generic) | 183 | static struct ghes *ghes_new(struct acpi_hest_generic *generic) |
95 | { | 184 | { |
96 | struct ghes *ghes; | 185 | struct ghes *ghes; |
@@ -101,7 +190,6 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic) | |||
101 | if (!ghes) | 190 | if (!ghes) |
102 | return ERR_PTR(-ENOMEM); | 191 | return ERR_PTR(-ENOMEM); |
103 | ghes->generic = generic; | 192 | ghes->generic = generic; |
104 | INIT_LIST_HEAD(&ghes->list); | ||
105 | rc = acpi_pre_map_gar(&generic->error_status_address); | 193 | rc = acpi_pre_map_gar(&generic->error_status_address); |
106 | if (rc) | 194 | if (rc) |
107 | goto err_free; | 195 | goto err_free; |
@@ -153,27 +241,46 @@ static inline int ghes_severity(int severity) | |||
153 | case CPER_SEV_FATAL: | 241 | case CPER_SEV_FATAL: |
154 | return GHES_SEV_PANIC; | 242 | return GHES_SEV_PANIC; |
155 | default: | 243 | default: |
156 | /* Unkown, go panic */ | 244 | /* Unknown, go panic */ |
157 | return GHES_SEV_PANIC; | 245 | return GHES_SEV_PANIC; |
158 | } | 246 | } |
159 | } | 247 | } |
160 | 248 | ||
161 | /* SCI handler run in work queue, so ioremap can be used here */ | 249 | static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, |
162 | static int ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, | 250 | int from_phys) |
163 | int from_phys) | ||
164 | { | 251 | { |
165 | void *vaddr; | 252 | void __iomem *vaddr; |
166 | 253 | unsigned long flags = 0; | |
167 | vaddr = ioremap_cache(paddr, len); | 254 | int in_nmi = in_nmi(); |
168 | if (!vaddr) | 255 | u64 offset; |
169 | return -ENOMEM; | 256 | u32 trunk; |
170 | if (from_phys) | 257 | |
171 | memcpy(buffer, vaddr, len); | 258 | while (len > 0) { |
172 | else | 259 | offset = paddr - (paddr & PAGE_MASK); |
173 | memcpy(vaddr, buffer, len); | 260 | if (in_nmi) { |
174 | iounmap(vaddr); | 261 | raw_spin_lock(&ghes_ioremap_lock_nmi); |
175 | 262 | vaddr = ghes_ioremap_pfn_nmi(paddr >> PAGE_SHIFT); | |
176 | return 0; | 263 | } else { |
264 | spin_lock_irqsave(&ghes_ioremap_lock_irq, flags); | ||
265 | vaddr = ghes_ioremap_pfn_irq(paddr >> PAGE_SHIFT); | ||
266 | } | ||
267 | trunk = PAGE_SIZE - offset; | ||
268 | trunk = min(trunk, len); | ||
269 | if (from_phys) | ||
270 | memcpy_fromio(buffer, vaddr + offset, trunk); | ||
271 | else | ||
272 | memcpy_toio(vaddr + offset, buffer, trunk); | ||
273 | len -= trunk; | ||
274 | paddr += trunk; | ||
275 | buffer += trunk; | ||
276 | if (in_nmi) { | ||
277 | ghes_iounmap_nmi(vaddr); | ||
278 | raw_spin_unlock(&ghes_ioremap_lock_nmi); | ||
279 | } else { | ||
280 | ghes_iounmap_irq(vaddr); | ||
281 | spin_unlock_irqrestore(&ghes_ioremap_lock_irq, flags); | ||
282 | } | ||
283 | } | ||
177 | } | 284 | } |
178 | 285 | ||
179 | static int ghes_read_estatus(struct ghes *ghes, int silent) | 286 | static int ghes_read_estatus(struct ghes *ghes, int silent) |
@@ -194,10 +301,8 @@ static int ghes_read_estatus(struct ghes *ghes, int silent) | |||
194 | if (!buf_paddr) | 301 | if (!buf_paddr) |
195 | return -ENOENT; | 302 | return -ENOENT; |
196 | 303 | ||
197 | rc = ghes_copy_tofrom_phys(ghes->estatus, buf_paddr, | 304 | ghes_copy_tofrom_phys(ghes->estatus, buf_paddr, |
198 | sizeof(*ghes->estatus), 1); | 305 | sizeof(*ghes->estatus), 1); |
199 | if (rc) | ||
200 | return rc; | ||
201 | if (!ghes->estatus->block_status) | 306 | if (!ghes->estatus->block_status) |
202 | return -ENOENT; | 307 | return -ENOENT; |
203 | 308 | ||
@@ -212,17 +317,15 @@ static int ghes_read_estatus(struct ghes *ghes, int silent) | |||
212 | goto err_read_block; | 317 | goto err_read_block; |
213 | if (apei_estatus_check_header(ghes->estatus)) | 318 | if (apei_estatus_check_header(ghes->estatus)) |
214 | goto err_read_block; | 319 | goto err_read_block; |
215 | rc = ghes_copy_tofrom_phys(ghes->estatus + 1, | 320 | ghes_copy_tofrom_phys(ghes->estatus + 1, |
216 | buf_paddr + sizeof(*ghes->estatus), | 321 | buf_paddr + sizeof(*ghes->estatus), |
217 | len - sizeof(*ghes->estatus), 1); | 322 | len - sizeof(*ghes->estatus), 1); |
218 | if (rc) | ||
219 | return rc; | ||
220 | if (apei_estatus_check(ghes->estatus)) | 323 | if (apei_estatus_check(ghes->estatus)) |
221 | goto err_read_block; | 324 | goto err_read_block; |
222 | rc = 0; | 325 | rc = 0; |
223 | 326 | ||
224 | err_read_block: | 327 | err_read_block: |
225 | if (rc && !silent) | 328 | if (rc && !silent && printk_ratelimit()) |
226 | pr_warning(FW_WARN GHES_PFX | 329 | pr_warning(FW_WARN GHES_PFX |
227 | "Failed to read error status block!\n"); | 330 | "Failed to read error status block!\n"); |
228 | return rc; | 331 | return rc; |
@@ -255,11 +358,26 @@ static void ghes_do_proc(struct ghes *ghes) | |||
255 | } | 358 | } |
256 | #endif | 359 | #endif |
257 | } | 360 | } |
361 | } | ||
258 | 362 | ||
259 | if (!processed && printk_ratelimit()) | 363 | static void ghes_print_estatus(const char *pfx, struct ghes *ghes) |
260 | pr_warning(GHES_PFX | 364 | { |
261 | "Unknown error record from generic hardware error source: %d\n", | 365 | /* Not more than 2 messages every 5 seconds */ |
262 | ghes->generic->header.source_id); | 366 | static DEFINE_RATELIMIT_STATE(ratelimit, 5*HZ, 2); |
367 | |||
368 | if (pfx == NULL) { | ||
369 | if (ghes_severity(ghes->estatus->error_severity) <= | ||
370 | GHES_SEV_CORRECTED) | ||
371 | pfx = KERN_WARNING HW_ERR; | ||
372 | else | ||
373 | pfx = KERN_ERR HW_ERR; | ||
374 | } | ||
375 | if (__ratelimit(&ratelimit)) { | ||
376 | printk( | ||
377 | "%s""Hardware error from APEI Generic Hardware Error Source: %d\n", | ||
378 | pfx, ghes->generic->header.source_id); | ||
379 | apei_estatus_print(pfx, ghes->estatus); | ||
380 | } | ||
263 | } | 381 | } |
264 | 382 | ||
265 | static int ghes_proc(struct ghes *ghes) | 383 | static int ghes_proc(struct ghes *ghes) |
@@ -269,6 +387,7 @@ static int ghes_proc(struct ghes *ghes) | |||
269 | rc = ghes_read_estatus(ghes, 0); | 387 | rc = ghes_read_estatus(ghes, 0); |
270 | if (rc) | 388 | if (rc) |
271 | goto out; | 389 | goto out; |
390 | ghes_print_estatus(NULL, ghes); | ||
272 | ghes_do_proc(ghes); | 391 | ghes_do_proc(ghes); |
273 | 392 | ||
274 | out: | 393 | out: |
@@ -276,6 +395,42 @@ out: | |||
276 | return 0; | 395 | return 0; |
277 | } | 396 | } |
278 | 397 | ||
398 | static void ghes_add_timer(struct ghes *ghes) | ||
399 | { | ||
400 | struct acpi_hest_generic *g = ghes->generic; | ||
401 | unsigned long expire; | ||
402 | |||
403 | if (!g->notify.poll_interval) { | ||
404 | pr_warning(FW_WARN GHES_PFX "Poll interval is 0 for generic hardware error source: %d, disabled.\n", | ||
405 | g->header.source_id); | ||
406 | return; | ||
407 | } | ||
408 | expire = jiffies + msecs_to_jiffies(g->notify.poll_interval); | ||
409 | ghes->timer.expires = round_jiffies_relative(expire); | ||
410 | add_timer(&ghes->timer); | ||
411 | } | ||
412 | |||
413 | static void ghes_poll_func(unsigned long data) | ||
414 | { | ||
415 | struct ghes *ghes = (void *)data; | ||
416 | |||
417 | ghes_proc(ghes); | ||
418 | if (!(ghes->flags & GHES_EXITING)) | ||
419 | ghes_add_timer(ghes); | ||
420 | } | ||
421 | |||
422 | static irqreturn_t ghes_irq_func(int irq, void *data) | ||
423 | { | ||
424 | struct ghes *ghes = data; | ||
425 | int rc; | ||
426 | |||
427 | rc = ghes_proc(ghes); | ||
428 | if (rc) | ||
429 | return IRQ_NONE; | ||
430 | |||
431 | return IRQ_HANDLED; | ||
432 | } | ||
433 | |||
279 | static int ghes_notify_sci(struct notifier_block *this, | 434 | static int ghes_notify_sci(struct notifier_block *this, |
280 | unsigned long event, void *data) | 435 | unsigned long event, void *data) |
281 | { | 436 | { |
@@ -292,10 +447,63 @@ static int ghes_notify_sci(struct notifier_block *this, | |||
292 | return ret; | 447 | return ret; |
293 | } | 448 | } |
294 | 449 | ||
450 | static int ghes_notify_nmi(struct notifier_block *this, | ||
451 | unsigned long cmd, void *data) | ||
452 | { | ||
453 | struct ghes *ghes, *ghes_global = NULL; | ||
454 | int sev, sev_global = -1; | ||
455 | int ret = NOTIFY_DONE; | ||
456 | |||
457 | if (cmd != DIE_NMI) | ||
458 | return ret; | ||
459 | |||
460 | raw_spin_lock(&ghes_nmi_lock); | ||
461 | list_for_each_entry_rcu(ghes, &ghes_nmi, list) { | ||
462 | if (ghes_read_estatus(ghes, 1)) { | ||
463 | ghes_clear_estatus(ghes); | ||
464 | continue; | ||
465 | } | ||
466 | sev = ghes_severity(ghes->estatus->error_severity); | ||
467 | if (sev > sev_global) { | ||
468 | sev_global = sev; | ||
469 | ghes_global = ghes; | ||
470 | } | ||
471 | ret = NOTIFY_STOP; | ||
472 | } | ||
473 | |||
474 | if (ret == NOTIFY_DONE) | ||
475 | goto out; | ||
476 | |||
477 | if (sev_global >= GHES_SEV_PANIC) { | ||
478 | oops_begin(); | ||
479 | ghes_print_estatus(KERN_EMERG HW_ERR, ghes_global); | ||
480 | /* reboot to log the error! */ | ||
481 | if (panic_timeout == 0) | ||
482 | panic_timeout = ghes_panic_timeout; | ||
483 | panic("Fatal hardware error!"); | ||
484 | } | ||
485 | |||
486 | list_for_each_entry_rcu(ghes, &ghes_nmi, list) { | ||
487 | if (!(ghes->flags & GHES_TO_CLEAR)) | ||
488 | continue; | ||
489 | /* Do not print estatus because printk is not NMI safe */ | ||
490 | ghes_do_proc(ghes); | ||
491 | ghes_clear_estatus(ghes); | ||
492 | } | ||
493 | |||
494 | out: | ||
495 | raw_spin_unlock(&ghes_nmi_lock); | ||
496 | return ret; | ||
497 | } | ||
498 | |||
295 | static struct notifier_block ghes_notifier_sci = { | 499 | static struct notifier_block ghes_notifier_sci = { |
296 | .notifier_call = ghes_notify_sci, | 500 | .notifier_call = ghes_notify_sci, |
297 | }; | 501 | }; |
298 | 502 | ||
503 | static struct notifier_block ghes_notifier_nmi = { | ||
504 | .notifier_call = ghes_notify_nmi, | ||
505 | }; | ||
506 | |||
299 | static int __devinit ghes_probe(struct platform_device *ghes_dev) | 507 | static int __devinit ghes_probe(struct platform_device *ghes_dev) |
300 | { | 508 | { |
301 | struct acpi_hest_generic *generic; | 509 | struct acpi_hest_generic *generic; |
@@ -306,18 +514,27 @@ static int __devinit ghes_probe(struct platform_device *ghes_dev) | |||
306 | if (!generic->enabled) | 514 | if (!generic->enabled) |
307 | return -ENODEV; | 515 | return -ENODEV; |
308 | 516 | ||
309 | if (generic->error_block_length < | 517 | switch (generic->notify.type) { |
310 | sizeof(struct acpi_hest_generic_status)) { | 518 | case ACPI_HEST_NOTIFY_POLLED: |
311 | pr_warning(FW_BUG GHES_PFX | 519 | case ACPI_HEST_NOTIFY_EXTERNAL: |
312 | "Invalid error block length: %u for generic hardware error source: %d\n", | 520 | case ACPI_HEST_NOTIFY_SCI: |
313 | generic->error_block_length, | 521 | case ACPI_HEST_NOTIFY_NMI: |
522 | break; | ||
523 | case ACPI_HEST_NOTIFY_LOCAL: | ||
524 | pr_warning(GHES_PFX "Generic hardware error source: %d notified via local interrupt is not supported!\n", | ||
314 | generic->header.source_id); | 525 | generic->header.source_id); |
315 | goto err; | 526 | goto err; |
527 | default: | ||
528 | pr_warning(FW_WARN GHES_PFX "Unknown notification type: %u for generic hardware error source: %d\n", | ||
529 | generic->notify.type, generic->header.source_id); | ||
530 | goto err; | ||
316 | } | 531 | } |
317 | if (generic->records_to_preallocate == 0) { | 532 | |
318 | pr_warning(FW_BUG GHES_PFX | 533 | rc = -EIO; |
319 | "Invalid records to preallocate: %u for generic hardware error source: %d\n", | 534 | if (generic->error_block_length < |
320 | generic->records_to_preallocate, | 535 | sizeof(struct acpi_hest_generic_status)) { |
536 | pr_warning(FW_BUG GHES_PFX "Invalid error block length: %u for generic hardware error source: %d\n", | ||
537 | generic->error_block_length, | ||
321 | generic->header.source_id); | 538 | generic->header.source_id); |
322 | goto err; | 539 | goto err; |
323 | } | 540 | } |
@@ -327,38 +544,43 @@ static int __devinit ghes_probe(struct platform_device *ghes_dev) | |||
327 | ghes = NULL; | 544 | ghes = NULL; |
328 | goto err; | 545 | goto err; |
329 | } | 546 | } |
330 | if (generic->notify.type == ACPI_HEST_NOTIFY_SCI) { | 547 | switch (generic->notify.type) { |
548 | case ACPI_HEST_NOTIFY_POLLED: | ||
549 | ghes->timer.function = ghes_poll_func; | ||
550 | ghes->timer.data = (unsigned long)ghes; | ||
551 | init_timer_deferrable(&ghes->timer); | ||
552 | ghes_add_timer(ghes); | ||
553 | break; | ||
554 | case ACPI_HEST_NOTIFY_EXTERNAL: | ||
555 | /* External interrupt vector is GSI */ | ||
556 | if (acpi_gsi_to_irq(generic->notify.vector, &ghes->irq)) { | ||
557 | pr_err(GHES_PFX "Failed to map GSI to IRQ for generic hardware error source: %d\n", | ||
558 | generic->header.source_id); | ||
559 | goto err; | ||
560 | } | ||
561 | if (request_irq(ghes->irq, ghes_irq_func, | ||
562 | 0, "GHES IRQ", ghes)) { | ||
563 | pr_err(GHES_PFX "Failed to register IRQ for generic hardware error source: %d\n", | ||
564 | generic->header.source_id); | ||
565 | goto err; | ||
566 | } | ||
567 | break; | ||
568 | case ACPI_HEST_NOTIFY_SCI: | ||
331 | mutex_lock(&ghes_list_mutex); | 569 | mutex_lock(&ghes_list_mutex); |
332 | if (list_empty(&ghes_sci)) | 570 | if (list_empty(&ghes_sci)) |
333 | register_acpi_hed_notifier(&ghes_notifier_sci); | 571 | register_acpi_hed_notifier(&ghes_notifier_sci); |
334 | list_add_rcu(&ghes->list, &ghes_sci); | 572 | list_add_rcu(&ghes->list, &ghes_sci); |
335 | mutex_unlock(&ghes_list_mutex); | 573 | mutex_unlock(&ghes_list_mutex); |
336 | } else { | 574 | break; |
337 | unsigned char *notify = NULL; | 575 | case ACPI_HEST_NOTIFY_NMI: |
338 | 576 | mutex_lock(&ghes_list_mutex); | |
339 | switch (generic->notify.type) { | 577 | if (list_empty(&ghes_nmi)) |
340 | case ACPI_HEST_NOTIFY_POLLED: | 578 | register_die_notifier(&ghes_notifier_nmi); |
341 | notify = "POLL"; | 579 | list_add_rcu(&ghes->list, &ghes_nmi); |
342 | break; | 580 | mutex_unlock(&ghes_list_mutex); |
343 | case ACPI_HEST_NOTIFY_EXTERNAL: | 581 | break; |
344 | case ACPI_HEST_NOTIFY_LOCAL: | 582 | default: |
345 | notify = "IRQ"; | 583 | BUG(); |
346 | break; | ||
347 | case ACPI_HEST_NOTIFY_NMI: | ||
348 | notify = "NMI"; | ||
349 | break; | ||
350 | } | ||
351 | if (notify) { | ||
352 | pr_warning(GHES_PFX | ||
353 | "Generic hardware error source: %d notified via %s is not supported!\n", | ||
354 | generic->header.source_id, notify); | ||
355 | } else { | ||
356 | pr_warning(FW_WARN GHES_PFX | ||
357 | "Unknown notification type: %u for generic hardware error source: %d\n", | ||
358 | generic->notify.type, generic->header.source_id); | ||
359 | } | ||
360 | rc = -ENODEV; | ||
361 | goto err; | ||
362 | } | 584 | } |
363 | platform_set_drvdata(ghes_dev, ghes); | 585 | platform_set_drvdata(ghes_dev, ghes); |
364 | 586 | ||
@@ -379,7 +601,14 @@ static int __devexit ghes_remove(struct platform_device *ghes_dev) | |||
379 | ghes = platform_get_drvdata(ghes_dev); | 601 | ghes = platform_get_drvdata(ghes_dev); |
380 | generic = ghes->generic; | 602 | generic = ghes->generic; |
381 | 603 | ||
604 | ghes->flags |= GHES_EXITING; | ||
382 | switch (generic->notify.type) { | 605 | switch (generic->notify.type) { |
606 | case ACPI_HEST_NOTIFY_POLLED: | ||
607 | del_timer_sync(&ghes->timer); | ||
608 | break; | ||
609 | case ACPI_HEST_NOTIFY_EXTERNAL: | ||
610 | free_irq(ghes->irq, ghes); | ||
611 | break; | ||
383 | case ACPI_HEST_NOTIFY_SCI: | 612 | case ACPI_HEST_NOTIFY_SCI: |
384 | mutex_lock(&ghes_list_mutex); | 613 | mutex_lock(&ghes_list_mutex); |
385 | list_del_rcu(&ghes->list); | 614 | list_del_rcu(&ghes->list); |
@@ -387,12 +616,23 @@ static int __devexit ghes_remove(struct platform_device *ghes_dev) | |||
387 | unregister_acpi_hed_notifier(&ghes_notifier_sci); | 616 | unregister_acpi_hed_notifier(&ghes_notifier_sci); |
388 | mutex_unlock(&ghes_list_mutex); | 617 | mutex_unlock(&ghes_list_mutex); |
389 | break; | 618 | break; |
619 | case ACPI_HEST_NOTIFY_NMI: | ||
620 | mutex_lock(&ghes_list_mutex); | ||
621 | list_del_rcu(&ghes->list); | ||
622 | if (list_empty(&ghes_nmi)) | ||
623 | unregister_die_notifier(&ghes_notifier_nmi); | ||
624 | mutex_unlock(&ghes_list_mutex); | ||
625 | /* | ||
626 | * To synchronize with NMI handler, ghes can only be | ||
627 | * freed after NMI handler finishes. | ||
628 | */ | ||
629 | synchronize_rcu(); | ||
630 | break; | ||
390 | default: | 631 | default: |
391 | BUG(); | 632 | BUG(); |
392 | break; | 633 | break; |
393 | } | 634 | } |
394 | 635 | ||
395 | synchronize_rcu(); | ||
396 | ghes_fini(ghes); | 636 | ghes_fini(ghes); |
397 | kfree(ghes); | 637 | kfree(ghes); |
398 | 638 | ||
@@ -412,6 +652,8 @@ static struct platform_driver ghes_platform_driver = { | |||
412 | 652 | ||
413 | static int __init ghes_init(void) | 653 | static int __init ghes_init(void) |
414 | { | 654 | { |
655 | int rc; | ||
656 | |||
415 | if (acpi_disabled) | 657 | if (acpi_disabled) |
416 | return -ENODEV; | 658 | return -ENODEV; |
417 | 659 | ||
@@ -420,12 +662,25 @@ static int __init ghes_init(void) | |||
420 | return -EINVAL; | 662 | return -EINVAL; |
421 | } | 663 | } |
422 | 664 | ||
423 | return platform_driver_register(&ghes_platform_driver); | 665 | rc = ghes_ioremap_init(); |
666 | if (rc) | ||
667 | goto err; | ||
668 | |||
669 | rc = platform_driver_register(&ghes_platform_driver); | ||
670 | if (rc) | ||
671 | goto err_ioremap_exit; | ||
672 | |||
673 | return 0; | ||
674 | err_ioremap_exit: | ||
675 | ghes_ioremap_exit(); | ||
676 | err: | ||
677 | return rc; | ||
424 | } | 678 | } |
425 | 679 | ||
426 | static void __exit ghes_exit(void) | 680 | static void __exit ghes_exit(void) |
427 | { | 681 | { |
428 | platform_driver_unregister(&ghes_platform_driver); | 682 | platform_driver_unregister(&ghes_platform_driver); |
683 | ghes_ioremap_exit(); | ||
429 | } | 684 | } |
430 | 685 | ||
431 | module_init(ghes_init); | 686 | module_init(ghes_init); |
diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c index 1a3508a7fe03..181bc2f7bb74 100644 --- a/drivers/acpi/apei/hest.c +++ b/drivers/acpi/apei/hest.c | |||
@@ -46,9 +46,9 @@ EXPORT_SYMBOL_GPL(hest_disable); | |||
46 | 46 | ||
47 | /* HEST table parsing */ | 47 | /* HEST table parsing */ |
48 | 48 | ||
49 | static struct acpi_table_hest *hest_tab; | 49 | static struct acpi_table_hest *__read_mostly hest_tab; |
50 | 50 | ||
51 | static int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { | 51 | static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { |
52 | [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ | 52 | [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ |
53 | [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, | 53 | [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, |
54 | [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi), | 54 | [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi), |
@@ -126,7 +126,7 @@ struct ghes_arr { | |||
126 | unsigned int count; | 126 | unsigned int count; |
127 | }; | 127 | }; |
128 | 128 | ||
129 | static int hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) | 129 | static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) |
130 | { | 130 | { |
131 | int *count = data; | 131 | int *count = data; |
132 | 132 | ||
@@ -135,17 +135,27 @@ static int hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data) | |||
135 | return 0; | 135 | return 0; |
136 | } | 136 | } |
137 | 137 | ||
138 | static int hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) | 138 | static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) |
139 | { | 139 | { |
140 | struct platform_device *ghes_dev; | 140 | struct platform_device *ghes_dev; |
141 | struct ghes_arr *ghes_arr = data; | 141 | struct ghes_arr *ghes_arr = data; |
142 | int rc; | 142 | int rc, i; |
143 | 143 | ||
144 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) | 144 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) |
145 | return 0; | 145 | return 0; |
146 | 146 | ||
147 | if (!((struct acpi_hest_generic *)hest_hdr)->enabled) | 147 | if (!((struct acpi_hest_generic *)hest_hdr)->enabled) |
148 | return 0; | 148 | return 0; |
149 | for (i = 0; i < ghes_arr->count; i++) { | ||
150 | struct acpi_hest_header *hdr; | ||
151 | ghes_dev = ghes_arr->ghes_devs[i]; | ||
152 | hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data; | ||
153 | if (hdr->source_id == hest_hdr->source_id) { | ||
154 | pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n", | ||
155 | hdr->source_id); | ||
156 | return -EIO; | ||
157 | } | ||
158 | } | ||
149 | ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id); | 159 | ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id); |
150 | if (!ghes_dev) | 160 | if (!ghes_dev) |
151 | return -ENOMEM; | 161 | return -ENOMEM; |
@@ -165,7 +175,7 @@ err: | |||
165 | return rc; | 175 | return rc; |
166 | } | 176 | } |
167 | 177 | ||
168 | static int hest_ghes_dev_register(unsigned int ghes_count) | 178 | static int __init hest_ghes_dev_register(unsigned int ghes_count) |
169 | { | 179 | { |
170 | int rc, i; | 180 | int rc, i; |
171 | struct ghes_arr ghes_arr; | 181 | struct ghes_arr ghes_arr; |
@@ -195,24 +205,24 @@ static int __init setup_hest_disable(char *str) | |||
195 | 205 | ||
196 | __setup("hest_disable", setup_hest_disable); | 206 | __setup("hest_disable", setup_hest_disable); |
197 | 207 | ||
198 | static int __init hest_init(void) | 208 | void __init acpi_hest_init(void) |
199 | { | 209 | { |
200 | acpi_status status; | 210 | acpi_status status; |
201 | int rc = -ENODEV; | 211 | int rc = -ENODEV; |
202 | unsigned int ghes_count = 0; | 212 | unsigned int ghes_count = 0; |
203 | 213 | ||
204 | if (acpi_disabled) | ||
205 | goto err; | ||
206 | |||
207 | if (hest_disable) { | 214 | if (hest_disable) { |
208 | pr_info(HEST_PFX "HEST tabling parsing is disabled.\n"); | 215 | pr_info(HEST_PFX "Table parsing disabled.\n"); |
209 | goto err; | 216 | return; |
210 | } | 217 | } |
211 | 218 | ||
219 | if (acpi_disabled) | ||
220 | goto err; | ||
221 | |||
212 | status = acpi_get_table(ACPI_SIG_HEST, 0, | 222 | status = acpi_get_table(ACPI_SIG_HEST, 0, |
213 | (struct acpi_table_header **)&hest_tab); | 223 | (struct acpi_table_header **)&hest_tab); |
214 | if (status == AE_NOT_FOUND) { | 224 | if (status == AE_NOT_FOUND) { |
215 | pr_info(HEST_PFX "Table is not found!\n"); | 225 | pr_info(HEST_PFX "Table not found.\n"); |
216 | goto err; | 226 | goto err; |
217 | } else if (ACPI_FAILURE(status)) { | 227 | } else if (ACPI_FAILURE(status)) { |
218 | const char *msg = acpi_format_exception(status); | 228 | const char *msg = acpi_format_exception(status); |
@@ -226,15 +236,11 @@ static int __init hest_init(void) | |||
226 | goto err; | 236 | goto err; |
227 | 237 | ||
228 | rc = hest_ghes_dev_register(ghes_count); | 238 | rc = hest_ghes_dev_register(ghes_count); |
229 | if (rc) | 239 | if (!rc) { |
230 | goto err; | 240 | pr_info(HEST_PFX "Table parsing has been initialized.\n"); |
231 | 241 | return; | |
232 | pr_info(HEST_PFX "HEST table parsing is initialized.\n"); | 242 | } |
233 | 243 | ||
234 | return 0; | ||
235 | err: | 244 | err: |
236 | hest_disable = 1; | 245 | hest_disable = 1; |
237 | return rc; | ||
238 | } | 246 | } |
239 | |||
240 | subsys_initcall(hest_init); | ||