aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/acpi/apei
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/apei')
-rw-r--r--drivers/acpi/apei/Kconfig9
-rw-r--r--drivers/acpi/apei/Makefile1
-rw-r--r--drivers/acpi/apei/apei-base.c25
-rw-r--r--drivers/acpi/apei/einj.c4
-rw-r--r--drivers/acpi/apei/erst-dbg.c211
-rw-r--r--drivers/acpi/apei/erst.c29
-rw-r--r--drivers/acpi/apei/ghes.c172
-rw-r--r--drivers/acpi/apei/hest.c79
8 files changed, 430 insertions, 100 deletions
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig
index f8c668f27b5a..fca34ccfd294 100644
--- a/drivers/acpi/apei/Kconfig
+++ b/drivers/acpi/apei/Kconfig
@@ -28,3 +28,12 @@ config ACPI_APEI_EINJ
28 EINJ provides a hardware error injection mechanism, it is 28 EINJ provides a hardware error injection mechanism, it is
29 mainly used for debugging and testing the other parts of 29 mainly used for debugging and testing the other parts of
30 APEI and some other RAS features. 30 APEI and some other RAS features.
31
32config ACPI_APEI_ERST_DEBUG
33 tristate "APEI Error Record Serialization Table (ERST) Debug Support"
34 depends on ACPI_APEI
35 help
36 ERST is a way provided by APEI to save and retrieve hardware
37 error information to and from a persistent store. Enable this
38 if you want to debugging and testing the ERST kernel support
39 and firmware implementation.
diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile
index b13b03a17789..d1d1bc0a4ee1 100644
--- a/drivers/acpi/apei/Makefile
+++ b/drivers/acpi/apei/Makefile
@@ -1,5 +1,6 @@
1obj-$(CONFIG_ACPI_APEI) += apei.o 1obj-$(CONFIG_ACPI_APEI) += apei.o
2obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o 2obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o
3obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o 3obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o
4obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o
4 5
5apei-y := apei-base.o hest.o cper.o erst.o 6apei-y := apei-base.o hest.o cper.o erst.o
diff --git a/drivers/acpi/apei/apei-base.c b/drivers/acpi/apei/apei-base.c
index 216e1e948ff6..4a904a4bf05f 100644
--- a/drivers/acpi/apei/apei-base.c
+++ b/drivers/acpi/apei/apei-base.c
@@ -445,11 +445,15 @@ EXPORT_SYMBOL_GPL(apei_resources_sub);
445int apei_resources_request(struct apei_resources *resources, 445int apei_resources_request(struct apei_resources *resources,
446 const char *desc) 446 const char *desc)
447{ 447{
448 struct apei_res *res, *res_bak; 448 struct apei_res *res, *res_bak = NULL;
449 struct resource *r; 449 struct resource *r;
450 int rc;
450 451
451 apei_resources_sub(resources, &apei_resources_all); 452 rc = apei_resources_sub(resources, &apei_resources_all);
453 if (rc)
454 return rc;
452 455
456 rc = -EINVAL;
453 list_for_each_entry(res, &resources->iomem, list) { 457 list_for_each_entry(res, &resources->iomem, list) {
454 r = request_mem_region(res->start, res->end - res->start, 458 r = request_mem_region(res->start, res->end - res->start,
455 desc); 459 desc);
@@ -475,28 +479,33 @@ int apei_resources_request(struct apei_resources *resources,
475 } 479 }
476 } 480 }
477 481
478 apei_resources_merge(&apei_resources_all, resources); 482 rc = apei_resources_merge(&apei_resources_all, resources);
483 if (rc) {
484 pr_err(APEI_PFX "Fail to merge resources!\n");
485 goto err_unmap_ioport;
486 }
479 487
480 return 0; 488 return 0;
481err_unmap_ioport: 489err_unmap_ioport:
482 list_for_each_entry(res, &resources->ioport, list) { 490 list_for_each_entry(res, &resources->ioport, list) {
483 if (res == res_bak) 491 if (res == res_bak)
484 break; 492 break;
485 release_mem_region(res->start, res->end - res->start); 493 release_region(res->start, res->end - res->start);
486 } 494 }
487 res_bak = NULL; 495 res_bak = NULL;
488err_unmap_iomem: 496err_unmap_iomem:
489 list_for_each_entry(res, &resources->iomem, list) { 497 list_for_each_entry(res, &resources->iomem, list) {
490 if (res == res_bak) 498 if (res == res_bak)
491 break; 499 break;
492 release_region(res->start, res->end - res->start); 500 release_mem_region(res->start, res->end - res->start);
493 } 501 }
494 return -EINVAL; 502 return rc;
495} 503}
496EXPORT_SYMBOL_GPL(apei_resources_request); 504EXPORT_SYMBOL_GPL(apei_resources_request);
497 505
498void apei_resources_release(struct apei_resources *resources) 506void apei_resources_release(struct apei_resources *resources)
499{ 507{
508 int rc;
500 struct apei_res *res; 509 struct apei_res *res;
501 510
502 list_for_each_entry(res, &resources->iomem, list) 511 list_for_each_entry(res, &resources->iomem, list)
@@ -504,7 +513,9 @@ void apei_resources_release(struct apei_resources *resources)
504 list_for_each_entry(res, &resources->ioport, list) 513 list_for_each_entry(res, &resources->ioport, list)
505 release_region(res->start, res->end - res->start); 514 release_region(res->start, res->end - res->start);
506 515
507 apei_resources_sub(&apei_resources_all, resources); 516 rc = apei_resources_sub(&apei_resources_all, resources);
517 if (rc)
518 pr_err(APEI_PFX "Fail to sub resources!\n");
508} 519}
509EXPORT_SYMBOL_GPL(apei_resources_release); 520EXPORT_SYMBOL_GPL(apei_resources_release);
510 521
diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c
index 465c885938ee..cf29df69380b 100644
--- a/drivers/acpi/apei/einj.c
+++ b/drivers/acpi/apei/einj.c
@@ -426,7 +426,9 @@ DEFINE_SIMPLE_ATTRIBUTE(error_inject_fops, NULL,
426 426
427static int einj_check_table(struct acpi_table_einj *einj_tab) 427static int einj_check_table(struct acpi_table_einj *einj_tab)
428{ 428{
429 if (einj_tab->header_length != sizeof(struct acpi_table_einj)) 429 if ((einj_tab->header_length !=
430 (sizeof(struct acpi_table_einj) - sizeof(einj_tab->header)))
431 && (einj_tab->header_length != sizeof(struct acpi_table_einj)))
430 return -EINVAL; 432 return -EINVAL;
431 if (einj_tab->header.length < sizeof(struct acpi_table_einj)) 433 if (einj_tab->header.length < sizeof(struct acpi_table_einj))
432 return -EINVAL; 434 return -EINVAL;
diff --git a/drivers/acpi/apei/erst-dbg.c b/drivers/acpi/apei/erst-dbg.c
new file mode 100644
index 000000000000..da1228a9a544
--- /dev/null
+++ b/drivers/acpi/apei/erst-dbg.c
@@ -0,0 +1,211 @@
1/*
2 * APEI Error Record Serialization Table debug support
3 *
4 * ERST is a way provided by APEI to save and retrieve hardware error
5 * information to and from a persistent store. This file provide the
6 * debugging/testing support for ERST kernel support and firmware
7 * implementation.
8 *
9 * Copyright 2010 Intel Corp.
10 * Author: Huang Ying <ying.huang@intel.com>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License version
14 * 2 as published by the Free Software Foundation.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26#include <linux/kernel.h>
27#include <linux/module.h>
28#include <linux/uaccess.h>
29#include <acpi/apei.h>
30#include <linux/miscdevice.h>
31
32#include "apei-internal.h"
33
34#define ERST_DBG_PFX "ERST DBG: "
35
36#define ERST_DBG_RECORD_LEN_MAX 4096
37
38static void *erst_dbg_buf;
39static unsigned int erst_dbg_buf_len;
40
41/* Prevent erst_dbg_read/write from being invoked concurrently */
42static DEFINE_MUTEX(erst_dbg_mutex);
43
44static int erst_dbg_open(struct inode *inode, struct file *file)
45{
46 if (erst_disable)
47 return -ENODEV;
48
49 return nonseekable_open(inode, file);
50}
51
52static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
53{
54 int rc;
55 u64 record_id;
56 u32 record_count;
57
58 switch (cmd) {
59 case APEI_ERST_CLEAR_RECORD:
60 rc = copy_from_user(&record_id, (void __user *)arg,
61 sizeof(record_id));
62 if (rc)
63 return -EFAULT;
64 return erst_clear(record_id);
65 case APEI_ERST_GET_RECORD_COUNT:
66 rc = erst_get_record_count();
67 if (rc < 0)
68 return rc;
69 record_count = rc;
70 rc = put_user(record_count, (u32 __user *)arg);
71 if (rc)
72 return rc;
73 return 0;
74 default:
75 return -ENOTTY;
76 }
77}
78
79static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
80 size_t usize, loff_t *off)
81{
82 int rc;
83 ssize_t len = 0;
84 u64 id;
85
86 if (*off != 0)
87 return -EINVAL;
88
89 if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
90 return -EINTR;
91
92retry_next:
93 rc = erst_get_next_record_id(&id);
94 if (rc)
95 goto out;
96 /* no more record */
97 if (id == APEI_ERST_INVALID_RECORD_ID)
98 goto out;
99retry:
100 rc = len = erst_read(id, erst_dbg_buf, erst_dbg_buf_len);
101 /* The record may be cleared by others, try read next record */
102 if (rc == -ENOENT)
103 goto retry_next;
104 if (rc < 0)
105 goto out;
106 if (len > ERST_DBG_RECORD_LEN_MAX) {
107 pr_warning(ERST_DBG_PFX
108 "Record (ID: 0x%llx) length is too long: %zd\n",
109 id, len);
110 rc = -EIO;
111 goto out;
112 }
113 if (len > erst_dbg_buf_len) {
114 void *p;
115 rc = -ENOMEM;
116 p = kmalloc(len, GFP_KERNEL);
117 if (!p)
118 goto out;
119 kfree(erst_dbg_buf);
120 erst_dbg_buf = p;
121 erst_dbg_buf_len = len;
122 goto retry;
123 }
124
125 rc = -EINVAL;
126 if (len > usize)
127 goto out;
128
129 rc = -EFAULT;
130 if (copy_to_user(ubuf, erst_dbg_buf, len))
131 goto out;
132 rc = 0;
133out:
134 mutex_unlock(&erst_dbg_mutex);
135 return rc ? rc : len;
136}
137
138static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
139 size_t usize, loff_t *off)
140{
141 int rc;
142 struct cper_record_header *rcd;
143
144 if (!capable(CAP_SYS_ADMIN))
145 return -EPERM;
146
147 if (usize > ERST_DBG_RECORD_LEN_MAX) {
148 pr_err(ERST_DBG_PFX "Too long record to be written\n");
149 return -EINVAL;
150 }
151
152 if (mutex_lock_interruptible(&erst_dbg_mutex))
153 return -EINTR;
154 if (usize > erst_dbg_buf_len) {
155 void *p;
156 rc = -ENOMEM;
157 p = kmalloc(usize, GFP_KERNEL);
158 if (!p)
159 goto out;
160 kfree(erst_dbg_buf);
161 erst_dbg_buf = p;
162 erst_dbg_buf_len = usize;
163 }
164 rc = copy_from_user(erst_dbg_buf, ubuf, usize);
165 if (rc) {
166 rc = -EFAULT;
167 goto out;
168 }
169 rcd = erst_dbg_buf;
170 rc = -EINVAL;
171 if (rcd->record_length != usize)
172 goto out;
173
174 rc = erst_write(erst_dbg_buf);
175
176out:
177 mutex_unlock(&erst_dbg_mutex);
178 return rc < 0 ? rc : usize;
179}
180
181static const struct file_operations erst_dbg_ops = {
182 .owner = THIS_MODULE,
183 .open = erst_dbg_open,
184 .read = erst_dbg_read,
185 .write = erst_dbg_write,
186 .unlocked_ioctl = erst_dbg_ioctl,
187};
188
189static struct miscdevice erst_dbg_dev = {
190 .minor = MISC_DYNAMIC_MINOR,
191 .name = "erst_dbg",
192 .fops = &erst_dbg_ops,
193};
194
195static __init int erst_dbg_init(void)
196{
197 return misc_register(&erst_dbg_dev);
198}
199
200static __exit void erst_dbg_exit(void)
201{
202 misc_deregister(&erst_dbg_dev);
203 kfree(erst_dbg_buf);
204}
205
206module_init(erst_dbg_init);
207module_exit(erst_dbg_exit);
208
209MODULE_AUTHOR("Huang Ying");
210MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
211MODULE_LICENSE("GPL");
diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c
index 18645f4e83cd..1211c03149e8 100644
--- a/drivers/acpi/apei/erst.c
+++ b/drivers/acpi/apei/erst.c
@@ -2,7 +2,7 @@
2 * APEI Error Record Serialization Table support 2 * APEI Error Record Serialization Table support
3 * 3 *
4 * ERST is a way provided by APEI to save and retrieve hardware error 4 * ERST is a way provided by APEI to save and retrieve hardware error
5 * infomation to and from a persistent store. 5 * information to and from a persistent store.
6 * 6 *
7 * For more information about ERST, please refer to ACPI Specification 7 * For more information about ERST, please refer to ACPI Specification
8 * version 4.0, section 17.4. 8 * version 4.0, section 17.4.
@@ -266,13 +266,30 @@ static int erst_exec_move_data(struct apei_exec_context *ctx,
266{ 266{
267 int rc; 267 int rc;
268 u64 offset; 268 u64 offset;
269 void *src, *dst;
270
271 /* ioremap does not work in interrupt context */
272 if (in_interrupt()) {
273 pr_warning(ERST_PFX
274 "MOVE_DATA can not be used in interrupt context");
275 return -EBUSY;
276 }
269 277
270 rc = __apei_exec_read_register(entry, &offset); 278 rc = __apei_exec_read_register(entry, &offset);
271 if (rc) 279 if (rc)
272 return rc; 280 return rc;
273 memmove((void *)ctx->dst_base + offset, 281
274 (void *)ctx->src_base + offset, 282 src = ioremap(ctx->src_base + offset, ctx->var2);
275 ctx->var2); 283 if (!src)
284 return -ENOMEM;
285 dst = ioremap(ctx->dst_base + offset, ctx->var2);
286 if (!dst)
287 return -ENOMEM;
288
289 memmove(dst, src, ctx->var2);
290
291 iounmap(src);
292 iounmap(dst);
276 293
277 return 0; 294 return 0;
278} 295}
@@ -750,7 +767,9 @@ __setup("erst_disable", setup_erst_disable);
750 767
751static int erst_check_table(struct acpi_table_erst *erst_tab) 768static int erst_check_table(struct acpi_table_erst *erst_tab)
752{ 769{
753 if (erst_tab->header_length != sizeof(struct acpi_table_erst)) 770 if ((erst_tab->header_length !=
771 (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header)))
772 && (erst_tab->header_length != sizeof(struct acpi_table_einj)))
754 return -EINVAL; 773 return -EINVAL;
755 if (erst_tab->header.length < sizeof(struct acpi_table_erst)) 774 if (erst_tab->header.length < sizeof(struct acpi_table_erst))
756 return -EINVAL; 775 return -EINVAL;
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index fd0cc016a099..0d505e59214d 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -41,6 +41,8 @@
41#include <linux/interrupt.h> 41#include <linux/interrupt.h>
42#include <linux/cper.h> 42#include <linux/cper.h>
43#include <linux/kdebug.h> 43#include <linux/kdebug.h>
44#include <linux/platform_device.h>
45#include <linux/mutex.h>
44#include <acpi/apei.h> 46#include <acpi/apei.h>
45#include <acpi/atomicio.h> 47#include <acpi/atomicio.h>
46#include <acpi/hed.h> 48#include <acpi/hed.h>
@@ -87,6 +89,7 @@ struct ghes {
87 * used for that. 89 * used for that.
88 */ 90 */
89static LIST_HEAD(ghes_sci); 91static LIST_HEAD(ghes_sci);
92static DEFINE_MUTEX(ghes_list_mutex);
90 93
91static struct ghes *ghes_new(struct acpi_hest_generic *generic) 94static struct ghes *ghes_new(struct acpi_hest_generic *generic)
92{ 95{
@@ -132,26 +135,26 @@ static void ghes_fini(struct ghes *ghes)
132} 135}
133 136
134enum { 137enum {
135 GHES_SER_NO = 0x0, 138 GHES_SEV_NO = 0x0,
136 GHES_SER_CORRECTED = 0x1, 139 GHES_SEV_CORRECTED = 0x1,
137 GHES_SER_RECOVERABLE = 0x2, 140 GHES_SEV_RECOVERABLE = 0x2,
138 GHES_SER_PANIC = 0x3, 141 GHES_SEV_PANIC = 0x3,
139}; 142};
140 143
141static inline int ghes_severity(int severity) 144static inline int ghes_severity(int severity)
142{ 145{
143 switch (severity) { 146 switch (severity) {
144 case CPER_SER_INFORMATIONAL: 147 case CPER_SEV_INFORMATIONAL:
145 return GHES_SER_NO; 148 return GHES_SEV_NO;
146 case CPER_SER_CORRECTED: 149 case CPER_SEV_CORRECTED:
147 return GHES_SER_CORRECTED; 150 return GHES_SEV_CORRECTED;
148 case CPER_SER_RECOVERABLE: 151 case CPER_SEV_RECOVERABLE:
149 return GHES_SER_RECOVERABLE; 152 return GHES_SEV_RECOVERABLE;
150 case CPER_SER_FATAL: 153 case CPER_SEV_FATAL:
151 return GHES_SER_PANIC; 154 return GHES_SEV_PANIC;
152 default: 155 default:
153 /* Unkown, go panic */ 156 /* Unkown, go panic */
154 return GHES_SER_PANIC; 157 return GHES_SEV_PANIC;
155 } 158 }
156} 159}
157 160
@@ -237,16 +240,16 @@ static void ghes_clear_estatus(struct ghes *ghes)
237 240
238static void ghes_do_proc(struct ghes *ghes) 241static void ghes_do_proc(struct ghes *ghes)
239{ 242{
240 int ser, processed = 0; 243 int sev, processed = 0;
241 struct acpi_hest_generic_data *gdata; 244 struct acpi_hest_generic_data *gdata;
242 245
243 ser = ghes_severity(ghes->estatus->error_severity); 246 sev = ghes_severity(ghes->estatus->error_severity);
244 apei_estatus_for_each_section(ghes->estatus, gdata) { 247 apei_estatus_for_each_section(ghes->estatus, gdata) {
245#ifdef CONFIG_X86_MCE 248#ifdef CONFIG_X86_MCE
246 if (!uuid_le_cmp(*(uuid_le *)gdata->section_type, 249 if (!uuid_le_cmp(*(uuid_le *)gdata->section_type,
247 CPER_SEC_PLATFORM_MEM)) { 250 CPER_SEC_PLATFORM_MEM)) {
248 apei_mce_report_mem_error( 251 apei_mce_report_mem_error(
249 ser == GHES_SER_CORRECTED, 252 sev == GHES_SEV_CORRECTED,
250 (struct cper_sec_mem_err *)(gdata+1)); 253 (struct cper_sec_mem_err *)(gdata+1));
251 processed = 1; 254 processed = 1;
252 } 255 }
@@ -293,18 +296,15 @@ static struct notifier_block ghes_notifier_sci = {
293 .notifier_call = ghes_notify_sci, 296 .notifier_call = ghes_notify_sci,
294}; 297};
295 298
296static int hest_ghes_parse(struct acpi_hest_header *hest_hdr, void *data) 299static int __devinit ghes_probe(struct platform_device *ghes_dev)
297{ 300{
298 struct acpi_hest_generic *generic; 301 struct acpi_hest_generic *generic;
299 struct ghes *ghes = NULL; 302 struct ghes *ghes = NULL;
300 int rc = 0; 303 int rc = -EINVAL;
301 304
302 if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) 305 generic = *(struct acpi_hest_generic **)ghes_dev->dev.platform_data;
303 return 0;
304
305 generic = (struct acpi_hest_generic *)hest_hdr;
306 if (!generic->enabled) 306 if (!generic->enabled)
307 return 0; 307 return -ENODEV;
308 308
309 if (generic->error_block_length < 309 if (generic->error_block_length <
310 sizeof(struct acpi_hest_generic_status)) { 310 sizeof(struct acpi_hest_generic_status)) {
@@ -327,62 +327,91 @@ static int hest_ghes_parse(struct acpi_hest_header *hest_hdr, void *data)
327 ghes = NULL; 327 ghes = NULL;
328 goto err; 328 goto err;
329 } 329 }
330 switch (generic->notify.type) { 330 if (generic->notify.type == ACPI_HEST_NOTIFY_SCI) {
331 case ACPI_HEST_NOTIFY_POLLED: 331 mutex_lock(&ghes_list_mutex);
332 pr_warning(GHES_PFX
333"Generic hardware error source: %d notified via POLL is not supported!\n",
334 generic->header.source_id);
335 break;
336 case ACPI_HEST_NOTIFY_EXTERNAL:
337 case ACPI_HEST_NOTIFY_LOCAL:
338 pr_warning(GHES_PFX
339"Generic hardware error source: %d notified via IRQ is not supported!\n",
340 generic->header.source_id);
341 break;
342 case ACPI_HEST_NOTIFY_SCI:
343 if (list_empty(&ghes_sci)) 332 if (list_empty(&ghes_sci))
344 register_acpi_hed_notifier(&ghes_notifier_sci); 333 register_acpi_hed_notifier(&ghes_notifier_sci);
345 list_add_rcu(&ghes->list, &ghes_sci); 334 list_add_rcu(&ghes->list, &ghes_sci);
346 break; 335 mutex_unlock(&ghes_list_mutex);
347 case ACPI_HEST_NOTIFY_NMI: 336 } else {
348 pr_warning(GHES_PFX 337 unsigned char *notify = NULL;
349"Generic hardware error source: %d notified via NMI is not supported!\n", 338
350 generic->header.source_id); 339 switch (generic->notify.type) {
351 break; 340 case ACPI_HEST_NOTIFY_POLLED:
352 default: 341 notify = "POLL";
353 pr_warning(FW_WARN GHES_PFX 342 break;
354 "Unknown notification type: %u for generic hardware error source: %d\n", 343 case ACPI_HEST_NOTIFY_EXTERNAL:
355 generic->notify.type, generic->header.source_id); 344 case ACPI_HEST_NOTIFY_LOCAL:
356 break; 345 notify = "IRQ";
346 break;
347 case ACPI_HEST_NOTIFY_NMI:
348 notify = "NMI";
349 break;
350 }
351 if (notify) {
352 pr_warning(GHES_PFX
353"Generic hardware error source: %d notified via %s is not supported!\n",
354 generic->header.source_id, notify);
355 } else {
356 pr_warning(FW_WARN GHES_PFX
357"Unknown notification type: %u for generic hardware error source: %d\n",
358 generic->notify.type, generic->header.source_id);
359 }
360 rc = -ENODEV;
361 goto err;
357 } 362 }
363 platform_set_drvdata(ghes_dev, ghes);
358 364
359 return 0; 365 return 0;
360err: 366err:
361 if (ghes) 367 if (ghes) {
362 ghes_fini(ghes); 368 ghes_fini(ghes);
369 kfree(ghes);
370 }
363 return rc; 371 return rc;
364} 372}
365 373
366static void ghes_cleanup(void) 374static int __devexit ghes_remove(struct platform_device *ghes_dev)
367{ 375{
368 struct ghes *ghes, *nghes; 376 struct ghes *ghes;
377 struct acpi_hest_generic *generic;
369 378
370 if (!list_empty(&ghes_sci)) 379 ghes = platform_get_drvdata(ghes_dev);
371 unregister_acpi_hed_notifier(&ghes_notifier_sci); 380 generic = ghes->generic;
381
382 switch (generic->notify.type) {
383 case ACPI_HEST_NOTIFY_SCI:
384 mutex_lock(&ghes_list_mutex);
385 list_del_rcu(&ghes->list);
386 if (list_empty(&ghes_sci))
387 unregister_acpi_hed_notifier(&ghes_notifier_sci);
388 mutex_unlock(&ghes_list_mutex);
389 break;
390 default:
391 BUG();
392 break;
393 }
372 394
373 synchronize_rcu(); 395 synchronize_rcu();
396 ghes_fini(ghes);
397 kfree(ghes);
374 398
375 list_for_each_entry_safe(ghes, nghes, &ghes_sci, list) { 399 platform_set_drvdata(ghes_dev, NULL);
376 list_del(&ghes->list); 400
377 ghes_fini(ghes); 401 return 0;
378 kfree(ghes);
379 }
380} 402}
381 403
404static struct platform_driver ghes_platform_driver = {
405 .driver = {
406 .name = "GHES",
407 .owner = THIS_MODULE,
408 },
409 .probe = ghes_probe,
410 .remove = ghes_remove,
411};
412
382static int __init ghes_init(void) 413static int __init ghes_init(void)
383{ 414{
384 int rc;
385
386 if (acpi_disabled) 415 if (acpi_disabled)
387 return -ENODEV; 416 return -ENODEV;
388 417
@@ -391,32 +420,12 @@ static int __init ghes_init(void)
391 return -EINVAL; 420 return -EINVAL;
392 } 421 }
393 422
394 rc = apei_hest_parse(hest_ghes_parse, NULL); 423 return platform_driver_register(&ghes_platform_driver);
395 if (rc) {
396 pr_err(GHES_PFX
397 "Error during parsing HEST generic hardware error sources.\n");
398 goto err_cleanup;
399 }
400
401 if (list_empty(&ghes_sci)) {
402 pr_info(GHES_PFX
403 "No functional generic hardware error sources.\n");
404 rc = -ENODEV;
405 goto err_cleanup;
406 }
407
408 pr_info(GHES_PFX
409 "Generic Hardware Error Source support is initialized.\n");
410
411 return 0;
412err_cleanup:
413 ghes_cleanup();
414 return rc;
415} 424}
416 425
417static void __exit ghes_exit(void) 426static void __exit ghes_exit(void)
418{ 427{
419 ghes_cleanup(); 428 platform_driver_unregister(&ghes_platform_driver);
420} 429}
421 430
422module_init(ghes_init); 431module_init(ghes_init);
@@ -425,3 +434,4 @@ module_exit(ghes_exit);
425MODULE_AUTHOR("Huang Ying"); 434MODULE_AUTHOR("Huang Ying");
426MODULE_DESCRIPTION("APEI Generic Hardware Error Source support"); 435MODULE_DESCRIPTION("APEI Generic Hardware Error Source support");
427MODULE_LICENSE("GPL"); 436MODULE_LICENSE("GPL");
437MODULE_ALIAS("platform:GHES");
diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c
index e7f40d362cb3..1a3508a7fe03 100644
--- a/drivers/acpi/apei/hest.c
+++ b/drivers/acpi/apei/hest.c
@@ -34,6 +34,7 @@
34#include <linux/kdebug.h> 34#include <linux/kdebug.h>
35#include <linux/highmem.h> 35#include <linux/highmem.h>
36#include <linux/io.h> 36#include <linux/io.h>
37#include <linux/platform_device.h>
37#include <acpi/apei.h> 38#include <acpi/apei.h>
38 39
39#include "apei-internal.h" 40#include "apei-internal.h"
@@ -47,11 +48,6 @@ EXPORT_SYMBOL_GPL(hest_disable);
47 48
48static struct acpi_table_hest *hest_tab; 49static struct acpi_table_hest *hest_tab;
49 50
50static int hest_void_parse(struct acpi_hest_header *hest_hdr, void *data)
51{
52 return 0;
53}
54
55static int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { 51static int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = {
56 [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ 52 [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */
57 [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, 53 [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1,
@@ -125,6 +121,72 @@ int apei_hest_parse(apei_hest_func_t func, void *data)
125} 121}
126EXPORT_SYMBOL_GPL(apei_hest_parse); 122EXPORT_SYMBOL_GPL(apei_hest_parse);
127 123
124struct ghes_arr {
125 struct platform_device **ghes_devs;
126 unsigned int count;
127};
128
129static int hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)
130{
131 int *count = data;
132
133 if (hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR)
134 (*count)++;
135 return 0;
136}
137
138static int hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
139{
140 struct platform_device *ghes_dev;
141 struct ghes_arr *ghes_arr = data;
142 int rc;
143
144 if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR)
145 return 0;
146
147 if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
148 return 0;
149 ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
150 if (!ghes_dev)
151 return -ENOMEM;
152
153 rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
154 if (rc)
155 goto err;
156
157 rc = platform_device_add(ghes_dev);
158 if (rc)
159 goto err;
160 ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;
161
162 return 0;
163err:
164 platform_device_put(ghes_dev);
165 return rc;
166}
167
168static int hest_ghes_dev_register(unsigned int ghes_count)
169{
170 int rc, i;
171 struct ghes_arr ghes_arr;
172
173 ghes_arr.count = 0;
174 ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL);
175 if (!ghes_arr.ghes_devs)
176 return -ENOMEM;
177
178 rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);
179 if (rc)
180 goto err;
181out:
182 kfree(ghes_arr.ghes_devs);
183 return rc;
184err:
185 for (i = 0; i < ghes_arr.count; i++)
186 platform_device_unregister(ghes_arr.ghes_devs[i]);
187 goto out;
188}
189
128static int __init setup_hest_disable(char *str) 190static int __init setup_hest_disable(char *str)
129{ 191{
130 hest_disable = 1; 192 hest_disable = 1;
@@ -137,6 +199,7 @@ static int __init hest_init(void)
137{ 199{
138 acpi_status status; 200 acpi_status status;
139 int rc = -ENODEV; 201 int rc = -ENODEV;
202 unsigned int ghes_count = 0;
140 203
141 if (acpi_disabled) 204 if (acpi_disabled)
142 goto err; 205 goto err;
@@ -158,7 +221,11 @@ static int __init hest_init(void)
158 goto err; 221 goto err;
159 } 222 }
160 223
161 rc = apei_hest_parse(hest_void_parse, NULL); 224 rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);
225 if (rc)
226 goto err;
227
228 rc = hest_ghes_dev_register(ghes_count);
162 if (rc) 229 if (rc)
163 goto err; 230 goto err;
164 231