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