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