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
  40bool 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};
  56
  57static int hest_esrc_len(struct acpi_hest_header *hest_hdr)
  58{
  59        u16 hest_type = hest_hdr->type;
  60        int len;
  61
  62        if (hest_type >= ACPI_HEST_TYPE_RESERVED)
  63                return 0;
  64
  65        len = hest_esrc_len_tab[hest_type];
  66
  67        if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) {
  68                struct acpi_hest_ia_corrected *cmc;
  69                cmc = (struct acpi_hest_ia_corrected *)hest_hdr;
  70                len = sizeof(*cmc) + cmc->num_hardware_banks *
  71                        sizeof(struct acpi_hest_ia_error_bank);
  72        } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) {
  73                struct acpi_hest_ia_machine_check *mc;
  74                mc = (struct acpi_hest_ia_machine_check *)hest_hdr;
  75                len = sizeof(*mc) + mc->num_hardware_banks *
  76                        sizeof(struct acpi_hest_ia_error_bank);
  77        }
  78        BUG_ON(len == -1);
  79
  80        return len;
  81};
  82
  83int apei_hest_parse(apei_hest_func_t func, void *data)
  84{
  85        struct acpi_hest_header *hest_hdr;
  86        int i, rc, len;
  87
  88        if (hest_disable || !hest_tab)
  89                return -EINVAL;
  90
  91        hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);
  92        for (i = 0; i < hest_tab->error_source_count; i++) {
  93                len = hest_esrc_len(hest_hdr);
  94                if (!len) {
  95                        pr_warning(FW_WARN HEST_PFX
  96                                   "Unknown or unused hardware error source "
  97                                   "type: %d for hardware error source: %d.\n",
  98                                   hest_hdr->type, hest_hdr->source_id);
  99                        return -EINVAL;
 100                }
 101                if ((void *)hest_hdr + len >
 102                    (void *)hest_tab + hest_tab->header.length) {
 103                        pr_warning(FW_BUG HEST_PFX
 104                "Table contents overflow for hardware error source: %d.\n",
 105                                hest_hdr->source_id);
 106                        return -EINVAL;
 107                }
 108
 109                rc = func(hest_hdr, data);
 110                if (rc)
 111                        return rc;
 112
 113                hest_hdr = (void *)hest_hdr + len;
 114        }
 115
 116        return 0;
 117}
 118EXPORT_SYMBOL_GPL(apei_hest_parse);
 119
 120/*
 121 * Check if firmware advertises firmware first mode. We need FF bit to be set
 122 * along with a set of MC banks which work in FF mode.
 123 */
 124static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data)
 125{
 126        return arch_apei_enable_cmcff(hest_hdr, data);
 127}
 128
 129struct ghes_arr {
 130        struct platform_device **ghes_devs;
 131        unsigned int count;
 132};
 133
 134static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)
 135{
 136        int *count = data;
 137
 138        if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR)
 139                (*count)++;
 140        return 0;
 141}
 142
 143static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
 144{
 145        struct platform_device *ghes_dev;
 146        struct ghes_arr *ghes_arr = data;
 147        int rc, i;
 148
 149        if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR)
 150                return 0;
 151
 152        if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
 153                return 0;
 154        for (i = 0; i < ghes_arr->count; i++) {
 155                struct acpi_hest_header *hdr;
 156                ghes_dev = ghes_arr->ghes_devs[i];
 157                hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;
 158                if (hdr->source_id == hest_hdr->source_id) {
 159                        pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",
 160                                   hdr->source_id);
 161                        return -EIO;
 162                }
 163        }
 164        ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
 165        if (!ghes_dev)
 166                return -ENOMEM;
 167
 168        rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
 169        if (rc)
 170                goto err;
 171
 172        rc = platform_device_add(ghes_dev);
 173        if (rc)
 174                goto err;
 175        ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;
 176
 177        return 0;
 178err:
 179        platform_device_put(ghes_dev);
 180        return rc;
 181}
 182
 183static int __init hest_ghes_dev_register(unsigned int ghes_count)
 184{
 185        int rc, i;
 186        struct ghes_arr ghes_arr;
 187
 188        ghes_arr.count = 0;
 189        ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL);
 190        if (!ghes_arr.ghes_devs)
 191                return -ENOMEM;
 192
 193        rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);
 194        if (rc)
 195                goto err;
 196out:
 197        kfree(ghes_arr.ghes_devs);
 198        return rc;
 199err:
 200        for (i = 0; i < ghes_arr.count; i++)
 201                platform_device_unregister(ghes_arr.ghes_devs[i]);
 202        goto out;
 203}
 204
 205static int __init setup_hest_disable(char *str)
 206{
 207        hest_disable = 1;
 208        return 0;
 209}
 210
 211__setup("hest_disable", setup_hest_disable);
 212
 213void __init acpi_hest_init(void)
 214{
 215        acpi_status status;
 216        int rc = -ENODEV;
 217        unsigned int ghes_count = 0;
 218
 219        if (hest_disable) {
 220                pr_info(HEST_PFX "Table parsing disabled.\n");
 221                return;
 222        }
 223
 224        status = acpi_get_table(ACPI_SIG_HEST, 0,
 225                                (struct acpi_table_header **)&hest_tab);
 226        if (status == AE_NOT_FOUND)
 227                goto err;
 228        else if (ACPI_FAILURE(status)) {
 229                const char *msg = acpi_format_exception(status);
 230                pr_err(HEST_PFX "Failed to get table, %s\n", msg);
 231                rc = -EINVAL;
 232                goto err;
 233        }
 234
 235        if (!acpi_disable_cmcff)
 236                apei_hest_parse(hest_parse_cmc, NULL);
 237
 238        if (!ghes_disable) {
 239                rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);
 240                if (rc)
 241                        goto err;
 242                rc = hest_ghes_dev_register(ghes_count);
 243                if (rc)
 244                        goto err;
 245        }
 246
 247        pr_info(HEST_PFX "Table parsing has been initialized.\n");
 248        return;
 249err:
 250        hest_disable = 1;
 251}
 252