linux/drivers/acpi/apei/hest.c
<<
>>
Prefs
   1/*
   2 * APEI Hardware Error Souce Table support
   3 *
   4 * HEST describes error sources in detail; communicates operational
   5 * parameters (i.e. severity levels, masking bits, and threshold
   6 * values) to Linux as necessary. It also allows the BIOS to report
   7 * non-standard error sources to Linux (for example, chipset-specific
   8 * error registers).
   9 *
  10 * For more information about HEST, please refer to ACPI Specification
  11 * version 4.0, section 17.3.2.
  12 *
  13 * Copyright 2009 Intel Corp.
  14 *   Author: Huang Ying <ying.huang@intel.com>
  15 *
  16 * This program is free software; you can redistribute it and/or
  17 * modify it under the terms of the GNU General Public License version
  18 * 2 as published by the Free Software Foundation;
  19 *
  20 * This program is distributed in the hope that it will be useful,
  21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23 * GNU General Public License for more details.
  24 */
  25
  26#include <linux/kernel.h>
  27#include <linux/module.h>
  28#include <linux/init.h>
  29#include <linux/acpi.h>
  30#include <linux/kdebug.h>
  31#include <linux/highmem.h>
  32#include <linux/io.h>
  33#include <linux/platform_device.h>
  34#include <acpi/apei.h>
  35
  36#include "apei-internal.h"
  37
  38#define HEST_PFX "HEST: "
  39
  40int hest_disable;
  41EXPORT_SYMBOL_GPL(hest_disable);
  42
  43/* HEST table parsing */
  44
  45static struct acpi_table_hest *__read_mostly hest_tab;
  46
  47static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = {
  48        [ACPI_HEST_TYPE_IA32_CHECK] = -1,       /* need further calculation */
  49        [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1,
  50        [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi),
  51        [ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root),
  52        [ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer),
  53        [ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge),
  54        [ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic),
  55        [ACPI_HEST_TYPE_GENERIC_ERROR_V2] = sizeof(struct acpi_hest_generic_v2),
  56};
  57
  58static int hest_esrc_len(struct acpi_hest_header *hest_hdr)
  59{
  60        u16 hest_type = hest_hdr->type;
  61        int len;
  62
  63        if (hest_type >= ACPI_HEST_TYPE_RESERVED)
  64                return 0;
  65
  66        len = hest_esrc_len_tab[hest_type];
  67
  68        if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) {
  69                struct acpi_hest_ia_corrected *cmc;
  70                cmc = (struct acpi_hest_ia_corrected *)hest_hdr;
  71                len = sizeof(*cmc) + cmc->num_hardware_banks *
  72                        sizeof(struct acpi_hest_ia_error_bank);
  73        } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) {
  74                struct acpi_hest_ia_machine_check *mc;
  75                mc = (struct acpi_hest_ia_machine_check *)hest_hdr;
  76                len = sizeof(*mc) + mc->num_hardware_banks *
  77                        sizeof(struct acpi_hest_ia_error_bank);
  78        }
  79        BUG_ON(len == -1);
  80
  81        return len;
  82};
  83
  84int apei_hest_parse(apei_hest_func_t func, void *data)
  85{
  86        struct acpi_hest_header *hest_hdr;
  87        int i, rc, len;
  88
  89        if (hest_disable || !hest_tab)
  90                return -EINVAL;
  91
  92        hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);
  93        for (i = 0; i < hest_tab->error_source_count; i++) {
  94                len = hest_esrc_len(hest_hdr);
  95                if (!len) {
  96                        pr_warning(FW_WARN HEST_PFX
  97                                   "Unknown or unused hardware error source "
  98                                   "type: %d for hardware error source: %d.\n",
  99                                   hest_hdr->type, hest_hdr->source_id);
 100                        return -EINVAL;
 101                }
 102                if ((void *)hest_hdr + len >
 103                    (void *)hest_tab + hest_tab->header.length) {
 104                        pr_warning(FW_BUG HEST_PFX
 105                "Table contents overflow for hardware error source: %d.\n",
 106                                hest_hdr->source_id);
 107                        return -EINVAL;
 108                }
 109
 110                rc = func(hest_hdr, data);
 111                if (rc)
 112                        return rc;
 113
 114                hest_hdr = (void *)hest_hdr + len;
 115        }
 116
 117        return 0;
 118}
 119EXPORT_SYMBOL_GPL(apei_hest_parse);
 120
 121/*
 122 * Check if firmware advertises firmware first mode. We need FF bit to be set
 123 * along with a set of MC banks which work in FF mode.
 124 */
 125static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data)
 126{
 127        if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK)
 128                return 0;
 129
 130        if (!acpi_disable_cmcff)
 131                return !arch_apei_enable_cmcff(hest_hdr, data);
 132
 133        return 0;
 134}
 135
 136struct ghes_arr {
 137        struct platform_device **ghes_devs;
 138        unsigned int count;
 139};
 140
 141static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)
 142{
 143        int *count = data;
 144
 145        if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR ||
 146            hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR_V2)
 147                (*count)++;
 148        return 0;
 149}
 150
 151static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
 152{
 153        struct platform_device *ghes_dev;
 154        struct ghes_arr *ghes_arr = data;
 155        int rc, i;
 156
 157        if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR &&
 158            hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR_V2)
 159                return 0;
 160
 161        if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
 162                return 0;
 163        for (i = 0; i < ghes_arr->count; i++) {
 164                struct acpi_hest_header *hdr;
 165                ghes_dev = ghes_arr->ghes_devs[i];
 166                hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;
 167                if (hdr->source_id == hest_hdr->source_id) {
 168                        pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",
 169                                   hdr->source_id);
 170                        return -EIO;
 171                }
 172        }
 173        ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
 174        if (!ghes_dev)
 175                return -ENOMEM;
 176
 177        rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
 178        if (rc)
 179                goto err;
 180
 181        rc = platform_device_add(ghes_dev);
 182        if (rc)
 183                goto err;
 184        ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;
 185
 186        return 0;
 187err:
 188        platform_device_put(ghes_dev);
 189        return rc;
 190}
 191
 192static int __init hest_ghes_dev_register(unsigned int ghes_count)
 193{
 194        int rc, i;
 195        struct ghes_arr ghes_arr;
 196
 197        ghes_arr.count = 0;
 198        ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL);
 199        if (!ghes_arr.ghes_devs)
 200                return -ENOMEM;
 201
 202        rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);
 203        if (rc)
 204                goto err;
 205out:
 206        kfree(ghes_arr.ghes_devs);
 207        return rc;
 208err:
 209        for (i = 0; i < ghes_arr.count; i++)
 210                platform_device_unregister(ghes_arr.ghes_devs[i]);
 211        goto out;
 212}
 213
 214static int __init setup_hest_disable(char *str)
 215{
 216        hest_disable = HEST_DISABLED;
 217        return 0;
 218}
 219
 220__setup("hest_disable", setup_hest_disable);
 221
 222void __init acpi_hest_init(void)
 223{
 224        acpi_status status;
 225        int rc = -ENODEV;
 226        unsigned int ghes_count = 0;
 227
 228        if (hest_disable) {
 229                pr_info(HEST_PFX "Table parsing disabled.\n");
 230                return;
 231        }
 232
 233        status = acpi_get_table(ACPI_SIG_HEST, 0,
 234                                (struct acpi_table_header **)&hest_tab);
 235        if (status == AE_NOT_FOUND) {
 236                hest_disable = HEST_NOT_FOUND;
 237                return;
 238        } else if (ACPI_FAILURE(status)) {
 239                const char *msg = acpi_format_exception(status);
 240                pr_err(HEST_PFX "Failed to get table, %s\n", msg);
 241                rc = -EINVAL;
 242                goto err;
 243        }
 244
 245        rc = apei_hest_parse(hest_parse_cmc, NULL);
 246        if (rc)
 247                goto err;
 248
 249        if (!ghes_disable) {
 250                rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);
 251                if (rc)
 252                        goto err;
 253                rc = hest_ghes_dev_register(ghes_count);
 254                if (rc)
 255                        goto err;
 256        }
 257
 258        pr_info(HEST_PFX "Table parsing has been initialized.\n");
 259        return;
 260err:
 261        hest_disable = HEST_DISABLED;
 262}
 263