1 // SPDX-License-Identifier: GPL-2.0-only 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 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 43 while (remain >= sizeof(struct acpi_bert_region)) { 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 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 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; 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 { 95 struct apei_resources bert_resources; 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; 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; 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 142 apei_resources_release(&bert_resources); 143 out_fini: 144 apei_resources_fini(&bert_resources); 145 146 return rc; 147 } 148 149 late_initcall(bert_init); 150