18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * APEI Boot Error Record Table (BERT) support
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2011 Intel Corp.
68c2ecf20Sopenharmony_ci *   Author: Huang Ying <ying.huang@intel.com>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Under normal circumstances, when a hardware error occurs, the error
98c2ecf20Sopenharmony_ci * handler receives control and processes the error. This gives OSPM a
108c2ecf20Sopenharmony_ci * chance to process the error condition, report it, and optionally attempt
118c2ecf20Sopenharmony_ci * recovery. In some cases, the system is unable to process an error.
128c2ecf20Sopenharmony_ci * For example, system firmware or a management controller may choose to
138c2ecf20Sopenharmony_ci * reset the system or the system might experience an uncontrolled crash
148c2ecf20Sopenharmony_ci * or reset.The boot error source is used to report unhandled errors that
158c2ecf20Sopenharmony_ci * occurred in a previous boot. This mechanism is described in the BERT
168c2ecf20Sopenharmony_ci * table.
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * For more information about BERT, please refer to ACPI Specification
198c2ecf20Sopenharmony_ci * version 4.0, section 17.3.1
208c2ecf20Sopenharmony_ci */
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include <linux/kernel.h>
238c2ecf20Sopenharmony_ci#include <linux/module.h>
248c2ecf20Sopenharmony_ci#include <linux/init.h>
258c2ecf20Sopenharmony_ci#include <linux/acpi.h>
268c2ecf20Sopenharmony_ci#include <linux/io.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#include "apei-internal.h"
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#undef pr_fmt
318c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "BERT: " fmt
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define ACPI_BERT_PRINT_MAX_RECORDS 5
348c2ecf20Sopenharmony_ci#define ACPI_BERT_PRINT_MAX_LEN 1024
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic int bert_disable;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/*
398c2ecf20Sopenharmony_ci * Print "all" the error records in the BERT table, but avoid huge spam to
408c2ecf20Sopenharmony_ci * the console if the BIOS included oversize records, or too many records.
418c2ecf20Sopenharmony_ci * Skipping some records here does not lose anything because the full
428c2ecf20Sopenharmony_ci * data is available to user tools in:
438c2ecf20Sopenharmony_ci *	/sys/firmware/acpi/tables/data/BERT
448c2ecf20Sopenharmony_ci */
458c2ecf20Sopenharmony_cistatic void __init bert_print_all(struct acpi_bert_region *region,
468c2ecf20Sopenharmony_ci				  unsigned int region_len)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	struct acpi_hest_generic_status *estatus =
498c2ecf20Sopenharmony_ci		(struct acpi_hest_generic_status *)region;
508c2ecf20Sopenharmony_ci	int remain = region_len;
518c2ecf20Sopenharmony_ci	int printed = 0, skipped = 0;
528c2ecf20Sopenharmony_ci	u32 estatus_len;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	while (remain >= sizeof(struct acpi_bert_region)) {
558c2ecf20Sopenharmony_ci		estatus_len = cper_estatus_len(estatus);
568c2ecf20Sopenharmony_ci		if (remain < estatus_len) {
578c2ecf20Sopenharmony_ci			pr_err(FW_BUG "Truncated status block (length: %u).\n",
588c2ecf20Sopenharmony_ci			       estatus_len);
598c2ecf20Sopenharmony_ci			break;
608c2ecf20Sopenharmony_ci		}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci		/* No more error records. */
638c2ecf20Sopenharmony_ci		if (!estatus->block_status)
648c2ecf20Sopenharmony_ci			break;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci		if (cper_estatus_check(estatus)) {
678c2ecf20Sopenharmony_ci			pr_err(FW_BUG "Invalid error record.\n");
688c2ecf20Sopenharmony_ci			break;
698c2ecf20Sopenharmony_ci		}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		if (estatus_len < ACPI_BERT_PRINT_MAX_LEN &&
728c2ecf20Sopenharmony_ci		    printed < ACPI_BERT_PRINT_MAX_RECORDS) {
738c2ecf20Sopenharmony_ci			pr_info_once("Error records from previous boot:\n");
748c2ecf20Sopenharmony_ci			cper_estatus_print(KERN_INFO HW_ERR, estatus);
758c2ecf20Sopenharmony_ci			printed++;
768c2ecf20Sopenharmony_ci		} else {
778c2ecf20Sopenharmony_ci			skipped++;
788c2ecf20Sopenharmony_ci		}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci		/*
818c2ecf20Sopenharmony_ci		 * Because the boot error source is "one-time polled" type,
828c2ecf20Sopenharmony_ci		 * clear Block Status of current Generic Error Status Block,
838c2ecf20Sopenharmony_ci		 * once it's printed.
848c2ecf20Sopenharmony_ci		 */
858c2ecf20Sopenharmony_ci		estatus->block_status = 0;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci		estatus = (void *)estatus + estatus_len;
888c2ecf20Sopenharmony_ci		remain -= estatus_len;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (skipped)
928c2ecf20Sopenharmony_ci		pr_info(HW_ERR "Skipped %d error records\n", skipped);
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic int __init setup_bert_disable(char *str)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	bert_disable = 1;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	return 1;
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci__setup("bert_disable", setup_bert_disable);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic int __init bert_check_table(struct acpi_table_bert *bert_tab)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	if (bert_tab->header.length < sizeof(struct acpi_table_bert) ||
1068c2ecf20Sopenharmony_ci	    bert_tab->region_length < sizeof(struct acpi_bert_region))
1078c2ecf20Sopenharmony_ci		return -EINVAL;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int __init bert_init(void)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct apei_resources bert_resources;
1158c2ecf20Sopenharmony_ci	struct acpi_bert_region *boot_error_region;
1168c2ecf20Sopenharmony_ci	struct acpi_table_bert *bert_tab;
1178c2ecf20Sopenharmony_ci	unsigned int region_len;
1188c2ecf20Sopenharmony_ci	acpi_status status;
1198c2ecf20Sopenharmony_ci	int rc = 0;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	if (acpi_disabled)
1228c2ecf20Sopenharmony_ci		return 0;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (bert_disable) {
1258c2ecf20Sopenharmony_ci		pr_info("Boot Error Record Table support is disabled.\n");
1268c2ecf20Sopenharmony_ci		return 0;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab);
1308c2ecf20Sopenharmony_ci	if (status == AE_NOT_FOUND)
1318c2ecf20Sopenharmony_ci		return 0;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
1348c2ecf20Sopenharmony_ci		pr_err("get table failed, %s.\n", acpi_format_exception(status));
1358c2ecf20Sopenharmony_ci		return -EINVAL;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	rc = bert_check_table(bert_tab);
1398c2ecf20Sopenharmony_ci	if (rc) {
1408c2ecf20Sopenharmony_ci		pr_err(FW_BUG "table invalid.\n");
1418c2ecf20Sopenharmony_ci		goto out_put_bert_tab;
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	region_len = bert_tab->region_length;
1458c2ecf20Sopenharmony_ci	apei_resources_init(&bert_resources);
1468c2ecf20Sopenharmony_ci	rc = apei_resources_add(&bert_resources, bert_tab->address,
1478c2ecf20Sopenharmony_ci				region_len, true);
1488c2ecf20Sopenharmony_ci	if (rc)
1498c2ecf20Sopenharmony_ci		goto out_put_bert_tab;
1508c2ecf20Sopenharmony_ci	rc = apei_resources_request(&bert_resources, "APEI BERT");
1518c2ecf20Sopenharmony_ci	if (rc)
1528c2ecf20Sopenharmony_ci		goto out_fini;
1538c2ecf20Sopenharmony_ci	boot_error_region = ioremap_cache(bert_tab->address, region_len);
1548c2ecf20Sopenharmony_ci	if (boot_error_region) {
1558c2ecf20Sopenharmony_ci		bert_print_all(boot_error_region, region_len);
1568c2ecf20Sopenharmony_ci		iounmap(boot_error_region);
1578c2ecf20Sopenharmony_ci	} else {
1588c2ecf20Sopenharmony_ci		rc = -ENOMEM;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	apei_resources_release(&bert_resources);
1628c2ecf20Sopenharmony_ciout_fini:
1638c2ecf20Sopenharmony_ci	apei_resources_fini(&bert_resources);
1648c2ecf20Sopenharmony_ciout_put_bert_tab:
1658c2ecf20Sopenharmony_ci	acpi_put_table((struct acpi_table_header *)bert_tab);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return rc;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cilate_initcall(bert_init);
171