diff options
| author | Len Brown <len.brown@intel.com> | 2010-08-14 23:55:47 -0400 | 
|---|---|---|
| committer | Len Brown <len.brown@intel.com> | 2010-08-14 23:55:47 -0400 | 
| commit | feb29c5175e61d0f1ec2cbcaccdfa55e588780be (patch) | |
| tree | e904f95a4ef4e601af0c2d6619671b8a210c8a65 | |
| parent | f2a66185bed21427d8d347a42eaf0ad1d3d9fc63 (diff) | |
| parent | 2ff729d506e8db82d76a93bc963df4d0a4d46b57 (diff) | |
Merge branch 'apei' into release
| -rw-r--r-- | arch/x86/kernel/cpu/mcheck/mce-apei.c | 4 | ||||
| -rw-r--r-- | drivers/acpi/apei/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/acpi/apei/Makefile | 1 | ||||
| -rw-r--r-- | drivers/acpi/apei/apei-base.c | 4 | ||||
| -rw-r--r-- | drivers/acpi/apei/erst-dbg.c | 207 | ||||
| -rw-r--r-- | drivers/acpi/apei/ghes.c | 172 | ||||
| -rw-r--r-- | drivers/acpi/apei/hest.c | 76 | ||||
| -rw-r--r-- | include/linux/cper.h | 8 | 
8 files changed, 386 insertions, 95 deletions
| diff --git a/arch/x86/kernel/cpu/mcheck/mce-apei.c b/arch/x86/kernel/cpu/mcheck/mce-apei.c index 745b54f9be89..8209472b27a5 100644 --- a/arch/x86/kernel/cpu/mcheck/mce-apei.c +++ b/arch/x86/kernel/cpu/mcheck/mce-apei.c | |||
| @@ -80,7 +80,7 @@ int apei_write_mce(struct mce *m) | |||
| 80 | rcd.hdr.revision = CPER_RECORD_REV; | 80 | rcd.hdr.revision = CPER_RECORD_REV; | 
| 81 | rcd.hdr.signature_end = CPER_SIG_END; | 81 | rcd.hdr.signature_end = CPER_SIG_END; | 
| 82 | rcd.hdr.section_count = 1; | 82 | rcd.hdr.section_count = 1; | 
| 83 | rcd.hdr.error_severity = CPER_SER_FATAL; | 83 | rcd.hdr.error_severity = CPER_SEV_FATAL; | 
| 84 | /* timestamp, platform_id, partition_id are all invalid */ | 84 | /* timestamp, platform_id, partition_id are all invalid */ | 
| 85 | rcd.hdr.validation_bits = 0; | 85 | rcd.hdr.validation_bits = 0; | 
| 86 | rcd.hdr.record_length = sizeof(rcd); | 86 | rcd.hdr.record_length = sizeof(rcd); | 
| @@ -96,7 +96,7 @@ int apei_write_mce(struct mce *m) | |||
| 96 | rcd.sec_hdr.validation_bits = 0; | 96 | rcd.sec_hdr.validation_bits = 0; | 
| 97 | rcd.sec_hdr.flags = CPER_SEC_PRIMARY; | 97 | rcd.sec_hdr.flags = CPER_SEC_PRIMARY; | 
| 98 | rcd.sec_hdr.section_type = CPER_SECTION_TYPE_MCE; | 98 | rcd.sec_hdr.section_type = CPER_SECTION_TYPE_MCE; | 
| 99 | rcd.sec_hdr.section_severity = CPER_SER_FATAL; | 99 | rcd.sec_hdr.section_severity = CPER_SEV_FATAL; | 
| 100 | 100 | ||
| 101 | memcpy(&rcd.mce, m, sizeof(*m)); | 101 | memcpy(&rcd.mce, m, sizeof(*m)); | 
| 102 | 102 | ||
| diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index f8c668f27b5a..907e350f1c7d 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 | |||
| 32 | config 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 infomation 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 @@ | |||
| 1 | obj-$(CONFIG_ACPI_APEI) += apei.o | 1 | obj-$(CONFIG_ACPI_APEI) += apei.o | 
| 2 | obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o | 2 | obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o | 
| 3 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o | 3 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o | 
| 4 | obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o | ||
| 4 | 5 | ||
| 5 | apei-y := apei-base.o hest.o cper.o erst.o | 6 | apei-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..73fd0c7487c1 100644 --- a/drivers/acpi/apei/apei-base.c +++ b/drivers/acpi/apei/apei-base.c | |||
| @@ -482,14 +482,14 @@ err_unmap_ioport: | |||
| 482 | list_for_each_entry(res, &resources->ioport, list) { | 482 | list_for_each_entry(res, &resources->ioport, list) { | 
| 483 | if (res == res_bak) | 483 | if (res == res_bak) | 
| 484 | break; | 484 | break; | 
| 485 | release_mem_region(res->start, res->end - res->start); | 485 | release_region(res->start, res->end - res->start); | 
| 486 | } | 486 | } | 
| 487 | res_bak = NULL; | 487 | res_bak = NULL; | 
| 488 | err_unmap_iomem: | 488 | err_unmap_iomem: | 
| 489 | list_for_each_entry(res, &resources->iomem, list) { | 489 | list_for_each_entry(res, &resources->iomem, list) { | 
| 490 | if (res == res_bak) | 490 | if (res == res_bak) | 
| 491 | break; | 491 | break; | 
| 492 | release_region(res->start, res->end - res->start); | 492 | release_mem_region(res->start, res->end - res->start); | 
| 493 | } | 493 | } | 
| 494 | return -EINVAL; | 494 | return -EINVAL; | 
| 495 | } | 495 | } | 
| diff --git a/drivers/acpi/apei/erst-dbg.c b/drivers/acpi/apei/erst-dbg.c new file mode 100644 index 000000000000..5281ddda2777 --- /dev/null +++ b/drivers/acpi/apei/erst-dbg.c | |||
| @@ -0,0 +1,207 @@ | |||
| 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 | * infomation 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 | |||
| 38 | static void *erst_dbg_buf; | ||
| 39 | static unsigned int erst_dbg_buf_len; | ||
| 40 | |||
| 41 | /* Prevent erst_dbg_read/write from being invoked concurrently */ | ||
| 42 | static DEFINE_MUTEX(erst_dbg_mutex); | ||
| 43 | |||
| 44 | static 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 | |||
| 52 | static 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 | |||
| 79 | static 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 | |||
| 92 | retry_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; | ||
| 99 | retry: | ||
| 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 | kfree(erst_dbg_buf); | ||
| 115 | rc = -ENOMEM; | ||
| 116 | erst_dbg_buf = kmalloc(len, GFP_KERNEL); | ||
| 117 | if (!erst_dbg_buf) | ||
| 118 | goto out; | ||
| 119 | erst_dbg_buf_len = len; | ||
| 120 | goto retry; | ||
| 121 | } | ||
| 122 | |||
| 123 | rc = -EINVAL; | ||
| 124 | if (len > usize) | ||
| 125 | goto out; | ||
| 126 | |||
| 127 | rc = -EFAULT; | ||
| 128 | if (copy_to_user(ubuf, erst_dbg_buf, len)) | ||
| 129 | goto out; | ||
| 130 | rc = 0; | ||
| 131 | out: | ||
| 132 | mutex_unlock(&erst_dbg_mutex); | ||
| 133 | return rc ? rc : len; | ||
| 134 | } | ||
| 135 | |||
| 136 | static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf, | ||
| 137 | size_t usize, loff_t *off) | ||
| 138 | { | ||
| 139 | int rc; | ||
| 140 | struct cper_record_header *rcd; | ||
| 141 | |||
| 142 | if (!capable(CAP_SYS_ADMIN)) | ||
| 143 | return -EPERM; | ||
| 144 | |||
| 145 | if (usize > ERST_DBG_RECORD_LEN_MAX) { | ||
| 146 | pr_err(ERST_DBG_PFX "Too long record to be written\n"); | ||
| 147 | return -EINVAL; | ||
| 148 | } | ||
| 149 | |||
| 150 | if (mutex_lock_interruptible(&erst_dbg_mutex)) | ||
| 151 | return -EINTR; | ||
| 152 | if (usize > erst_dbg_buf_len) { | ||
| 153 | kfree(erst_dbg_buf); | ||
| 154 | rc = -ENOMEM; | ||
| 155 | erst_dbg_buf = kmalloc(usize, GFP_KERNEL); | ||
| 156 | if (!erst_dbg_buf) | ||
| 157 | goto out; | ||
| 158 | erst_dbg_buf_len = usize; | ||
| 159 | } | ||
| 160 | rc = copy_from_user(erst_dbg_buf, ubuf, usize); | ||
| 161 | if (rc) { | ||
| 162 | rc = -EFAULT; | ||
| 163 | goto out; | ||
| 164 | } | ||
| 165 | rcd = erst_dbg_buf; | ||
| 166 | rc = -EINVAL; | ||
| 167 | if (rcd->record_length != usize) | ||
| 168 | goto out; | ||
| 169 | |||
| 170 | rc = erst_write(erst_dbg_buf); | ||
| 171 | |||
| 172 | out: | ||
| 173 | mutex_unlock(&erst_dbg_mutex); | ||
| 174 | return rc < 0 ? rc : usize; | ||
| 175 | } | ||
| 176 | |||
| 177 | static const struct file_operations erst_dbg_ops = { | ||
| 178 | .owner = THIS_MODULE, | ||
| 179 | .open = erst_dbg_open, | ||
| 180 | .read = erst_dbg_read, | ||
| 181 | .write = erst_dbg_write, | ||
| 182 | .unlocked_ioctl = erst_dbg_ioctl, | ||
| 183 | }; | ||
| 184 | |||
| 185 | static struct miscdevice erst_dbg_dev = { | ||
| 186 | .minor = MISC_DYNAMIC_MINOR, | ||
| 187 | .name = "erst_dbg", | ||
| 188 | .fops = &erst_dbg_ops, | ||
| 189 | }; | ||
| 190 | |||
| 191 | static __init int erst_dbg_init(void) | ||
| 192 | { | ||
| 193 | return misc_register(&erst_dbg_dev); | ||
| 194 | } | ||
| 195 | |||
| 196 | static __exit void erst_dbg_exit(void) | ||
| 197 | { | ||
| 198 | misc_deregister(&erst_dbg_dev); | ||
| 199 | kfree(erst_dbg_buf); | ||
| 200 | } | ||
| 201 | |||
| 202 | module_init(erst_dbg_init); | ||
| 203 | module_exit(erst_dbg_exit); | ||
| 204 | |||
| 205 | MODULE_AUTHOR("Huang Ying"); | ||
| 206 | MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support"); | ||
| 207 | MODULE_LICENSE("GPL"); | ||
| diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index fd0cc016a099..385a6059714a 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 | */ | 
| 89 | static LIST_HEAD(ghes_sci); | 91 | static LIST_HEAD(ghes_sci); | 
| 92 | static DEFINE_MUTEX(ghes_list_mutex); | ||
| 90 | 93 | ||
| 91 | static struct ghes *ghes_new(struct acpi_hest_generic *generic) | 94 | static 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 | ||
| 134 | enum { | 137 | enum { | 
| 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 | ||
| 141 | static inline int ghes_severity(int severity) | 144 | static 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 | ||
| 238 | static void ghes_do_proc(struct ghes *ghes) | 241 | static 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 | ||
| 296 | static int hest_ghes_parse(struct acpi_hest_header *hest_hdr, void *data) | 299 | static 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 = 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; | 
| 360 | err: | 366 | err: | 
| 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 | ||
| 366 | static void ghes_cleanup(void) | 374 | static 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 | ||
| 404 | static 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 | |||
| 382 | static int __init ghes_init(void) | 413 | static 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; | ||
| 412 | err_cleanup: | ||
| 413 | ghes_cleanup(); | ||
| 414 | return rc; | ||
| 415 | } | 424 | } | 
| 416 | 425 | ||
| 417 | static void __exit ghes_exit(void) | 426 | static void __exit ghes_exit(void) | 
| 418 | { | 427 | { | 
| 419 | ghes_cleanup(); | 428 | platform_driver_unregister(&ghes_platform_driver); | 
| 420 | } | 429 | } | 
| 421 | 430 | ||
| 422 | module_init(ghes_init); | 431 | module_init(ghes_init); | 
| @@ -425,3 +434,4 @@ module_exit(ghes_exit); | |||
| 425 | MODULE_AUTHOR("Huang Ying"); | 434 | MODULE_AUTHOR("Huang Ying"); | 
| 426 | MODULE_DESCRIPTION("APEI Generic Hardware Error Source support"); | 435 | MODULE_DESCRIPTION("APEI Generic Hardware Error Source support"); | 
| 427 | MODULE_LICENSE("GPL"); | 436 | MODULE_LICENSE("GPL"); | 
| 437 | MODULE_ALIAS("platform:GHES"); | ||
| diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c index e7f40d362cb3..343168d18266 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 | ||
| 48 | static struct acpi_table_hest *hest_tab; | 49 | static struct acpi_table_hest *hest_tab; | 
| 49 | 50 | ||
| 50 | static int hest_void_parse(struct acpi_hest_header *hest_hdr, void *data) | ||
| 51 | { | ||
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | static int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = { | 51 | static 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,69 @@ int apei_hest_parse(apei_hest_func_t func, void *data) | |||
| 125 | } | 121 | } | 
| 126 | EXPORT_SYMBOL_GPL(apei_hest_parse); | 122 | EXPORT_SYMBOL_GPL(apei_hest_parse); | 
| 127 | 123 | ||
| 124 | struct ghes_arr { | ||
| 125 | struct platform_device **ghes_devs; | ||
| 126 | unsigned int count; | ||
| 127 | }; | ||
| 128 | |||
| 129 | static 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 | |||
| 138 | static int hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data) | ||
| 139 | { | ||
| 140 | struct acpi_hest_generic *generic; | ||
| 141 | struct platform_device *ghes_dev; | ||
| 142 | struct ghes_arr *ghes_arr = data; | ||
| 143 | int rc; | ||
| 144 | |||
| 145 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) | ||
| 146 | return 0; | ||
| 147 | generic = (struct acpi_hest_generic *)hest_hdr; | ||
| 148 | if (!generic->enabled) | ||
| 149 | return 0; | ||
| 150 | ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id); | ||
| 151 | if (!ghes_dev) | ||
| 152 | return -ENOMEM; | ||
| 153 | ghes_dev->dev.platform_data = generic; | ||
| 154 | rc = platform_device_add(ghes_dev); | ||
| 155 | if (rc) | ||
| 156 | goto err; | ||
| 157 | ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev; | ||
| 158 | |||
| 159 | return 0; | ||
| 160 | err: | ||
| 161 | platform_device_put(ghes_dev); | ||
| 162 | return rc; | ||
| 163 | } | ||
| 164 | |||
| 165 | static int hest_ghes_dev_register(unsigned int ghes_count) | ||
| 166 | { | ||
| 167 | int rc, i; | ||
| 168 | struct ghes_arr ghes_arr; | ||
| 169 | |||
| 170 | ghes_arr.count = 0; | ||
| 171 | ghes_arr.ghes_devs = kmalloc(sizeof(void *) * ghes_count, GFP_KERNEL); | ||
| 172 | if (!ghes_arr.ghes_devs) | ||
| 173 | return -ENOMEM; | ||
| 174 | |||
| 175 | rc = apei_hest_parse(hest_parse_ghes, &ghes_arr); | ||
| 176 | if (rc) | ||
| 177 | goto err; | ||
| 178 | out: | ||
| 179 | kfree(ghes_arr.ghes_devs); | ||
| 180 | return rc; | ||
| 181 | err: | ||
| 182 | for (i = 0; i < ghes_arr.count; i++) | ||
| 183 | platform_device_unregister(ghes_arr.ghes_devs[i]); | ||
| 184 | goto out; | ||
| 185 | } | ||
| 186 | |||
| 128 | static int __init setup_hest_disable(char *str) | 187 | static int __init setup_hest_disable(char *str) | 
| 129 | { | 188 | { | 
| 130 | hest_disable = 1; | 189 | hest_disable = 1; | 
| @@ -137,6 +196,7 @@ static int __init hest_init(void) | |||
| 137 | { | 196 | { | 
| 138 | acpi_status status; | 197 | acpi_status status; | 
| 139 | int rc = -ENODEV; | 198 | int rc = -ENODEV; | 
| 199 | unsigned int ghes_count = 0; | ||
| 140 | 200 | ||
| 141 | if (acpi_disabled) | 201 | if (acpi_disabled) | 
| 142 | goto err; | 202 | goto err; | 
| @@ -158,7 +218,11 @@ static int __init hest_init(void) | |||
| 158 | goto err; | 218 | goto err; | 
| 159 | } | 219 | } | 
| 160 | 220 | ||
| 161 | rc = apei_hest_parse(hest_void_parse, NULL); | 221 | rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count); | 
| 222 | if (rc) | ||
| 223 | goto err; | ||
| 224 | |||
| 225 | rc = hest_ghes_dev_register(ghes_count); | ||
| 162 | if (rc) | 226 | if (rc) | 
| 163 | goto err; | 227 | goto err; | 
| 164 | 228 | ||
| diff --git a/include/linux/cper.h b/include/linux/cper.h index 4b38f905b705..bf972f81e2a7 100644 --- a/include/linux/cper.h +++ b/include/linux/cper.h | |||
| @@ -39,10 +39,10 @@ | |||
| 39 | * Severity difinition for error_severity in struct cper_record_header | 39 | * Severity difinition for error_severity in struct cper_record_header | 
| 40 | * and section_severity in struct cper_section_descriptor | 40 | * and section_severity in struct cper_section_descriptor | 
| 41 | */ | 41 | */ | 
| 42 | #define CPER_SER_RECOVERABLE 0x0 | 42 | #define CPER_SEV_RECOVERABLE 0x0 | 
| 43 | #define CPER_SER_FATAL 0x1 | 43 | #define CPER_SEV_FATAL 0x1 | 
| 44 | #define CPER_SER_CORRECTED 0x2 | 44 | #define CPER_SEV_CORRECTED 0x2 | 
| 45 | #define CPER_SER_INFORMATIONAL 0x3 | 45 | #define CPER_SEV_INFORMATIONAL 0x3 | 
| 46 | 46 | ||
| 47 | /* | 47 | /* | 
| 48 | * Validation bits difinition for validation_bits in struct | 48 | * Validation bits difinition for validation_bits in struct | 
