hest.c 6.2 KB
Newer Older
H
Huang Ying 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/*
 * APEI Hardware Error Souce Table support
 *
 * HEST describes error sources in detail; communicates operational
 * parameters (i.e. severity levels, masking bits, and threshold
 * values) to Linux as necessary. It also allows the BIOS to report
 * non-standard error sources to Linux (for example, chipset-specific
 * error registers).
 *
 * For more information about HEST, please refer to ACPI Specification
 * version 4.0, section 17.3.2.
 *
 * Copyright 2009 Intel Corp.
 *   Author: Huang Ying <ying.huang@intel.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/kdebug.h>
#include <linux/highmem.h>
#include <linux/io.h>
37
#include <linux/platform_device.h>
H
Huang Ying 已提交
38 39 40 41 42 43
#include <acpi/apei.h>

#include "apei-internal.h"

#define HEST_PFX "HEST: "

44
bool hest_disable;
H
Huang Ying 已提交
45 46 47 48
EXPORT_SYMBOL_GPL(hest_disable);

/* HEST table parsing */

J
Jan Beulich 已提交
49
static struct acpi_table_hest *__read_mostly hest_tab;
H
Huang Ying 已提交
50

J
Jan Beulich 已提交
51
static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = {
H
Huang Ying 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
	[ACPI_HEST_TYPE_IA32_CHECK] = -1,	/* need further calculation */
	[ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1,
	[ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi),
	[ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root),
	[ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer),
	[ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge),
	[ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic),
};

static int hest_esrc_len(struct acpi_hest_header *hest_hdr)
{
	u16 hest_type = hest_hdr->type;
	int len;

	if (hest_type >= ACPI_HEST_TYPE_RESERVED)
		return 0;

	len = hest_esrc_len_tab[hest_type];

	if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) {
		struct acpi_hest_ia_corrected *cmc;
		cmc = (struct acpi_hest_ia_corrected *)hest_hdr;
		len = sizeof(*cmc) + cmc->num_hardware_banks *
			sizeof(struct acpi_hest_ia_error_bank);
	} else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) {
		struct acpi_hest_ia_machine_check *mc;
		mc = (struct acpi_hest_ia_machine_check *)hest_hdr;
		len = sizeof(*mc) + mc->num_hardware_banks *
			sizeof(struct acpi_hest_ia_error_bank);
	}
	BUG_ON(len == -1);

	return len;
};

int apei_hest_parse(apei_hest_func_t func, void *data)
{
	struct acpi_hest_header *hest_hdr;
	int i, rc, len;

	if (hest_disable)
		return -EINVAL;

	hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);
	for (i = 0; i < hest_tab->error_source_count; i++) {
		len = hest_esrc_len(hest_hdr);
		if (!len) {
			pr_warning(FW_WARN HEST_PFX
				   "Unknown or unused hardware error source "
				   "type: %d for hardware error source: %d.\n",
				   hest_hdr->type, hest_hdr->source_id);
			return -EINVAL;
		}
		if ((void *)hest_hdr + len >
		    (void *)hest_tab + hest_tab->header.length) {
			pr_warning(FW_BUG HEST_PFX
		"Table contents overflow for hardware error source: %d.\n",
				hest_hdr->source_id);
			return -EINVAL;
		}

		rc = func(hest_hdr, data);
		if (rc)
			return rc;

		hest_hdr = (void *)hest_hdr + len;
	}

	return 0;
}
EXPORT_SYMBOL_GPL(apei_hest_parse);

124 125 126 127 128
struct ghes_arr {
	struct platform_device **ghes_devs;
	unsigned int count;
};

J
Jan Beulich 已提交
129
static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)
130 131 132 133 134 135 136 137
{
	int *count = data;

	if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR)
		(*count)++;
	return 0;
}

J
Jan Beulich 已提交
138
static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
139 140 141
{
	struct platform_device *ghes_dev;
	struct ghes_arr *ghes_arr = data;
142
	int rc, i;
143 144 145

	if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR)
		return 0;
146 147

	if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
148
		return 0;
149 150 151 152 153 154 155 156 157 158
	for (i = 0; i < ghes_arr->count; i++) {
		struct acpi_hest_header *hdr;
		ghes_dev = ghes_arr->ghes_devs[i];
		hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;
		if (hdr->source_id == hest_hdr->source_id) {
			pr_warning(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",
				   hdr->source_id);
			return -EIO;
		}
	}
159 160 161
	ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
	if (!ghes_dev)
		return -ENOMEM;
162 163 164 165 166

	rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
	if (rc)
		goto err;

167 168 169 170 171 172 173 174 175 176 177
	rc = platform_device_add(ghes_dev);
	if (rc)
		goto err;
	ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;

	return 0;
err:
	platform_device_put(ghes_dev);
	return rc;
}

J
Jan Beulich 已提交
178
static int __init hest_ghes_dev_register(unsigned int ghes_count)
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
{
	int rc, i;
	struct ghes_arr ghes_arr;

	ghes_arr.count = 0;
	ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL);
	if (!ghes_arr.ghes_devs)
		return -ENOMEM;

	rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);
	if (rc)
		goto err;
out:
	kfree(ghes_arr.ghes_devs);
	return rc;
err:
	for (i = 0; i < ghes_arr.count; i++)
		platform_device_unregister(ghes_arr.ghes_devs[i]);
	goto out;
}

H
Huang Ying 已提交
200 201 202 203 204 205 206 207
static int __init setup_hest_disable(char *str)
{
	hest_disable = 1;
	return 0;
}

__setup("hest_disable", setup_hest_disable);

208
void __init acpi_hest_init(void)
H
Huang Ying 已提交
209 210 211
{
	acpi_status status;
	int rc = -ENODEV;
212
	unsigned int ghes_count = 0;
H
Huang Ying 已提交
213 214

	if (hest_disable) {
215 216
		pr_info(HEST_PFX "Table parsing disabled.\n");
		return;
H
Huang Ying 已提交
217 218
	}

219 220 221
	if (acpi_disabled)
		goto err;

H
Huang Ying 已提交
222 223
	status = acpi_get_table(ACPI_SIG_HEST, 0,
				(struct acpi_table_header **)&hest_tab);
224
	if (status == AE_NOT_FOUND)
H
Huang Ying 已提交
225
		goto err;
226
	else if (ACPI_FAILURE(status)) {
H
Huang Ying 已提交
227 228 229 230 231 232
		const char *msg = acpi_format_exception(status);
		pr_err(HEST_PFX "Failed to get table, %s\n", msg);
		rc = -EINVAL;
		goto err;
	}

233 234 235 236 237 238 239
	if (!ghes_disable) {
		rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);
		if (rc)
			goto err;
		rc = hest_ghes_dev_register(ghes_count);
		if (rc)
			goto err;
240
	}
H
Huang Ying 已提交
241

242 243
	pr_info(HEST_PFX "Table parsing has been initialized.\n");
	return;
H
Huang Ying 已提交
244 245 246
err:
	hest_disable = 1;
}