327 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * APEI Hardware Error Source Table support4 *5 * HEST describes error sources in detail; communicates operational6 * parameters (i.e. severity levels, masking bits, and threshold7 * values) to Linux as necessary. It also allows the BIOS to report8 * non-standard error sources to Linux (for example, chipset-specific9 * error registers).10 *11 * For more information about HEST, please refer to ACPI Specification12 * version 4.0, section 17.3.2.13 *14 * Copyright 2009 Intel Corp.15 * Author: Huang Ying <ying.huang@intel.com>16 */17 18#include <linux/kernel.h>19#include <linux/module.h>20#include <linux/init.h>21#include <linux/acpi.h>22#include <linux/kdebug.h>23#include <linux/highmem.h>24#include <linux/io.h>25#include <linux/platform_device.h>26#include <acpi/apei.h>27#include <acpi/ghes.h>28 29#include "apei-internal.h"30 31#define HEST_PFX "HEST: "32 33int hest_disable;34EXPORT_SYMBOL_GPL(hest_disable);35 36/* HEST table parsing */37 38static struct acpi_table_hest *__read_mostly hest_tab;39 40/*41 * Since GHES_ASSIST is not supported, skip initialization of GHES_ASSIST42 * structures for MCA.43 * During HEST parsing, detected MCA error sources are cached from early44 * table entries so that the Flags and Source Id fields from these cached45 * values are then referred to in later table entries to determine if the46 * encountered GHES_ASSIST structure should be initialized.47 */48static struct {49 struct acpi_hest_ia_corrected *cmc;50 struct acpi_hest_ia_machine_check *mc;51 struct acpi_hest_ia_deferred_check *dmc;52} mces;53 54static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = {55 [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */56 [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1,57 [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi),58 [ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root),59 [ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer),60 [ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge),61 [ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic),62 [ACPI_HEST_TYPE_GENERIC_ERROR_V2] = sizeof(struct acpi_hest_generic_v2),63 [ACPI_HEST_TYPE_IA32_DEFERRED_CHECK] = -1,64};65 66static inline bool is_generic_error(struct acpi_hest_header *hest_hdr)67{68 return hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR ||69 hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;70}71 72static int hest_esrc_len(struct acpi_hest_header *hest_hdr)73{74 u16 hest_type = hest_hdr->type;75 int len;76 77 if (hest_type >= ACPI_HEST_TYPE_RESERVED)78 return 0;79 80 len = hest_esrc_len_tab[hest_type];81 82 if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) {83 struct acpi_hest_ia_corrected *cmc;84 cmc = (struct acpi_hest_ia_corrected *)hest_hdr;85 len = sizeof(*cmc) + cmc->num_hardware_banks *86 sizeof(struct acpi_hest_ia_error_bank);87 mces.cmc = cmc;88 } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) {89 struct acpi_hest_ia_machine_check *mc;90 mc = (struct acpi_hest_ia_machine_check *)hest_hdr;91 len = sizeof(*mc) + mc->num_hardware_banks *92 sizeof(struct acpi_hest_ia_error_bank);93 mces.mc = mc;94 } else if (hest_type == ACPI_HEST_TYPE_IA32_DEFERRED_CHECK) {95 struct acpi_hest_ia_deferred_check *mc;96 mc = (struct acpi_hest_ia_deferred_check *)hest_hdr;97 len = sizeof(*mc) + mc->num_hardware_banks *98 sizeof(struct acpi_hest_ia_error_bank);99 mces.dmc = mc;100 }101 BUG_ON(len == -1);102 103 return len;104};105 106/*107 * GHES and GHESv2 structures share the same format, starting from108 * Source Id and ending in Error Status Block Length (inclusive).109 */110static bool is_ghes_assist_struct(struct acpi_hest_header *hest_hdr)111{112 struct acpi_hest_generic *ghes;113 u16 related_source_id;114 115 if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR &&116 hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR_V2)117 return false;118 119 ghes = (struct acpi_hest_generic *)hest_hdr;120 related_source_id = ghes->related_source_id;121 122 if (mces.cmc && mces.cmc->flags & ACPI_HEST_GHES_ASSIST &&123 related_source_id == mces.cmc->header.source_id)124 return true;125 if (mces.mc && mces.mc->flags & ACPI_HEST_GHES_ASSIST &&126 related_source_id == mces.mc->header.source_id)127 return true;128 if (mces.dmc && mces.dmc->flags & ACPI_HEST_GHES_ASSIST &&129 related_source_id == mces.dmc->header.source_id)130 return true;131 132 return false;133}134 135typedef int (*apei_hest_func_t)(struct acpi_hest_header *hest_hdr, void *data);136 137static int apei_hest_parse(apei_hest_func_t func, void *data)138{139 struct acpi_hest_header *hest_hdr;140 int i, rc, len;141 142 if (hest_disable || !hest_tab)143 return -EINVAL;144 145 hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);146 for (i = 0; i < hest_tab->error_source_count; i++) {147 len = hest_esrc_len(hest_hdr);148 if (!len) {149 pr_warn(FW_WARN HEST_PFX150 "Unknown or unused hardware error source "151 "type: %d for hardware error source: %d.\n",152 hest_hdr->type, hest_hdr->source_id);153 return -EINVAL;154 }155 if ((void *)hest_hdr + len >156 (void *)hest_tab + hest_tab->header.length) {157 pr_warn(FW_BUG HEST_PFX158 "Table contents overflow for hardware error source: %d.\n",159 hest_hdr->source_id);160 return -EINVAL;161 }162 163 if (is_ghes_assist_struct(hest_hdr)) {164 hest_hdr = (void *)hest_hdr + len;165 continue;166 }167 168 rc = func(hest_hdr, data);169 if (rc)170 return rc;171 172 hest_hdr = (void *)hest_hdr + len;173 }174 175 return 0;176}177 178/*179 * Check if firmware advertises firmware first mode. We need FF bit to be set180 * along with a set of MC banks which work in FF mode.181 */182static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data)183{184 if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK)185 return 0;186 187 if (!acpi_disable_cmcff)188 return !arch_apei_enable_cmcff(hest_hdr, data);189 190 return 0;191}192 193struct ghes_arr {194 struct platform_device **ghes_devs;195 unsigned int count;196};197 198static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)199{200 int *count = data;201 202 if (is_generic_error(hest_hdr))203 (*count)++;204 return 0;205}206 207static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)208{209 struct platform_device *ghes_dev;210 struct ghes_arr *ghes_arr = data;211 int rc, i;212 213 if (!is_generic_error(hest_hdr))214 return 0;215 216 if (!((struct acpi_hest_generic *)hest_hdr)->enabled)217 return 0;218 for (i = 0; i < ghes_arr->count; i++) {219 struct acpi_hest_header *hdr;220 ghes_dev = ghes_arr->ghes_devs[i];221 hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;222 if (hdr->source_id == hest_hdr->source_id) {223 pr_warn(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",224 hdr->source_id);225 return -EIO;226 }227 }228 ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);229 if (!ghes_dev)230 return -ENOMEM;231 232 rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));233 if (rc)234 goto err;235 236 rc = platform_device_add(ghes_dev);237 if (rc)238 goto err;239 ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;240 241 return 0;242err:243 platform_device_put(ghes_dev);244 return rc;245}246 247static int __init hest_ghes_dev_register(unsigned int ghes_count)248{249 int rc, i;250 struct ghes_arr ghes_arr;251 252 ghes_arr.count = 0;253 ghes_arr.ghes_devs = kmalloc_array(ghes_count, sizeof(void *),254 GFP_KERNEL);255 if (!ghes_arr.ghes_devs)256 return -ENOMEM;257 258 rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);259 if (rc)260 goto err;261 262 rc = ghes_estatus_pool_init(ghes_count);263 if (rc)264 goto err;265 266out:267 kfree(ghes_arr.ghes_devs);268 return rc;269err:270 for (i = 0; i < ghes_arr.count; i++)271 platform_device_unregister(ghes_arr.ghes_devs[i]);272 goto out;273}274 275static int __init setup_hest_disable(char *str)276{277 hest_disable = HEST_DISABLED;278 return 1;279}280 281__setup("hest_disable", setup_hest_disable);282 283void __init acpi_hest_init(void)284{285 acpi_status status;286 int rc;287 unsigned int ghes_count = 0;288 289 if (hest_disable) {290 pr_info(HEST_PFX "Table parsing disabled.\n");291 return;292 }293 294 status = acpi_get_table(ACPI_SIG_HEST, 0,295 (struct acpi_table_header **)&hest_tab);296 if (status == AE_NOT_FOUND) {297 hest_disable = HEST_NOT_FOUND;298 return;299 } else if (ACPI_FAILURE(status)) {300 const char *msg = acpi_format_exception(status);301 pr_err(HEST_PFX "Failed to get table, %s\n", msg);302 hest_disable = HEST_DISABLED;303 return;304 }305 306 rc = apei_hest_parse(hest_parse_cmc, NULL);307 if (rc)308 goto err;309 310 if (!ghes_disable) {311 rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);312 if (rc)313 goto err;314 315 if (ghes_count)316 rc = hest_ghes_dev_register(ghes_count);317 if (rc)318 goto err;319 }320 321 pr_info(HEST_PFX "Table parsing has been initialized.\n");322 return;323err:324 hest_disable = HEST_DISABLED;325 acpi_put_table((struct acpi_table_header *)hest_tab);326}327