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 while (remain >= sizeof(struct acpi_bert_region)) { 46 estatus_len = cper_estatus_len(estatus); 47 if (remain < estatus_len) { 48 pr_err(FW_BUG "Truncated status block (length: %u).\n", 49 estatus_len); 50 return; 51 } 52 53 /* No more error records. */ 54 if (!estatus->block_status) 55 return; 56 57 if (cper_estatus_check(estatus)) { 58 pr_err(FW_BUG "Invalid error record.\n"); 59 return; 60 } 61 62 pr_info_once("Error records from previous boot:\n"); 63 64 cper_estatus_print(KERN_INFO HW_ERR, estatus); 65 66 /* 67 * Because the boot error source is "one-time polled" type, 68 * clear Block Status of current Generic Error Status Block, 69 * once it's printed. 70 */ 71 estatus->block_status = 0; 72 73 estatus = (void *)estatus + estatus_len; 74 remain -= estatus_len; 75 } 76 } 77 78 static int __init setup_bert_disable(char *str) 79 { 80 bert_disable = 1; 81 82 return 0; 83 } 84 __setup("bert_disable", setup_bert_disable); 85 86 static int __init bert_check_table(struct acpi_table_bert *bert_tab) 87 { 88 if (bert_tab->header.length < sizeof(struct acpi_table_bert) || 89 bert_tab->region_length < sizeof(struct acpi_bert_region)) 90 return -EINVAL; 91 92 return 0; 93 } 94 95 static int __init bert_init(void) 96 { 97 struct apei_resources bert_resources; 98 struct acpi_bert_region *boot_error_region; 99 struct acpi_table_bert *bert_tab; 100 unsigned int region_len; 101 acpi_status status; 102 int rc = 0; 103 104 if (acpi_disabled) 105 return 0; 106 107 if (bert_disable) { 108 pr_info("Boot Error Record Table support is disabled.\n"); 109 return 0; 110 } 111 112 status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab); 113 if (status == AE_NOT_FOUND) 114 return 0; 115 116 if (ACPI_FAILURE(status)) { 117 pr_err("get table failed, %s.\n", acpi_format_exception(status)); 118 return -EINVAL; 119 } 120 121 rc = bert_check_table(bert_tab); 122 if (rc) { 123 pr_err(FW_BUG "table invalid.\n"); 124 return rc; 125 } 126 127 region_len = bert_tab->region_length; 128 apei_resources_init(&bert_resources); 129 rc = apei_resources_add(&bert_resources, bert_tab->address, 130 region_len, true); 131 if (rc) 132 return rc; 133 rc = apei_resources_request(&bert_resources, "APEI BERT"); 134 if (rc) 135 goto out_fini; 136 boot_error_region = ioremap_cache(bert_tab->address, region_len); 137 if (boot_error_region) { 138 bert_print_all(boot_error_region, region_len); 139 iounmap(boot_error_region); 140 } else { 141 rc = -ENOMEM; 142 } 143 144 apei_resources_release(&bert_resources); 145 out_fini: 146 apei_resources_fini(&bert_resources); 147 148 return rc; 149 } 150 151 late_initcall(bert_init); 152