1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * APEI Error Record Serialization Table support |
4 | * |
5 | * ERST is a way provided by APEI to save and retrieve hardware error |
6 | * information to and from a persistent store. |
7 | * |
8 | * For more information about ERST, please refer to ACPI Specification |
9 | * version 4.0, section 17.4. |
10 | * |
11 | * Copyright 2010 Intel Corp. |
12 | * Author: Huang Ying <ying.huang@intel.com> |
13 | */ |
14 | |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/init.h> |
18 | #include <linux/delay.h> |
19 | #include <linux/io.h> |
20 | #include <linux/acpi.h> |
21 | #include <linux/uaccess.h> |
22 | #include <linux/cper.h> |
23 | #include <linux/nmi.h> |
24 | #include <linux/hardirq.h> |
25 | #include <linux/pstore.h> |
26 | #include <linux/vmalloc.h> |
27 | #include <linux/mm.h> /* kvfree() */ |
28 | #include <acpi/apei.h> |
29 | |
30 | #include "apei-internal.h" |
31 | |
32 | #undef pr_fmt |
33 | #define pr_fmt(fmt) "ERST: " fmt |
34 | |
35 | /* ERST command status */ |
36 | #define ERST_STATUS_SUCCESS 0x0 |
37 | #define ERST_STATUS_NOT_ENOUGH_SPACE 0x1 |
38 | #define ERST_STATUS_HARDWARE_NOT_AVAILABLE 0x2 |
39 | #define ERST_STATUS_FAILED 0x3 |
40 | #define ERST_STATUS_RECORD_STORE_EMPTY 0x4 |
41 | #define ERST_STATUS_RECORD_NOT_FOUND 0x5 |
42 | |
43 | #define ERST_TAB_ENTRY(tab) \ |
44 | ((struct acpi_whea_header *)((char *)(tab) + \ |
45 | sizeof(struct acpi_table_erst))) |
46 | |
47 | #define SPIN_UNIT 100 /* 100ns */ |
48 | /* Firmware should respond within 1 milliseconds */ |
49 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) |
50 | #define FIRMWARE_MAX_STALL 50 /* 50us */ |
51 | |
52 | int erst_disable; |
53 | EXPORT_SYMBOL_GPL(erst_disable); |
54 | |
55 | static struct acpi_table_erst *erst_tab; |
56 | |
57 | /* ERST Error Log Address Range attributes */ |
58 | #define ERST_RANGE_RESERVED 0x0001 |
59 | #define ERST_RANGE_NVRAM 0x0002 |
60 | #define ERST_RANGE_SLOW 0x0004 |
61 | |
62 | /* ERST Exec max timings */ |
63 | #define ERST_EXEC_TIMING_MAX_MASK 0xFFFFFFFF00000000 |
64 | #define ERST_EXEC_TIMING_MAX_SHIFT 32 |
65 | |
66 | /* |
67 | * ERST Error Log Address Range, used as buffer for reading/writing |
68 | * error records. |
69 | */ |
70 | static struct erst_erange { |
71 | u64 base; |
72 | u64 size; |
73 | void __iomem *vaddr; |
74 | u32 attr; |
75 | u64 timings; |
76 | } erst_erange; |
77 | |
78 | /* |
79 | * Prevent ERST interpreter to run simultaneously, because the |
80 | * corresponding firmware implementation may not work properly when |
81 | * invoked simultaneously. |
82 | * |
83 | * It is used to provide exclusive accessing for ERST Error Log |
84 | * Address Range too. |
85 | */ |
86 | static DEFINE_RAW_SPINLOCK(erst_lock); |
87 | |
88 | static inline int erst_errno(int command_status) |
89 | { |
90 | switch (command_status) { |
91 | case ERST_STATUS_SUCCESS: |
92 | return 0; |
93 | case ERST_STATUS_HARDWARE_NOT_AVAILABLE: |
94 | return -ENODEV; |
95 | case ERST_STATUS_NOT_ENOUGH_SPACE: |
96 | return -ENOSPC; |
97 | case ERST_STATUS_RECORD_STORE_EMPTY: |
98 | case ERST_STATUS_RECORD_NOT_FOUND: |
99 | return -ENOENT; |
100 | default: |
101 | return -EINVAL; |
102 | } |
103 | } |
104 | |
105 | static inline u64 erst_get_timeout(void) |
106 | { |
107 | u64 timeout = FIRMWARE_TIMEOUT; |
108 | |
109 | if (erst_erange.attr & ERST_RANGE_SLOW) { |
110 | timeout = ((erst_erange.timings & ERST_EXEC_TIMING_MAX_MASK) >> |
111 | ERST_EXEC_TIMING_MAX_SHIFT) * NSEC_PER_MSEC; |
112 | if (timeout < FIRMWARE_TIMEOUT) |
113 | timeout = FIRMWARE_TIMEOUT; |
114 | } |
115 | return timeout; |
116 | } |
117 | |
118 | static int erst_timedout(u64 *t, u64 spin_unit) |
119 | { |
120 | if ((s64)*t < spin_unit) { |
121 | pr_warn(FW_WARN "Firmware does not respond in time.\n" ); |
122 | return 1; |
123 | } |
124 | *t -= spin_unit; |
125 | ndelay(spin_unit); |
126 | touch_nmi_watchdog(); |
127 | return 0; |
128 | } |
129 | |
130 | static int erst_exec_load_var1(struct apei_exec_context *ctx, |
131 | struct acpi_whea_header *entry) |
132 | { |
133 | return __apei_exec_read_register(entry, val: &ctx->var1); |
134 | } |
135 | |
136 | static int erst_exec_load_var2(struct apei_exec_context *ctx, |
137 | struct acpi_whea_header *entry) |
138 | { |
139 | return __apei_exec_read_register(entry, val: &ctx->var2); |
140 | } |
141 | |
142 | static int erst_exec_store_var1(struct apei_exec_context *ctx, |
143 | struct acpi_whea_header *entry) |
144 | { |
145 | return __apei_exec_write_register(entry, val: ctx->var1); |
146 | } |
147 | |
148 | static int erst_exec_add(struct apei_exec_context *ctx, |
149 | struct acpi_whea_header *entry) |
150 | { |
151 | ctx->var1 += ctx->var2; |
152 | return 0; |
153 | } |
154 | |
155 | static int erst_exec_subtract(struct apei_exec_context *ctx, |
156 | struct acpi_whea_header *entry) |
157 | { |
158 | ctx->var1 -= ctx->var2; |
159 | return 0; |
160 | } |
161 | |
162 | static int erst_exec_add_value(struct apei_exec_context *ctx, |
163 | struct acpi_whea_header *entry) |
164 | { |
165 | int rc; |
166 | u64 val; |
167 | |
168 | rc = __apei_exec_read_register(entry, val: &val); |
169 | if (rc) |
170 | return rc; |
171 | val += ctx->value; |
172 | rc = __apei_exec_write_register(entry, val); |
173 | return rc; |
174 | } |
175 | |
176 | static int erst_exec_subtract_value(struct apei_exec_context *ctx, |
177 | struct acpi_whea_header *entry) |
178 | { |
179 | int rc; |
180 | u64 val; |
181 | |
182 | rc = __apei_exec_read_register(entry, val: &val); |
183 | if (rc) |
184 | return rc; |
185 | val -= ctx->value; |
186 | rc = __apei_exec_write_register(entry, val); |
187 | return rc; |
188 | } |
189 | |
190 | static int erst_exec_stall(struct apei_exec_context *ctx, |
191 | struct acpi_whea_header *entry) |
192 | { |
193 | u64 stall_time; |
194 | |
195 | if (ctx->value > FIRMWARE_MAX_STALL) { |
196 | if (!in_nmi()) |
197 | pr_warn(FW_WARN |
198 | "Too long stall time for stall instruction: 0x%llx.\n" , |
199 | ctx->value); |
200 | stall_time = FIRMWARE_MAX_STALL; |
201 | } else |
202 | stall_time = ctx->value; |
203 | udelay(stall_time); |
204 | return 0; |
205 | } |
206 | |
207 | static int erst_exec_stall_while_true(struct apei_exec_context *ctx, |
208 | struct acpi_whea_header *entry) |
209 | { |
210 | int rc; |
211 | u64 val; |
212 | u64 timeout; |
213 | u64 stall_time; |
214 | |
215 | timeout = erst_get_timeout(); |
216 | |
217 | if (ctx->var1 > FIRMWARE_MAX_STALL) { |
218 | if (!in_nmi()) |
219 | pr_warn(FW_WARN |
220 | "Too long stall time for stall while true instruction: 0x%llx.\n" , |
221 | ctx->var1); |
222 | stall_time = FIRMWARE_MAX_STALL; |
223 | } else |
224 | stall_time = ctx->var1; |
225 | |
226 | for (;;) { |
227 | rc = __apei_exec_read_register(entry, val: &val); |
228 | if (rc) |
229 | return rc; |
230 | if (val != ctx->value) |
231 | break; |
232 | if (erst_timedout(t: &timeout, spin_unit: stall_time * NSEC_PER_USEC)) |
233 | return -EIO; |
234 | } |
235 | return 0; |
236 | } |
237 | |
238 | static int erst_exec_skip_next_instruction_if_true( |
239 | struct apei_exec_context *ctx, |
240 | struct acpi_whea_header *entry) |
241 | { |
242 | int rc; |
243 | u64 val; |
244 | |
245 | rc = __apei_exec_read_register(entry, val: &val); |
246 | if (rc) |
247 | return rc; |
248 | if (val == ctx->value) { |
249 | ctx->ip += 2; |
250 | return APEI_EXEC_SET_IP; |
251 | } |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int erst_exec_goto(struct apei_exec_context *ctx, |
257 | struct acpi_whea_header *entry) |
258 | { |
259 | ctx->ip = ctx->value; |
260 | return APEI_EXEC_SET_IP; |
261 | } |
262 | |
263 | static int erst_exec_set_src_address_base(struct apei_exec_context *ctx, |
264 | struct acpi_whea_header *entry) |
265 | { |
266 | return __apei_exec_read_register(entry, val: &ctx->src_base); |
267 | } |
268 | |
269 | static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx, |
270 | struct acpi_whea_header *entry) |
271 | { |
272 | return __apei_exec_read_register(entry, val: &ctx->dst_base); |
273 | } |
274 | |
275 | static int erst_exec_move_data(struct apei_exec_context *ctx, |
276 | struct acpi_whea_header *entry) |
277 | { |
278 | int rc; |
279 | u64 offset; |
280 | void *src, *dst; |
281 | |
282 | /* ioremap does not work in interrupt context */ |
283 | if (in_interrupt()) { |
284 | pr_warn("MOVE_DATA can not be used in interrupt context.\n" ); |
285 | return -EBUSY; |
286 | } |
287 | |
288 | rc = __apei_exec_read_register(entry, val: &offset); |
289 | if (rc) |
290 | return rc; |
291 | |
292 | src = ioremap(offset: ctx->src_base + offset, size: ctx->var2); |
293 | if (!src) |
294 | return -ENOMEM; |
295 | dst = ioremap(offset: ctx->dst_base + offset, size: ctx->var2); |
296 | if (!dst) { |
297 | iounmap(addr: src); |
298 | return -ENOMEM; |
299 | } |
300 | |
301 | memmove(dst, src, ctx->var2); |
302 | |
303 | iounmap(addr: src); |
304 | iounmap(addr: dst); |
305 | |
306 | return 0; |
307 | } |
308 | |
309 | static struct apei_exec_ins_type erst_ins_type[] = { |
310 | [ACPI_ERST_READ_REGISTER] = { |
311 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
312 | .run = apei_exec_read_register, |
313 | }, |
314 | [ACPI_ERST_READ_REGISTER_VALUE] = { |
315 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
316 | .run = apei_exec_read_register_value, |
317 | }, |
318 | [ACPI_ERST_WRITE_REGISTER] = { |
319 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
320 | .run = apei_exec_write_register, |
321 | }, |
322 | [ACPI_ERST_WRITE_REGISTER_VALUE] = { |
323 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
324 | .run = apei_exec_write_register_value, |
325 | }, |
326 | [ACPI_ERST_NOOP] = { |
327 | .flags = 0, |
328 | .run = apei_exec_noop, |
329 | }, |
330 | [ACPI_ERST_LOAD_VAR1] = { |
331 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
332 | .run = erst_exec_load_var1, |
333 | }, |
334 | [ACPI_ERST_LOAD_VAR2] = { |
335 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
336 | .run = erst_exec_load_var2, |
337 | }, |
338 | [ACPI_ERST_STORE_VAR1] = { |
339 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
340 | .run = erst_exec_store_var1, |
341 | }, |
342 | [ACPI_ERST_ADD] = { |
343 | .flags = 0, |
344 | .run = erst_exec_add, |
345 | }, |
346 | [ACPI_ERST_SUBTRACT] = { |
347 | .flags = 0, |
348 | .run = erst_exec_subtract, |
349 | }, |
350 | [ACPI_ERST_ADD_VALUE] = { |
351 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
352 | .run = erst_exec_add_value, |
353 | }, |
354 | [ACPI_ERST_SUBTRACT_VALUE] = { |
355 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
356 | .run = erst_exec_subtract_value, |
357 | }, |
358 | [ACPI_ERST_STALL] = { |
359 | .flags = 0, |
360 | .run = erst_exec_stall, |
361 | }, |
362 | [ACPI_ERST_STALL_WHILE_TRUE] = { |
363 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
364 | .run = erst_exec_stall_while_true, |
365 | }, |
366 | [ACPI_ERST_SKIP_NEXT_IF_TRUE] = { |
367 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
368 | .run = erst_exec_skip_next_instruction_if_true, |
369 | }, |
370 | [ACPI_ERST_GOTO] = { |
371 | .flags = 0, |
372 | .run = erst_exec_goto, |
373 | }, |
374 | [ACPI_ERST_SET_SRC_ADDRESS_BASE] = { |
375 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
376 | .run = erst_exec_set_src_address_base, |
377 | }, |
378 | [ACPI_ERST_SET_DST_ADDRESS_BASE] = { |
379 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
380 | .run = erst_exec_set_dst_address_base, |
381 | }, |
382 | [ACPI_ERST_MOVE_DATA] = { |
383 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, |
384 | .run = erst_exec_move_data, |
385 | }, |
386 | }; |
387 | |
388 | static inline void erst_exec_ctx_init(struct apei_exec_context *ctx) |
389 | { |
390 | apei_exec_ctx_init(ctx, ins_table: erst_ins_type, ARRAY_SIZE(erst_ins_type), |
391 | ERST_TAB_ENTRY(erst_tab), entries: erst_tab->entries); |
392 | } |
393 | |
394 | static int erst_get_erange(struct erst_erange *range) |
395 | { |
396 | struct apei_exec_context ctx; |
397 | int rc; |
398 | |
399 | erst_exec_ctx_init(ctx: &ctx); |
400 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_ERROR_RANGE); |
401 | if (rc) |
402 | return rc; |
403 | range->base = apei_exec_ctx_get_output(ctx: &ctx); |
404 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_ERROR_LENGTH); |
405 | if (rc) |
406 | return rc; |
407 | range->size = apei_exec_ctx_get_output(ctx: &ctx); |
408 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_ERROR_ATTRIBUTES); |
409 | if (rc) |
410 | return rc; |
411 | range->attr = apei_exec_ctx_get_output(ctx: &ctx); |
412 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_EXECUTE_TIMINGS); |
413 | if (rc == 0) |
414 | range->timings = apei_exec_ctx_get_output(ctx: &ctx); |
415 | else if (rc == -ENOENT) |
416 | range->timings = 0; |
417 | else |
418 | return rc; |
419 | |
420 | return 0; |
421 | } |
422 | |
423 | static ssize_t __erst_get_record_count(void) |
424 | { |
425 | struct apei_exec_context ctx; |
426 | int rc; |
427 | |
428 | erst_exec_ctx_init(ctx: &ctx); |
429 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_RECORD_COUNT); |
430 | if (rc) |
431 | return rc; |
432 | return apei_exec_ctx_get_output(ctx: &ctx); |
433 | } |
434 | |
435 | ssize_t erst_get_record_count(void) |
436 | { |
437 | ssize_t count; |
438 | unsigned long flags; |
439 | |
440 | if (erst_disable) |
441 | return -ENODEV; |
442 | |
443 | raw_spin_lock_irqsave(&erst_lock, flags); |
444 | count = __erst_get_record_count(); |
445 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
446 | |
447 | return count; |
448 | } |
449 | EXPORT_SYMBOL_GPL(erst_get_record_count); |
450 | |
451 | #define ERST_RECORD_ID_CACHE_SIZE_MIN 16 |
452 | #define ERST_RECORD_ID_CACHE_SIZE_MAX 1024 |
453 | |
454 | struct erst_record_id_cache { |
455 | struct mutex lock; |
456 | u64 *entries; |
457 | int len; |
458 | int size; |
459 | int refcount; |
460 | }; |
461 | |
462 | static struct erst_record_id_cache erst_record_id_cache = { |
463 | .lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock), |
464 | .refcount = 0, |
465 | }; |
466 | |
467 | static int __erst_get_next_record_id(u64 *record_id) |
468 | { |
469 | struct apei_exec_context ctx; |
470 | int rc; |
471 | |
472 | erst_exec_ctx_init(ctx: &ctx); |
473 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_RECORD_ID); |
474 | if (rc) |
475 | return rc; |
476 | *record_id = apei_exec_ctx_get_output(ctx: &ctx); |
477 | |
478 | return 0; |
479 | } |
480 | |
481 | int erst_get_record_id_begin(int *pos) |
482 | { |
483 | int rc; |
484 | |
485 | if (erst_disable) |
486 | return -ENODEV; |
487 | |
488 | rc = mutex_lock_interruptible(&erst_record_id_cache.lock); |
489 | if (rc) |
490 | return rc; |
491 | erst_record_id_cache.refcount++; |
492 | mutex_unlock(lock: &erst_record_id_cache.lock); |
493 | |
494 | *pos = 0; |
495 | |
496 | return 0; |
497 | } |
498 | EXPORT_SYMBOL_GPL(erst_get_record_id_begin); |
499 | |
500 | /* erst_record_id_cache.lock must be held by caller */ |
501 | static int __erst_record_id_cache_add_one(void) |
502 | { |
503 | u64 id, prev_id, first_id; |
504 | int i, rc; |
505 | u64 *entries; |
506 | unsigned long flags; |
507 | |
508 | id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID; |
509 | retry: |
510 | raw_spin_lock_irqsave(&erst_lock, flags); |
511 | rc = __erst_get_next_record_id(record_id: &id); |
512 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
513 | if (rc == -ENOENT) |
514 | return 0; |
515 | if (rc) |
516 | return rc; |
517 | if (id == APEI_ERST_INVALID_RECORD_ID) |
518 | return 0; |
519 | /* can not skip current ID, or loop back to first ID */ |
520 | if (id == prev_id || id == first_id) |
521 | return 0; |
522 | if (first_id == APEI_ERST_INVALID_RECORD_ID) |
523 | first_id = id; |
524 | prev_id = id; |
525 | |
526 | entries = erst_record_id_cache.entries; |
527 | for (i = 0; i < erst_record_id_cache.len; i++) { |
528 | if (entries[i] == id) |
529 | break; |
530 | } |
531 | /* record id already in cache, try next */ |
532 | if (i < erst_record_id_cache.len) |
533 | goto retry; |
534 | if (erst_record_id_cache.len >= erst_record_id_cache.size) { |
535 | int new_size; |
536 | u64 *new_entries; |
537 | |
538 | new_size = erst_record_id_cache.size * 2; |
539 | new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN, |
540 | ERST_RECORD_ID_CACHE_SIZE_MAX); |
541 | if (new_size <= erst_record_id_cache.size) { |
542 | if (printk_ratelimit()) |
543 | pr_warn(FW_WARN "too many record IDs!\n" ); |
544 | return 0; |
545 | } |
546 | new_entries = kvmalloc_array(n: new_size, size: sizeof(entries[0]), |
547 | GFP_KERNEL); |
548 | if (!new_entries) |
549 | return -ENOMEM; |
550 | memcpy(new_entries, entries, |
551 | erst_record_id_cache.len * sizeof(entries[0])); |
552 | kvfree(addr: entries); |
553 | erst_record_id_cache.entries = entries = new_entries; |
554 | erst_record_id_cache.size = new_size; |
555 | } |
556 | entries[i] = id; |
557 | erst_record_id_cache.len++; |
558 | |
559 | return 1; |
560 | } |
561 | |
562 | /* |
563 | * Get the record ID of an existing error record on the persistent |
564 | * storage. If there is no error record on the persistent storage, the |
565 | * returned record_id is APEI_ERST_INVALID_RECORD_ID. |
566 | */ |
567 | int erst_get_record_id_next(int *pos, u64 *record_id) |
568 | { |
569 | int rc = 0; |
570 | u64 *entries; |
571 | |
572 | if (erst_disable) |
573 | return -ENODEV; |
574 | |
575 | /* must be enclosed by erst_get_record_id_begin/end */ |
576 | BUG_ON(!erst_record_id_cache.refcount); |
577 | BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len); |
578 | |
579 | mutex_lock(&erst_record_id_cache.lock); |
580 | entries = erst_record_id_cache.entries; |
581 | for (; *pos < erst_record_id_cache.len; (*pos)++) |
582 | if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID) |
583 | break; |
584 | /* found next record id in cache */ |
585 | if (*pos < erst_record_id_cache.len) { |
586 | *record_id = entries[*pos]; |
587 | (*pos)++; |
588 | goto out_unlock; |
589 | } |
590 | |
591 | /* Try to add one more record ID to cache */ |
592 | rc = __erst_record_id_cache_add_one(); |
593 | if (rc < 0) |
594 | goto out_unlock; |
595 | /* successfully add one new ID */ |
596 | if (rc == 1) { |
597 | *record_id = erst_record_id_cache.entries[*pos]; |
598 | (*pos)++; |
599 | rc = 0; |
600 | } else { |
601 | *pos = -1; |
602 | *record_id = APEI_ERST_INVALID_RECORD_ID; |
603 | } |
604 | out_unlock: |
605 | mutex_unlock(lock: &erst_record_id_cache.lock); |
606 | |
607 | return rc; |
608 | } |
609 | EXPORT_SYMBOL_GPL(erst_get_record_id_next); |
610 | |
611 | /* erst_record_id_cache.lock must be held by caller */ |
612 | static void __erst_record_id_cache_compact(void) |
613 | { |
614 | int i, wpos = 0; |
615 | u64 *entries; |
616 | |
617 | if (erst_record_id_cache.refcount) |
618 | return; |
619 | |
620 | entries = erst_record_id_cache.entries; |
621 | for (i = 0; i < erst_record_id_cache.len; i++) { |
622 | if (entries[i] == APEI_ERST_INVALID_RECORD_ID) |
623 | continue; |
624 | if (wpos != i) |
625 | entries[wpos] = entries[i]; |
626 | wpos++; |
627 | } |
628 | erst_record_id_cache.len = wpos; |
629 | } |
630 | |
631 | void erst_get_record_id_end(void) |
632 | { |
633 | /* |
634 | * erst_disable != 0 should be detected by invoker via the |
635 | * return value of erst_get_record_id_begin/next, so this |
636 | * function should not be called for erst_disable != 0. |
637 | */ |
638 | BUG_ON(erst_disable); |
639 | |
640 | mutex_lock(&erst_record_id_cache.lock); |
641 | erst_record_id_cache.refcount--; |
642 | BUG_ON(erst_record_id_cache.refcount < 0); |
643 | __erst_record_id_cache_compact(); |
644 | mutex_unlock(lock: &erst_record_id_cache.lock); |
645 | } |
646 | EXPORT_SYMBOL_GPL(erst_get_record_id_end); |
647 | |
648 | static int __erst_write_to_storage(u64 offset) |
649 | { |
650 | struct apei_exec_context ctx; |
651 | u64 timeout; |
652 | u64 val; |
653 | int rc; |
654 | |
655 | timeout = erst_get_timeout(); |
656 | |
657 | erst_exec_ctx_init(ctx: &ctx); |
658 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_BEGIN_WRITE); |
659 | if (rc) |
660 | return rc; |
661 | apei_exec_ctx_set_input(ctx: &ctx, input: offset); |
662 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_SET_RECORD_OFFSET); |
663 | if (rc) |
664 | return rc; |
665 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_EXECUTE_OPERATION); |
666 | if (rc) |
667 | return rc; |
668 | for (;;) { |
669 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_CHECK_BUSY_STATUS); |
670 | if (rc) |
671 | return rc; |
672 | val = apei_exec_ctx_get_output(ctx: &ctx); |
673 | if (!val) |
674 | break; |
675 | if (erst_timedout(t: &timeout, SPIN_UNIT)) |
676 | return -EIO; |
677 | } |
678 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_COMMAND_STATUS); |
679 | if (rc) |
680 | return rc; |
681 | val = apei_exec_ctx_get_output(ctx: &ctx); |
682 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_END); |
683 | if (rc) |
684 | return rc; |
685 | |
686 | return erst_errno(command_status: val); |
687 | } |
688 | |
689 | static int __erst_read_from_storage(u64 record_id, u64 offset) |
690 | { |
691 | struct apei_exec_context ctx; |
692 | u64 timeout; |
693 | u64 val; |
694 | int rc; |
695 | |
696 | timeout = erst_get_timeout(); |
697 | |
698 | erst_exec_ctx_init(ctx: &ctx); |
699 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_BEGIN_READ); |
700 | if (rc) |
701 | return rc; |
702 | apei_exec_ctx_set_input(ctx: &ctx, input: offset); |
703 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_SET_RECORD_OFFSET); |
704 | if (rc) |
705 | return rc; |
706 | apei_exec_ctx_set_input(ctx: &ctx, input: record_id); |
707 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_SET_RECORD_ID); |
708 | if (rc) |
709 | return rc; |
710 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_EXECUTE_OPERATION); |
711 | if (rc) |
712 | return rc; |
713 | for (;;) { |
714 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_CHECK_BUSY_STATUS); |
715 | if (rc) |
716 | return rc; |
717 | val = apei_exec_ctx_get_output(ctx: &ctx); |
718 | if (!val) |
719 | break; |
720 | if (erst_timedout(t: &timeout, SPIN_UNIT)) |
721 | return -EIO; |
722 | } |
723 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_COMMAND_STATUS); |
724 | if (rc) |
725 | return rc; |
726 | val = apei_exec_ctx_get_output(ctx: &ctx); |
727 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_END); |
728 | if (rc) |
729 | return rc; |
730 | |
731 | return erst_errno(command_status: val); |
732 | } |
733 | |
734 | static int __erst_clear_from_storage(u64 record_id) |
735 | { |
736 | struct apei_exec_context ctx; |
737 | u64 timeout; |
738 | u64 val; |
739 | int rc; |
740 | |
741 | timeout = erst_get_timeout(); |
742 | |
743 | erst_exec_ctx_init(ctx: &ctx); |
744 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_BEGIN_CLEAR); |
745 | if (rc) |
746 | return rc; |
747 | apei_exec_ctx_set_input(ctx: &ctx, input: record_id); |
748 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_SET_RECORD_ID); |
749 | if (rc) |
750 | return rc; |
751 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_EXECUTE_OPERATION); |
752 | if (rc) |
753 | return rc; |
754 | for (;;) { |
755 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_CHECK_BUSY_STATUS); |
756 | if (rc) |
757 | return rc; |
758 | val = apei_exec_ctx_get_output(ctx: &ctx); |
759 | if (!val) |
760 | break; |
761 | if (erst_timedout(t: &timeout, SPIN_UNIT)) |
762 | return -EIO; |
763 | } |
764 | rc = apei_exec_run(ctx: &ctx, action: ACPI_ERST_GET_COMMAND_STATUS); |
765 | if (rc) |
766 | return rc; |
767 | val = apei_exec_ctx_get_output(ctx: &ctx); |
768 | rc = apei_exec_run_optional(ctx: &ctx, action: ACPI_ERST_END); |
769 | if (rc) |
770 | return rc; |
771 | |
772 | return erst_errno(command_status: val); |
773 | } |
774 | |
775 | /* NVRAM ERST Error Log Address Range is not supported yet */ |
776 | static void pr_unimpl_nvram(void) |
777 | { |
778 | if (printk_ratelimit()) |
779 | pr_warn("NVRAM ERST Log Address Range not implemented yet.\n" ); |
780 | } |
781 | |
782 | static int __erst_write_to_nvram(const struct cper_record_header *record) |
783 | { |
784 | /* do not print message, because printk is not safe for NMI */ |
785 | return -ENOSYS; |
786 | } |
787 | |
788 | static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset) |
789 | { |
790 | pr_unimpl_nvram(); |
791 | return -ENOSYS; |
792 | } |
793 | |
794 | static int __erst_clear_from_nvram(u64 record_id) |
795 | { |
796 | pr_unimpl_nvram(); |
797 | return -ENOSYS; |
798 | } |
799 | |
800 | int erst_write(const struct cper_record_header *record) |
801 | { |
802 | int rc; |
803 | unsigned long flags; |
804 | struct cper_record_header *rcd_erange; |
805 | |
806 | if (erst_disable) |
807 | return -ENODEV; |
808 | |
809 | if (memcmp(p: record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE)) |
810 | return -EINVAL; |
811 | |
812 | if (erst_erange.attr & ERST_RANGE_NVRAM) { |
813 | if (!raw_spin_trylock_irqsave(&erst_lock, flags)) |
814 | return -EBUSY; |
815 | rc = __erst_write_to_nvram(record); |
816 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
817 | return rc; |
818 | } |
819 | |
820 | if (record->record_length > erst_erange.size) |
821 | return -EINVAL; |
822 | |
823 | if (!raw_spin_trylock_irqsave(&erst_lock, flags)) |
824 | return -EBUSY; |
825 | memcpy(erst_erange.vaddr, record, record->record_length); |
826 | rcd_erange = erst_erange.vaddr; |
827 | /* signature for serialization system */ |
828 | memcpy(&rcd_erange->persistence_information, "ER" , 2); |
829 | |
830 | rc = __erst_write_to_storage(offset: 0); |
831 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
832 | |
833 | return rc; |
834 | } |
835 | EXPORT_SYMBOL_GPL(erst_write); |
836 | |
837 | static int __erst_read_to_erange(u64 record_id, u64 *offset) |
838 | { |
839 | int rc; |
840 | |
841 | if (erst_erange.attr & ERST_RANGE_NVRAM) |
842 | return __erst_read_to_erange_from_nvram( |
843 | record_id, offset); |
844 | |
845 | rc = __erst_read_from_storage(record_id, offset: 0); |
846 | if (rc) |
847 | return rc; |
848 | *offset = 0; |
849 | |
850 | return 0; |
851 | } |
852 | |
853 | static ssize_t __erst_read(u64 record_id, struct cper_record_header *record, |
854 | size_t buflen) |
855 | { |
856 | int rc; |
857 | u64 offset, len = 0; |
858 | struct cper_record_header *rcd_tmp; |
859 | |
860 | rc = __erst_read_to_erange(record_id, offset: &offset); |
861 | if (rc) |
862 | return rc; |
863 | rcd_tmp = erst_erange.vaddr + offset; |
864 | len = rcd_tmp->record_length; |
865 | if (len <= buflen) |
866 | memcpy(record, rcd_tmp, len); |
867 | |
868 | return len; |
869 | } |
870 | |
871 | /* |
872 | * If return value > buflen, the buffer size is not big enough, |
873 | * else if return value < 0, something goes wrong, |
874 | * else everything is OK, and return value is record length |
875 | */ |
876 | ssize_t erst_read(u64 record_id, struct cper_record_header *record, |
877 | size_t buflen) |
878 | { |
879 | ssize_t len; |
880 | unsigned long flags; |
881 | |
882 | if (erst_disable) |
883 | return -ENODEV; |
884 | |
885 | raw_spin_lock_irqsave(&erst_lock, flags); |
886 | len = __erst_read(record_id, record, buflen); |
887 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
888 | return len; |
889 | } |
890 | EXPORT_SYMBOL_GPL(erst_read); |
891 | |
892 | static void erst_clear_cache(u64 record_id) |
893 | { |
894 | int i; |
895 | u64 *entries; |
896 | |
897 | mutex_lock(&erst_record_id_cache.lock); |
898 | |
899 | entries = erst_record_id_cache.entries; |
900 | for (i = 0; i < erst_record_id_cache.len; i++) { |
901 | if (entries[i] == record_id) |
902 | entries[i] = APEI_ERST_INVALID_RECORD_ID; |
903 | } |
904 | __erst_record_id_cache_compact(); |
905 | |
906 | mutex_unlock(lock: &erst_record_id_cache.lock); |
907 | } |
908 | |
909 | ssize_t erst_read_record(u64 record_id, struct cper_record_header *record, |
910 | size_t buflen, size_t recordlen, const guid_t *creatorid) |
911 | { |
912 | ssize_t len; |
913 | |
914 | /* |
915 | * if creatorid is NULL, read any record for erst-dbg module |
916 | */ |
917 | if (creatorid == NULL) { |
918 | len = erst_read(record_id, record, buflen); |
919 | if (len == -ENOENT) |
920 | erst_clear_cache(record_id); |
921 | |
922 | return len; |
923 | } |
924 | |
925 | len = erst_read(record_id, record, buflen); |
926 | /* |
927 | * if erst_read return value is -ENOENT skip to next record_id, |
928 | * and clear the record_id cache. |
929 | */ |
930 | if (len == -ENOENT) { |
931 | erst_clear_cache(record_id); |
932 | goto out; |
933 | } |
934 | |
935 | if (len < 0) |
936 | goto out; |
937 | |
938 | /* |
939 | * if erst_read return value is less than record head length, |
940 | * consider it as -EIO, and clear the record_id cache. |
941 | */ |
942 | if (len < recordlen) { |
943 | len = -EIO; |
944 | erst_clear_cache(record_id); |
945 | goto out; |
946 | } |
947 | |
948 | /* |
949 | * if creatorid is not wanted, consider it as not found, |
950 | * for skipping to next record_id. |
951 | */ |
952 | if (!guid_equal(u1: &record->creator_id, u2: creatorid)) |
953 | len = -ENOENT; |
954 | |
955 | out: |
956 | return len; |
957 | } |
958 | EXPORT_SYMBOL_GPL(erst_read_record); |
959 | |
960 | int erst_clear(u64 record_id) |
961 | { |
962 | int rc, i; |
963 | unsigned long flags; |
964 | u64 *entries; |
965 | |
966 | if (erst_disable) |
967 | return -ENODEV; |
968 | |
969 | rc = mutex_lock_interruptible(&erst_record_id_cache.lock); |
970 | if (rc) |
971 | return rc; |
972 | raw_spin_lock_irqsave(&erst_lock, flags); |
973 | if (erst_erange.attr & ERST_RANGE_NVRAM) |
974 | rc = __erst_clear_from_nvram(record_id); |
975 | else |
976 | rc = __erst_clear_from_storage(record_id); |
977 | raw_spin_unlock_irqrestore(&erst_lock, flags); |
978 | if (rc) |
979 | goto out; |
980 | entries = erst_record_id_cache.entries; |
981 | for (i = 0; i < erst_record_id_cache.len; i++) { |
982 | if (entries[i] == record_id) |
983 | entries[i] = APEI_ERST_INVALID_RECORD_ID; |
984 | } |
985 | __erst_record_id_cache_compact(); |
986 | out: |
987 | mutex_unlock(lock: &erst_record_id_cache.lock); |
988 | return rc; |
989 | } |
990 | EXPORT_SYMBOL_GPL(erst_clear); |
991 | |
992 | static int __init setup_erst_disable(char *str) |
993 | { |
994 | erst_disable = 1; |
995 | return 1; |
996 | } |
997 | |
998 | __setup("erst_disable" , setup_erst_disable); |
999 | |
1000 | static int erst_check_table(struct acpi_table_erst *erst_tab) |
1001 | { |
1002 | if ((erst_tab->header_length != |
1003 | (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header))) |
1004 | && (erst_tab->header_length != sizeof(struct acpi_table_erst))) |
1005 | return -EINVAL; |
1006 | if (erst_tab->header.length < sizeof(struct acpi_table_erst)) |
1007 | return -EINVAL; |
1008 | if (erst_tab->entries != |
1009 | (erst_tab->header.length - sizeof(struct acpi_table_erst)) / |
1010 | sizeof(struct acpi_erst_entry)) |
1011 | return -EINVAL; |
1012 | |
1013 | return 0; |
1014 | } |
1015 | |
1016 | static int erst_open_pstore(struct pstore_info *psi); |
1017 | static int erst_close_pstore(struct pstore_info *psi); |
1018 | static ssize_t erst_reader(struct pstore_record *record); |
1019 | static int erst_writer(struct pstore_record *record); |
1020 | static int erst_clearer(struct pstore_record *record); |
1021 | |
1022 | static struct pstore_info erst_info = { |
1023 | .owner = THIS_MODULE, |
1024 | .name = "erst" , |
1025 | .flags = PSTORE_FLAGS_DMESG, |
1026 | .open = erst_open_pstore, |
1027 | .close = erst_close_pstore, |
1028 | .read = erst_reader, |
1029 | .write = erst_writer, |
1030 | .erase = erst_clearer |
1031 | }; |
1032 | |
1033 | #define CPER_CREATOR_PSTORE \ |
1034 | GUID_INIT(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ |
1035 | 0x64, 0x90, 0xb8, 0x9d) |
1036 | #define CPER_SECTION_TYPE_DMESG \ |
1037 | GUID_INIT(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \ |
1038 | 0x94, 0x19, 0xeb, 0x12) |
1039 | #define CPER_SECTION_TYPE_DMESG_Z \ |
1040 | GUID_INIT(0x4f118707, 0x04dd, 0x4055, 0xb5, 0xdd, 0x95, 0x6d, \ |
1041 | 0x34, 0xdd, 0xfa, 0xc6) |
1042 | #define CPER_SECTION_TYPE_MCE \ |
1043 | GUID_INIT(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ |
1044 | 0x04, 0x4a, 0x38, 0xfc) |
1045 | |
1046 | struct cper_pstore_record { |
1047 | struct cper_record_header hdr; |
1048 | struct cper_section_descriptor sec_hdr; |
1049 | char data[]; |
1050 | } __packed; |
1051 | |
1052 | static int reader_pos; |
1053 | |
1054 | static int erst_open_pstore(struct pstore_info *psi) |
1055 | { |
1056 | if (erst_disable) |
1057 | return -ENODEV; |
1058 | |
1059 | return erst_get_record_id_begin(&reader_pos); |
1060 | } |
1061 | |
1062 | static int erst_close_pstore(struct pstore_info *psi) |
1063 | { |
1064 | erst_get_record_id_end(); |
1065 | |
1066 | return 0; |
1067 | } |
1068 | |
1069 | static ssize_t erst_reader(struct pstore_record *record) |
1070 | { |
1071 | int rc; |
1072 | ssize_t len = 0; |
1073 | u64 record_id; |
1074 | struct cper_pstore_record *rcd; |
1075 | size_t rcd_len = sizeof(*rcd) + erst_info.bufsize; |
1076 | |
1077 | if (erst_disable) |
1078 | return -ENODEV; |
1079 | |
1080 | rcd = kmalloc(size: rcd_len, GFP_KERNEL); |
1081 | if (!rcd) { |
1082 | rc = -ENOMEM; |
1083 | goto out; |
1084 | } |
1085 | skip: |
1086 | rc = erst_get_record_id_next(&reader_pos, &record_id); |
1087 | if (rc) |
1088 | goto out; |
1089 | |
1090 | /* no more record */ |
1091 | if (record_id == APEI_ERST_INVALID_RECORD_ID) { |
1092 | rc = -EINVAL; |
1093 | goto out; |
1094 | } |
1095 | |
1096 | len = erst_read_record(record_id, &rcd->hdr, rcd_len, sizeof(*rcd), |
1097 | &CPER_CREATOR_PSTORE); |
1098 | /* The record may be cleared by others, try read next record */ |
1099 | if (len == -ENOENT) |
1100 | goto skip; |
1101 | else if (len < 0) |
1102 | goto out; |
1103 | |
1104 | record->buf = kmalloc(size: len, GFP_KERNEL); |
1105 | if (record->buf == NULL) { |
1106 | rc = -ENOMEM; |
1107 | goto out; |
1108 | } |
1109 | memcpy(record->buf, rcd->data, len - sizeof(*rcd)); |
1110 | record->id = record_id; |
1111 | record->compressed = false; |
1112 | record->ecc_notice_size = 0; |
1113 | if (guid_equal(u1: &rcd->sec_hdr.section_type, u2: &CPER_SECTION_TYPE_DMESG_Z)) { |
1114 | record->type = PSTORE_TYPE_DMESG; |
1115 | record->compressed = true; |
1116 | } else if (guid_equal(u1: &rcd->sec_hdr.section_type, u2: &CPER_SECTION_TYPE_DMESG)) |
1117 | record->type = PSTORE_TYPE_DMESG; |
1118 | else if (guid_equal(u1: &rcd->sec_hdr.section_type, u2: &CPER_SECTION_TYPE_MCE)) |
1119 | record->type = PSTORE_TYPE_MCE; |
1120 | else |
1121 | record->type = PSTORE_TYPE_MAX; |
1122 | |
1123 | if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP) |
1124 | record->time.tv_sec = rcd->hdr.timestamp; |
1125 | else |
1126 | record->time.tv_sec = 0; |
1127 | record->time.tv_nsec = 0; |
1128 | |
1129 | out: |
1130 | kfree(objp: rcd); |
1131 | return (rc < 0) ? rc : (len - sizeof(*rcd)); |
1132 | } |
1133 | |
1134 | static int erst_writer(struct pstore_record *record) |
1135 | { |
1136 | struct cper_pstore_record *rcd = (struct cper_pstore_record *) |
1137 | (erst_info.buf - sizeof(*rcd)); |
1138 | int ret; |
1139 | |
1140 | memset(rcd, 0, sizeof(*rcd)); |
1141 | memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE); |
1142 | rcd->hdr.revision = CPER_RECORD_REV; |
1143 | rcd->hdr.signature_end = CPER_SIG_END; |
1144 | rcd->hdr.section_count = 1; |
1145 | rcd->hdr.error_severity = CPER_SEV_FATAL; |
1146 | /* timestamp valid. platform_id, partition_id are invalid */ |
1147 | rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP; |
1148 | rcd->hdr.timestamp = ktime_get_real_seconds(); |
1149 | rcd->hdr.record_length = sizeof(*rcd) + record->size; |
1150 | rcd->hdr.creator_id = CPER_CREATOR_PSTORE; |
1151 | rcd->hdr.notification_type = CPER_NOTIFY_MCE; |
1152 | rcd->hdr.record_id = cper_next_record_id(); |
1153 | rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR; |
1154 | |
1155 | rcd->sec_hdr.section_offset = sizeof(*rcd); |
1156 | rcd->sec_hdr.section_length = record->size; |
1157 | rcd->sec_hdr.revision = CPER_SEC_REV; |
1158 | /* fru_id and fru_text is invalid */ |
1159 | rcd->sec_hdr.validation_bits = 0; |
1160 | rcd->sec_hdr.flags = CPER_SEC_PRIMARY; |
1161 | switch (record->type) { |
1162 | case PSTORE_TYPE_DMESG: |
1163 | if (record->compressed) |
1164 | rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG_Z; |
1165 | else |
1166 | rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG; |
1167 | break; |
1168 | case PSTORE_TYPE_MCE: |
1169 | rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE; |
1170 | break; |
1171 | default: |
1172 | return -EINVAL; |
1173 | } |
1174 | rcd->sec_hdr.section_severity = CPER_SEV_FATAL; |
1175 | |
1176 | ret = erst_write(&rcd->hdr); |
1177 | record->id = rcd->hdr.record_id; |
1178 | |
1179 | return ret; |
1180 | } |
1181 | |
1182 | static int erst_clearer(struct pstore_record *record) |
1183 | { |
1184 | return erst_clear(record->id); |
1185 | } |
1186 | |
1187 | static int __init erst_init(void) |
1188 | { |
1189 | int rc = 0; |
1190 | acpi_status status; |
1191 | struct apei_exec_context ctx; |
1192 | struct apei_resources erst_resources; |
1193 | struct resource *r; |
1194 | char *buf; |
1195 | |
1196 | if (acpi_disabled) |
1197 | goto err; |
1198 | |
1199 | if (erst_disable) { |
1200 | pr_info( |
1201 | "Error Record Serialization Table (ERST) support is disabled.\n" ); |
1202 | goto err; |
1203 | } |
1204 | |
1205 | status = acpi_get_table(ACPI_SIG_ERST, instance: 0, |
1206 | out_table: (struct acpi_table_header **)&erst_tab); |
1207 | if (status == AE_NOT_FOUND) |
1208 | goto err; |
1209 | else if (ACPI_FAILURE(status)) { |
1210 | const char *msg = acpi_format_exception(exception: status); |
1211 | pr_err("Failed to get table, %s\n" , msg); |
1212 | rc = -EINVAL; |
1213 | goto err; |
1214 | } |
1215 | |
1216 | rc = erst_check_table(erst_tab); |
1217 | if (rc) { |
1218 | pr_err(FW_BUG "ERST table is invalid.\n" ); |
1219 | goto err_put_erst_tab; |
1220 | } |
1221 | |
1222 | apei_resources_init(resources: &erst_resources); |
1223 | erst_exec_ctx_init(ctx: &ctx); |
1224 | rc = apei_exec_collect_resources(ctx: &ctx, resources: &erst_resources); |
1225 | if (rc) |
1226 | goto err_fini; |
1227 | rc = apei_resources_request(resources: &erst_resources, desc: "APEI ERST" ); |
1228 | if (rc) |
1229 | goto err_fini; |
1230 | rc = apei_exec_pre_map_gars(ctx: &ctx); |
1231 | if (rc) |
1232 | goto err_release; |
1233 | rc = erst_get_erange(range: &erst_erange); |
1234 | if (rc) { |
1235 | if (rc == -ENODEV) |
1236 | pr_info( |
1237 | "The corresponding hardware device or firmware implementation " |
1238 | "is not available.\n" ); |
1239 | else |
1240 | pr_err("Failed to get Error Log Address Range.\n" ); |
1241 | goto err_unmap_reg; |
1242 | } |
1243 | |
1244 | r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST" ); |
1245 | if (!r) { |
1246 | pr_err("Can not request [mem %#010llx-%#010llx] for ERST.\n" , |
1247 | (unsigned long long)erst_erange.base, |
1248 | (unsigned long long)erst_erange.base + erst_erange.size - 1); |
1249 | rc = -EIO; |
1250 | goto err_unmap_reg; |
1251 | } |
1252 | rc = -ENOMEM; |
1253 | erst_erange.vaddr = ioremap_cache(offset: erst_erange.base, |
1254 | size: erst_erange.size); |
1255 | if (!erst_erange.vaddr) |
1256 | goto err_release_erange; |
1257 | |
1258 | pr_info( |
1259 | "Error Record Serialization Table (ERST) support is initialized.\n" ); |
1260 | |
1261 | buf = kmalloc(size: erst_erange.size, GFP_KERNEL); |
1262 | if (buf) { |
1263 | erst_info.buf = buf + sizeof(struct cper_pstore_record); |
1264 | erst_info.bufsize = erst_erange.size - |
1265 | sizeof(struct cper_pstore_record); |
1266 | rc = pstore_register(&erst_info); |
1267 | if (rc) { |
1268 | if (rc != -EPERM) |
1269 | pr_info( |
1270 | "Could not register with persistent store.\n" ); |
1271 | erst_info.buf = NULL; |
1272 | erst_info.bufsize = 0; |
1273 | kfree(objp: buf); |
1274 | } |
1275 | } else |
1276 | pr_err( |
1277 | "Failed to allocate %lld bytes for persistent store error log.\n" , |
1278 | erst_erange.size); |
1279 | |
1280 | /* Cleanup ERST Resources */ |
1281 | apei_resources_fini(resources: &erst_resources); |
1282 | |
1283 | return 0; |
1284 | |
1285 | err_release_erange: |
1286 | release_mem_region(erst_erange.base, erst_erange.size); |
1287 | err_unmap_reg: |
1288 | apei_exec_post_unmap_gars(ctx: &ctx); |
1289 | err_release: |
1290 | apei_resources_release(resources: &erst_resources); |
1291 | err_fini: |
1292 | apei_resources_fini(resources: &erst_resources); |
1293 | err_put_erst_tab: |
1294 | acpi_put_table(table: (struct acpi_table_header *)erst_tab); |
1295 | err: |
1296 | erst_disable = 1; |
1297 | return rc; |
1298 | } |
1299 | |
1300 | device_initcall(erst_init); |
1301 | |