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 apei_resources bert_resources; 101 struct acpi_bert_region *boot_error_region; 102 struct acpi_table_bert *bert_tab; 103 unsigned int region_len; 104 acpi_status status; 105 int rc = 0; 106 107 if (acpi_disabled) 108 return 0; 109 110 if (bert_disable) { 111 pr_info("Boot Error Record Table support is disabled.\n"); 112 return 0; 113 } 114 115 status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab); 116 if (status == AE_NOT_FOUND) 117 return 0; 118 119 if (ACPI_FAILURE(status)) { 120 pr_err("get table failed, %s.\n", acpi_format_exception(status)); 121 return -EINVAL; 122 } 123 124 rc = bert_check_table(bert_tab); 125 if (rc) { 126 pr_err(FW_BUG "table invalid.\n"); 127 return rc; 128 } 129 130 region_len = bert_tab->region_length; 131 apei_resources_init(&bert_resources); 132 rc = apei_resources_add(&bert_resources, bert_tab->address, 133 region_len, true); 134 if (rc) 135 return rc; 136 rc = apei_resources_request(&bert_resources, "APEI BERT"); 137 if (rc) 138 goto out_fini; 139 boot_error_region = ioremap_cache(bert_tab->address, region_len); 140 if (boot_error_region) { 141 bert_print_all(boot_error_region, region_len); 142 iounmap(boot_error_region); 143 } else { 144 rc = -ENOMEM; 145 } 146 147 apei_resources_release(&bert_resources); 148 out_fini: 149 apei_resources_fini(&bert_resources); 150 151 return rc; 152 } 153 154 late_initcall(bert_init); 155