brintos

brintos / linux-shallow public Read only

0
0
Text · 8.4 KiB · 20d7576 Raw
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