linux/drivers/acpi/apei/bert.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * APEI Boot Error Record Table (BERT) support
   4 *
   5 * Copyright 2011 Intel Corp.
   6 *   Author: Huang Ying <ying.huang@intel.com>
   7 *
   8 * Under normal circumstances, when a hardware error occurs, the error
   9 * handler receives control and processes the error. This gives OSPM a
  10 * chance to process the error condition, report it, and optionally attempt
  11 * recovery. In some cases, the system is unable to process an error.
  12 * For example, system firmware or a management controller may choose to
  13 * reset the system or the system might experience an uncontrolled crash
  14 * or reset.The boot error source is used to report unhandled errors that
  15 * occurred in a previous boot. This mechanism is described in the BERT
  16 * table.
  17 *
  18 * For more information about BERT, please refer to ACPI Specification
  19 * version 4.0, section 17.3.1
  20 */
  21
  22#include <linux/kernel.h>
  23#include <linux/module.h>
  24#include <linux/init.h>
  25#include <linux/acpi.h>
  26#include <linux/io.h>
  27
  28#include "apei-internal.h"
  29
  30#undef pr_fmt
  31#define pr_fmt(fmt) "BERT: " fmt
  32
  33static int bert_disable;
  34
  35static void __init bert_print_all(struct acpi_bert_region *region,
  36                                  unsigned int region_len)
  37{
  38        struct acpi_hest_generic_status *estatus =
  39                (struct acpi_hest_generic_status *)region;
  40        int remain = region_len;
  41        u32 estatus_len;
  42
  43        while (remain >= sizeof(struct acpi_bert_region)) {
  44                estatus_len = cper_estatus_len(estatus);
  45                if (remain < estatus_len) {
  46                        pr_err(FW_BUG "Truncated status block (length: %u).\n",
  47                               estatus_len);
  48                        return;
  49                }
  50
  51                /* No more error records. */
  52                if (!estatus->block_status)
  53                        return;
  54
  55                if (cper_estatus_check(estatus)) {
  56                        pr_err(FW_BUG "Invalid error record.\n");
  57                        return;
  58                }
  59
  60                pr_info_once("Error records from previous boot:\n");
  61
  62                cper_estatus_print(KERN_INFO HW_ERR, estatus);
  63
  64                /*
  65                 * Because the boot error source is "one-time polled" type,
  66                 * clear Block Status of current Generic Error Status Block,
  67                 * once it's printed.
  68                 */
  69                estatus->block_status = 0;
  70
  71                estatus = (void *)estatus + estatus_len;
  72                remain -= estatus_len;
  73        }
  74}
  75
  76static int __init setup_bert_disable(char *str)
  77{
  78        bert_disable = 1;
  79
  80        return 0;
  81}
  82__setup("bert_disable", setup_bert_disable);
  83
  84static int __init bert_check_table(struct acpi_table_bert *bert_tab)
  85{
  86        if (bert_tab->header.length < sizeof(struct acpi_table_bert) ||
  87            bert_tab->region_length < sizeof(struct acpi_bert_region))
  88                return -EINVAL;
  89
  90        return 0;
  91}
  92
  93static int __init bert_init(void)
  94{
  95        struct apei_resources bert_resources;
  96        struct acpi_bert_region *boot_error_region;
  97        struct acpi_table_bert *bert_tab;
  98        unsigned int region_len;
  99        acpi_status status;
 100        int rc = 0;
 101
 102        if (acpi_disabled)
 103                return 0;
 104
 105        if (bert_disable) {
 106                pr_info("Boot Error Record Table support is disabled.\n");
 107                return 0;
 108        }
 109
 110        status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab);
 111        if (status == AE_NOT_FOUND)
 112                return 0;
 113
 114        if (ACPI_FAILURE(status)) {
 115                pr_err("get table failed, %s.\n", acpi_format_exception(status));
 116                return -EINVAL;
 117        }
 118
 119        rc = bert_check_table(bert_tab);
 120        if (rc) {
 121                pr_err(FW_BUG "table invalid.\n");
 122                return rc;
 123        }
 124
 125        region_len = bert_tab->region_length;
 126        apei_resources_init(&bert_resources);
 127        rc = apei_resources_add(&bert_resources, bert_tab->address,
 128                                region_len, true);
 129        if (rc)
 130                return rc;
 131        rc = apei_resources_request(&bert_resources, "APEI BERT");
 132        if (rc)
 133                goto out_fini;
 134        boot_error_region = ioremap_cache(bert_tab->address, region_len);
 135        if (boot_error_region) {
 136                bert_print_all(boot_error_region, region_len);
 137                iounmap(boot_error_region);
 138        } else {
 139                rc = -ENOMEM;
 140        }
 141
 142        apei_resources_release(&bert_resources);
 143out_fini:
 144        apei_resources_fini(&bert_resources);
 145
 146        return rc;
 147}
 148
 149late_initcall(bert_init);
 150