diff options
49 files changed, 4436 insertions, 408 deletions
diff --git a/Documentation/acpi/apei/einj.txt b/Documentation/acpi/apei/einj.txt new file mode 100644 index 000000000000..dfab71848dc8 --- /dev/null +++ b/Documentation/acpi/apei/einj.txt | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | APEI Error INJection | ||
| 2 | ~~~~~~~~~~~~~~~~~~~~ | ||
| 3 | |||
| 4 | EINJ provides a hardware error injection mechanism | ||
| 5 | It is very useful for debugging and testing of other APEI and RAS features. | ||
| 6 | |||
| 7 | To use EINJ, make sure the following are enabled in your kernel | ||
| 8 | configuration: | ||
| 9 | |||
| 10 | CONFIG_DEBUG_FS | ||
| 11 | CONFIG_ACPI_APEI | ||
| 12 | CONFIG_ACPI_APEI_EINJ | ||
| 13 | |||
| 14 | The user interface of EINJ is debug file system, under the | ||
| 15 | directory apei/einj. The following files are provided. | ||
| 16 | |||
| 17 | - available_error_type | ||
| 18 | Reading this file returns the error injection capability of the | ||
| 19 | platform, that is, which error types are supported. The error type | ||
| 20 | definition is as follow, the left field is the error type value, the | ||
| 21 | right field is error description. | ||
| 22 | |||
| 23 | 0x00000001 Processor Correctable | ||
| 24 | 0x00000002 Processor Uncorrectable non-fatal | ||
| 25 | 0x00000004 Processor Uncorrectable fatal | ||
| 26 | 0x00000008 Memory Correctable | ||
| 27 | 0x00000010 Memory Uncorrectable non-fatal | ||
| 28 | 0x00000020 Memory Uncorrectable fatal | ||
| 29 | 0x00000040 PCI Express Correctable | ||
| 30 | 0x00000080 PCI Express Uncorrectable fatal | ||
| 31 | 0x00000100 PCI Express Uncorrectable non-fatal | ||
| 32 | 0x00000200 Platform Correctable | ||
| 33 | 0x00000400 Platform Uncorrectable non-fatal | ||
| 34 | 0x00000800 Platform Uncorrectable fatal | ||
| 35 | |||
| 36 | The format of file contents are as above, except there are only the | ||
| 37 | available error type lines. | ||
| 38 | |||
| 39 | - error_type | ||
| 40 | This file is used to set the error type value. The error type value | ||
| 41 | is defined in "available_error_type" description. | ||
| 42 | |||
| 43 | - error_inject | ||
| 44 | Write any integer to this file to trigger the error | ||
| 45 | injection. Before this, please specify all necessary error | ||
| 46 | parameters. | ||
| 47 | |||
| 48 | - param1 | ||
| 49 | This file is used to set the first error parameter value. Effect of | ||
| 50 | parameter depends on error_type specified. For memory error, this is | ||
| 51 | physical memory address. | ||
| 52 | |||
| 53 | - param2 | ||
| 54 | This file is used to set the second error parameter value. Effect of | ||
| 55 | parameter depends on error_type specified. For memory error, this is | ||
| 56 | physical memory address mask. | ||
| 57 | |||
| 58 | For more information about EINJ, please refer to ACPI specification | ||
| 59 | version 4.0, section 17.5. | ||
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 839b21b0699a..42d77735fd45 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
| @@ -750,6 +750,10 @@ and is between 256 and 4096 characters. It is defined in the file | |||
| 750 | Default value is 0. | 750 | Default value is 0. |
| 751 | Value can be changed at runtime via /selinux/enforce. | 751 | Value can be changed at runtime via /selinux/enforce. |
| 752 | 752 | ||
| 753 | erst_disable [ACPI] | ||
| 754 | Disable Error Record Serialization Table (ERST) | ||
| 755 | support. | ||
| 756 | |||
| 753 | ether= [HW,NET] Ethernet cards parameters | 757 | ether= [HW,NET] Ethernet cards parameters |
| 754 | This option is obsoleted by the "netdev=" option, which | 758 | This option is obsoleted by the "netdev=" option, which |
| 755 | has equivalent usage. See its documentation for details. | 759 | has equivalent usage. See its documentation for details. |
| @@ -843,6 +847,11 @@ and is between 256 and 4096 characters. It is defined in the file | |||
| 843 | hd= [EIDE] (E)IDE hard drive subsystem geometry | 847 | hd= [EIDE] (E)IDE hard drive subsystem geometry |
| 844 | Format: <cyl>,<head>,<sect> | 848 | Format: <cyl>,<head>,<sect> |
| 845 | 849 | ||
| 850 | hest_disable [ACPI] | ||
| 851 | Disable Hardware Error Source Table (HEST) support; | ||
| 852 | corresponding firmware-first mode error processing | ||
| 853 | logic will be disabled. | ||
| 854 | |||
| 846 | highmem=nn[KMG] [KNL,BOOT] forces the highmem zone to have an exact | 855 | highmem=nn[KMG] [KNL,BOOT] forces the highmem zone to have an exact |
| 847 | size of <nn>. This works even on boxes that have no | 856 | size of <nn>. This works even on boxes that have no |
| 848 | highmem otherwise. This also works to reduce highmem | 857 | highmem otherwise. This also works to reduce highmem |
diff --git a/arch/ia64/pci/pci.c b/arch/ia64/pci/pci.c index 64aff520b899..aa2533ae7e9e 100644 --- a/arch/ia64/pci/pci.c +++ b/arch/ia64/pci/pci.c | |||
| @@ -335,8 +335,11 @@ pcibios_setup_root_windows(struct pci_bus *bus, struct pci_controller *ctrl) | |||
| 335 | } | 335 | } |
| 336 | 336 | ||
| 337 | struct pci_bus * __devinit | 337 | struct pci_bus * __devinit |
| 338 | pci_acpi_scan_root(struct acpi_device *device, int domain, int bus) | 338 | pci_acpi_scan_root(struct acpi_pci_root *root) |
| 339 | { | 339 | { |
| 340 | struct acpi_device *device = root->device; | ||
| 341 | int domain = root->segment; | ||
| 342 | int bus = root->secondary.start; | ||
| 340 | struct pci_controller *controller; | 343 | struct pci_controller *controller; |
| 341 | unsigned int windows = 0; | 344 | unsigned int windows = 0; |
| 342 | struct pci_bus *pbus; | 345 | struct pci_bus *pbus; |
diff --git a/arch/x86/include/asm/mce.h b/arch/x86/include/asm/mce.h index 6c3fdd631ed3..f32a4301c4d4 100644 --- a/arch/x86/include/asm/mce.h +++ b/arch/x86/include/asm/mce.h | |||
| @@ -225,5 +225,13 @@ extern void mcheck_intel_therm_init(void); | |||
| 225 | static inline void mcheck_intel_therm_init(void) { } | 225 | static inline void mcheck_intel_therm_init(void) { } |
| 226 | #endif | 226 | #endif |
| 227 | 227 | ||
| 228 | /* | ||
| 229 | * Used by APEI to report memory error via /dev/mcelog | ||
| 230 | */ | ||
| 231 | |||
| 232 | struct cper_sec_mem_err; | ||
| 233 | extern void apei_mce_report_mem_error(int corrected, | ||
| 234 | struct cper_sec_mem_err *mem_err); | ||
| 235 | |||
| 228 | #endif /* __KERNEL__ */ | 236 | #endif /* __KERNEL__ */ |
| 229 | #endif /* _ASM_X86_MCE_H */ | 237 | #endif /* _ASM_X86_MCE_H */ |
diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index f9961034e557..82e508677b91 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c | |||
| @@ -162,8 +162,6 @@ static int __init acpi_sleep_setup(char *str) | |||
| 162 | #endif | 162 | #endif |
| 163 | if (strncmp(str, "old_ordering", 12) == 0) | 163 | if (strncmp(str, "old_ordering", 12) == 0) |
| 164 | acpi_old_suspend_ordering(); | 164 | acpi_old_suspend_ordering(); |
| 165 | if (strncmp(str, "sci_force_enable", 16) == 0) | ||
| 166 | acpi_set_sci_en_on_resume(); | ||
| 167 | str = strchr(str, ','); | 165 | str = strchr(str, ','); |
| 168 | if (str != NULL) | 166 | if (str != NULL) |
| 169 | str += strspn(str, ", \t"); | 167 | str += strspn(str, ", \t"); |
diff --git a/arch/x86/kernel/cpu/mcheck/Makefile b/arch/x86/kernel/cpu/mcheck/Makefile index 4ac6d48fe11b..bb34b03af252 100644 --- a/arch/x86/kernel/cpu/mcheck/Makefile +++ b/arch/x86/kernel/cpu/mcheck/Makefile | |||
| @@ -7,3 +7,5 @@ obj-$(CONFIG_X86_MCE_THRESHOLD) += threshold.o | |||
| 7 | obj-$(CONFIG_X86_MCE_INJECT) += mce-inject.o | 7 | obj-$(CONFIG_X86_MCE_INJECT) += mce-inject.o |
| 8 | 8 | ||
| 9 | obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o | 9 | obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o |
| 10 | |||
| 11 | obj-$(CONFIG_ACPI_APEI) += mce-apei.o | ||
diff --git a/arch/x86/kernel/cpu/mcheck/mce-apei.c b/arch/x86/kernel/cpu/mcheck/mce-apei.c new file mode 100644 index 000000000000..745b54f9be89 --- /dev/null +++ b/arch/x86/kernel/cpu/mcheck/mce-apei.c | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | /* | ||
| 2 | * Bridge between MCE and APEI | ||
| 3 | * | ||
| 4 | * On some machine, corrected memory errors are reported via APEI | ||
| 5 | * generic hardware error source (GHES) instead of corrected Machine | ||
| 6 | * Check. These corrected memory errors can be reported to user space | ||
| 7 | * through /dev/mcelog via faking a corrected Machine Check, so that | ||
| 8 | * the error memory page can be offlined by /sbin/mcelog if the error | ||
| 9 | * count for one page is beyond the threshold. | ||
| 10 | * | ||
| 11 | * For fatal MCE, save MCE record into persistent storage via ERST, so | ||
| 12 | * that the MCE record can be logged after reboot via ERST. | ||
| 13 | * | ||
| 14 | * Copyright 2010 Intel Corp. | ||
| 15 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 16 | * | ||
| 17 | * This program is free software; you can redistribute it and/or | ||
| 18 | * modify it under the terms of the GNU General Public License version | ||
| 19 | * 2 as published by the Free Software Foundation. | ||
| 20 | * | ||
| 21 | * This program is distributed in the hope that it will be useful, | ||
| 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 24 | * GNU General Public License for more details. | ||
| 25 | * | ||
| 26 | * You should have received a copy of the GNU General Public License | ||
| 27 | * along with this program; if not, write to the Free Software | ||
| 28 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 29 | */ | ||
| 30 | |||
| 31 | #include <linux/kernel.h> | ||
| 32 | #include <linux/acpi.h> | ||
| 33 | #include <linux/cper.h> | ||
| 34 | #include <acpi/apei.h> | ||
| 35 | #include <asm/mce.h> | ||
| 36 | |||
| 37 | #include "mce-internal.h" | ||
| 38 | |||
| 39 | void apei_mce_report_mem_error(int corrected, struct cper_sec_mem_err *mem_err) | ||
| 40 | { | ||
| 41 | struct mce m; | ||
| 42 | |||
| 43 | /* Only corrected MC is reported */ | ||
| 44 | if (!corrected) | ||
| 45 | return; | ||
| 46 | |||
| 47 | mce_setup(&m); | ||
| 48 | m.bank = 1; | ||
| 49 | /* Fake a memory read corrected error with unknown channel */ | ||
| 50 | m.status = MCI_STATUS_VAL | MCI_STATUS_EN | MCI_STATUS_ADDRV | 0x9f; | ||
| 51 | m.addr = mem_err->physical_addr; | ||
| 52 | mce_log(&m); | ||
| 53 | mce_notify_irq(); | ||
| 54 | } | ||
| 55 | EXPORT_SYMBOL_GPL(apei_mce_report_mem_error); | ||
| 56 | |||
| 57 | #define CPER_CREATOR_MCE \ | ||
| 58 | UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ | ||
| 59 | 0x64, 0x90, 0xb8, 0x9d) | ||
| 60 | #define CPER_SECTION_TYPE_MCE \ | ||
| 61 | UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ | ||
| 62 | 0x04, 0x4a, 0x38, 0xfc) | ||
| 63 | |||
| 64 | /* | ||
| 65 | * CPER specification (in UEFI specification 2.3 appendix N) requires | ||
| 66 | * byte-packed. | ||
| 67 | */ | ||
| 68 | struct cper_mce_record { | ||
| 69 | struct cper_record_header hdr; | ||
| 70 | struct cper_section_descriptor sec_hdr; | ||
| 71 | struct mce mce; | ||
| 72 | } __packed; | ||
| 73 | |||
| 74 | int apei_write_mce(struct mce *m) | ||
| 75 | { | ||
| 76 | struct cper_mce_record rcd; | ||
| 77 | |||
| 78 | memset(&rcd, 0, sizeof(rcd)); | ||
| 79 | memcpy(rcd.hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE); | ||
| 80 | rcd.hdr.revision = CPER_RECORD_REV; | ||
| 81 | rcd.hdr.signature_end = CPER_SIG_END; | ||
| 82 | rcd.hdr.section_count = 1; | ||
| 83 | rcd.hdr.error_severity = CPER_SER_FATAL; | ||
| 84 | /* timestamp, platform_id, partition_id are all invalid */ | ||
| 85 | rcd.hdr.validation_bits = 0; | ||
| 86 | rcd.hdr.record_length = sizeof(rcd); | ||
| 87 | rcd.hdr.creator_id = CPER_CREATOR_MCE; | ||
| 88 | rcd.hdr.notification_type = CPER_NOTIFY_MCE; | ||
| 89 | rcd.hdr.record_id = cper_next_record_id(); | ||
| 90 | rcd.hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR; | ||
| 91 | |||
| 92 | rcd.sec_hdr.section_offset = (void *)&rcd.mce - (void *)&rcd; | ||
| 93 | rcd.sec_hdr.section_length = sizeof(rcd.mce); | ||
| 94 | rcd.sec_hdr.revision = CPER_SEC_REV; | ||
| 95 | /* fru_id and fru_text is invalid */ | ||
| 96 | rcd.sec_hdr.validation_bits = 0; | ||
| 97 | rcd.sec_hdr.flags = CPER_SEC_PRIMARY; | ||
| 98 | rcd.sec_hdr.section_type = CPER_SECTION_TYPE_MCE; | ||
| 99 | rcd.sec_hdr.section_severity = CPER_SER_FATAL; | ||
| 100 | |||
| 101 | memcpy(&rcd.mce, m, sizeof(*m)); | ||
| 102 | |||
| 103 | return erst_write(&rcd.hdr); | ||
| 104 | } | ||
| 105 | |||
| 106 | ssize_t apei_read_mce(struct mce *m, u64 *record_id) | ||
| 107 | { | ||
| 108 | struct cper_mce_record rcd; | ||
| 109 | ssize_t len; | ||
| 110 | |||
| 111 | len = erst_read_next(&rcd.hdr, sizeof(rcd)); | ||
| 112 | if (len <= 0) | ||
| 113 | return len; | ||
| 114 | /* Can not skip other records in storage via ERST unless clear them */ | ||
| 115 | else if (len != sizeof(rcd) || | ||
| 116 | uuid_le_cmp(rcd.hdr.creator_id, CPER_CREATOR_MCE)) { | ||
| 117 | if (printk_ratelimit()) | ||
| 118 | pr_warning( | ||
| 119 | "MCE-APEI: Can not skip the unknown record in ERST"); | ||
| 120 | return -EIO; | ||
| 121 | } | ||
| 122 | |||
| 123 | memcpy(m, &rcd.mce, sizeof(*m)); | ||
| 124 | *record_id = rcd.hdr.record_id; | ||
| 125 | |||
| 126 | return sizeof(*m); | ||
| 127 | } | ||
| 128 | |||
| 129 | /* Check whether there is record in ERST */ | ||
| 130 | int apei_check_mce(void) | ||
| 131 | { | ||
| 132 | return erst_get_record_count(); | ||
| 133 | } | ||
| 134 | |||
| 135 | int apei_clear_mce(u64 record_id) | ||
| 136 | { | ||
| 137 | return erst_clear(record_id); | ||
| 138 | } | ||
diff --git a/arch/x86/kernel/cpu/mcheck/mce-internal.h b/arch/x86/kernel/cpu/mcheck/mce-internal.h index 32996f9fab67..fefcc69ee8b5 100644 --- a/arch/x86/kernel/cpu/mcheck/mce-internal.h +++ b/arch/x86/kernel/cpu/mcheck/mce-internal.h | |||
| @@ -28,3 +28,26 @@ extern int mce_ser; | |||
| 28 | 28 | ||
| 29 | extern struct mce_bank *mce_banks; | 29 | extern struct mce_bank *mce_banks; |
| 30 | 30 | ||
| 31 | #ifdef CONFIG_ACPI_APEI | ||
| 32 | int apei_write_mce(struct mce *m); | ||
| 33 | ssize_t apei_read_mce(struct mce *m, u64 *record_id); | ||
| 34 | int apei_check_mce(void); | ||
| 35 | int apei_clear_mce(u64 record_id); | ||
| 36 | #else | ||
| 37 | static inline int apei_write_mce(struct mce *m) | ||
| 38 | { | ||
| 39 | return -EINVAL; | ||
| 40 | } | ||
| 41 | static inline ssize_t apei_read_mce(struct mce *m, u64 *record_id) | ||
| 42 | { | ||
| 43 | return 0; | ||
| 44 | } | ||
| 45 | static inline int apei_check_mce(void) | ||
| 46 | { | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | static inline int apei_clear_mce(u64 record_id) | ||
| 50 | { | ||
| 51 | return -EINVAL; | ||
| 52 | } | ||
| 53 | #endif | ||
diff --git a/arch/x86/kernel/cpu/mcheck/mce.c b/arch/x86/kernel/cpu/mcheck/mce.c index 8a6f0afa767e..09535ca9b9d7 100644 --- a/arch/x86/kernel/cpu/mcheck/mce.c +++ b/arch/x86/kernel/cpu/mcheck/mce.c | |||
| @@ -264,7 +264,7 @@ static void wait_for_panic(void) | |||
| 264 | 264 | ||
| 265 | static void mce_panic(char *msg, struct mce *final, char *exp) | 265 | static void mce_panic(char *msg, struct mce *final, char *exp) |
| 266 | { | 266 | { |
| 267 | int i; | 267 | int i, apei_err = 0; |
| 268 | 268 | ||
| 269 | if (!fake_panic) { | 269 | if (!fake_panic) { |
| 270 | /* | 270 | /* |
| @@ -287,8 +287,11 @@ static void mce_panic(char *msg, struct mce *final, char *exp) | |||
| 287 | struct mce *m = &mcelog.entry[i]; | 287 | struct mce *m = &mcelog.entry[i]; |
| 288 | if (!(m->status & MCI_STATUS_VAL)) | 288 | if (!(m->status & MCI_STATUS_VAL)) |
| 289 | continue; | 289 | continue; |
| 290 | if (!(m->status & MCI_STATUS_UC)) | 290 | if (!(m->status & MCI_STATUS_UC)) { |
| 291 | print_mce(m); | 291 | print_mce(m); |
| 292 | if (!apei_err) | ||
| 293 | apei_err = apei_write_mce(m); | ||
| 294 | } | ||
| 292 | } | 295 | } |
| 293 | /* Now print uncorrected but with the final one last */ | 296 | /* Now print uncorrected but with the final one last */ |
| 294 | for (i = 0; i < MCE_LOG_LEN; i++) { | 297 | for (i = 0; i < MCE_LOG_LEN; i++) { |
| @@ -297,11 +300,17 @@ static void mce_panic(char *msg, struct mce *final, char *exp) | |||
| 297 | continue; | 300 | continue; |
| 298 | if (!(m->status & MCI_STATUS_UC)) | 301 | if (!(m->status & MCI_STATUS_UC)) |
| 299 | continue; | 302 | continue; |
| 300 | if (!final || memcmp(m, final, sizeof(struct mce))) | 303 | if (!final || memcmp(m, final, sizeof(struct mce))) { |
| 301 | print_mce(m); | 304 | print_mce(m); |
| 305 | if (!apei_err) | ||
| 306 | apei_err = apei_write_mce(m); | ||
| 307 | } | ||
| 302 | } | 308 | } |
| 303 | if (final) | 309 | if (final) { |
| 304 | print_mce(final); | 310 | print_mce(final); |
| 311 | if (!apei_err) | ||
| 312 | apei_err = apei_write_mce(final); | ||
| 313 | } | ||
| 305 | if (cpu_missing) | 314 | if (cpu_missing) |
| 306 | printk(KERN_EMERG "Some CPUs didn't answer in synchronization\n"); | 315 | printk(KERN_EMERG "Some CPUs didn't answer in synchronization\n"); |
| 307 | print_mce_tail(); | 316 | print_mce_tail(); |
| @@ -1493,6 +1502,43 @@ static void collect_tscs(void *data) | |||
| 1493 | rdtscll(cpu_tsc[smp_processor_id()]); | 1502 | rdtscll(cpu_tsc[smp_processor_id()]); |
| 1494 | } | 1503 | } |
| 1495 | 1504 | ||
| 1505 | static int mce_apei_read_done; | ||
| 1506 | |||
| 1507 | /* Collect MCE record of previous boot in persistent storage via APEI ERST. */ | ||
| 1508 | static int __mce_read_apei(char __user **ubuf, size_t usize) | ||
| 1509 | { | ||
| 1510 | int rc; | ||
| 1511 | u64 record_id; | ||
| 1512 | struct mce m; | ||
| 1513 | |||
| 1514 | if (usize < sizeof(struct mce)) | ||
| 1515 | return -EINVAL; | ||
| 1516 | |||
| 1517 | rc = apei_read_mce(&m, &record_id); | ||
| 1518 | /* Error or no more MCE record */ | ||
| 1519 | if (rc <= 0) { | ||
| 1520 | mce_apei_read_done = 1; | ||
| 1521 | return rc; | ||
| 1522 | } | ||
| 1523 | rc = -EFAULT; | ||
| 1524 | if (copy_to_user(*ubuf, &m, sizeof(struct mce))) | ||
| 1525 | return rc; | ||
| 1526 | /* | ||
| 1527 | * In fact, we should have cleared the record after that has | ||
| 1528 | * been flushed to the disk or sent to network in | ||
| 1529 | * /sbin/mcelog, but we have no interface to support that now, | ||
| 1530 | * so just clear it to avoid duplication. | ||
| 1531 | */ | ||
| 1532 | rc = apei_clear_mce(record_id); | ||
| 1533 | if (rc) { | ||
| 1534 | mce_apei_read_done = 1; | ||
| 1535 | return rc; | ||
| 1536 | } | ||
| 1537 | *ubuf += sizeof(struct mce); | ||
| 1538 | |||
| 1539 | return 0; | ||
| 1540 | } | ||
| 1541 | |||
| 1496 | static ssize_t mce_read(struct file *filp, char __user *ubuf, size_t usize, | 1542 | static ssize_t mce_read(struct file *filp, char __user *ubuf, size_t usize, |
| 1497 | loff_t *off) | 1543 | loff_t *off) |
| 1498 | { | 1544 | { |
| @@ -1506,15 +1552,19 @@ static ssize_t mce_read(struct file *filp, char __user *ubuf, size_t usize, | |||
| 1506 | return -ENOMEM; | 1552 | return -ENOMEM; |
| 1507 | 1553 | ||
| 1508 | mutex_lock(&mce_read_mutex); | 1554 | mutex_lock(&mce_read_mutex); |
| 1555 | |||
| 1556 | if (!mce_apei_read_done) { | ||
| 1557 | err = __mce_read_apei(&buf, usize); | ||
| 1558 | if (err || buf != ubuf) | ||
| 1559 | goto out; | ||
| 1560 | } | ||
| 1561 | |||
| 1509 | next = rcu_dereference_check_mce(mcelog.next); | 1562 | next = rcu_dereference_check_mce(mcelog.next); |
| 1510 | 1563 | ||
| 1511 | /* Only supports full reads right now */ | 1564 | /* Only supports full reads right now */ |
| 1512 | if (*off != 0 || usize < MCE_LOG_LEN*sizeof(struct mce)) { | 1565 | err = -EINVAL; |
| 1513 | mutex_unlock(&mce_read_mutex); | 1566 | if (*off != 0 || usize < MCE_LOG_LEN*sizeof(struct mce)) |
| 1514 | kfree(cpu_tsc); | 1567 | goto out; |
| 1515 | |||
| 1516 | return -EINVAL; | ||
| 1517 | } | ||
| 1518 | 1568 | ||
| 1519 | err = 0; | 1569 | err = 0; |
| 1520 | prev = 0; | 1570 | prev = 0; |
| @@ -1562,10 +1612,15 @@ timeout: | |||
| 1562 | memset(&mcelog.entry[i], 0, sizeof(struct mce)); | 1612 | memset(&mcelog.entry[i], 0, sizeof(struct mce)); |
| 1563 | } | 1613 | } |
| 1564 | } | 1614 | } |
| 1615 | |||
| 1616 | if (err) | ||
| 1617 | err = -EFAULT; | ||
| 1618 | |||
| 1619 | out: | ||
| 1565 | mutex_unlock(&mce_read_mutex); | 1620 | mutex_unlock(&mce_read_mutex); |
| 1566 | kfree(cpu_tsc); | 1621 | kfree(cpu_tsc); |
| 1567 | 1622 | ||
| 1568 | return err ? -EFAULT : buf - ubuf; | 1623 | return err ? err : buf - ubuf; |
| 1569 | } | 1624 | } |
| 1570 | 1625 | ||
| 1571 | static unsigned int mce_poll(struct file *file, poll_table *wait) | 1626 | static unsigned int mce_poll(struct file *file, poll_table *wait) |
| @@ -1573,6 +1628,8 @@ static unsigned int mce_poll(struct file *file, poll_table *wait) | |||
| 1573 | poll_wait(file, &mce_wait, wait); | 1628 | poll_wait(file, &mce_wait, wait); |
| 1574 | if (rcu_dereference_check_mce(mcelog.next)) | 1629 | if (rcu_dereference_check_mce(mcelog.next)) |
| 1575 | return POLLIN | POLLRDNORM; | 1630 | return POLLIN | POLLRDNORM; |
| 1631 | if (!mce_apei_read_done && apei_check_mce()) | ||
| 1632 | return POLLIN | POLLRDNORM; | ||
| 1576 | return 0; | 1633 | return 0; |
| 1577 | } | 1634 | } |
| 1578 | 1635 | ||
diff --git a/arch/x86/pci/acpi.c b/arch/x86/pci/acpi.c index 31930fd30ea9..9dcf43d7d0c0 100644 --- a/arch/x86/pci/acpi.c +++ b/arch/x86/pci/acpi.c | |||
| @@ -224,8 +224,11 @@ res_alloc_fail: | |||
| 224 | return; | 224 | return; |
| 225 | } | 225 | } |
| 226 | 226 | ||
| 227 | struct pci_bus * __devinit pci_acpi_scan_root(struct acpi_device *device, int domain, int busnum) | 227 | struct pci_bus * __devinit pci_acpi_scan_root(struct acpi_pci_root *root) |
| 228 | { | 228 | { |
| 229 | struct acpi_device *device = root->device; | ||
| 230 | int domain = root->segment; | ||
| 231 | int busnum = root->secondary.start; | ||
| 229 | struct pci_bus *bus; | 232 | struct pci_bus *bus; |
| 230 | struct pci_sysdata *sd; | 233 | struct pci_sysdata *sd; |
| 231 | int node; | 234 | int node; |
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 93d2c7971df6..746411518802 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig | |||
| @@ -360,4 +360,13 @@ config ACPI_SBS | |||
| 360 | To compile this driver as a module, choose M here: | 360 | To compile this driver as a module, choose M here: |
| 361 | the modules will be called sbs and sbshc. | 361 | the modules will be called sbs and sbshc. |
| 362 | 362 | ||
| 363 | config ACPI_HED | ||
| 364 | tristate "Hardware Error Device" | ||
| 365 | help | ||
| 366 | This driver supports the Hardware Error Device (PNP0C33), | ||
| 367 | which is used to report some hardware errors notified via | ||
| 368 | SCI, mainly the corrected errors. | ||
| 369 | |||
| 370 | source "drivers/acpi/apei/Kconfig" | ||
| 371 | |||
| 363 | endif # ACPI | 372 | endif # ACPI |
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index a8d8998dd5c5..6ee33169e1dc 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile | |||
| @@ -19,7 +19,7 @@ obj-y += acpi.o \ | |||
| 19 | 19 | ||
| 20 | # All the builtin files are in the "acpi." module_param namespace. | 20 | # All the builtin files are in the "acpi." module_param namespace. |
| 21 | acpi-y += osl.o utils.o reboot.o | 21 | acpi-y += osl.o utils.o reboot.o |
| 22 | acpi-y += hest.o | 22 | acpi-y += atomicio.o |
| 23 | 23 | ||
| 24 | # sleep related files | 24 | # sleep related files |
| 25 | acpi-y += wakeup.o | 25 | acpi-y += wakeup.o |
| @@ -59,6 +59,7 @@ obj-$(CONFIG_ACPI_BATTERY) += battery.o | |||
| 59 | obj-$(CONFIG_ACPI_SBS) += sbshc.o | 59 | obj-$(CONFIG_ACPI_SBS) += sbshc.o |
| 60 | obj-$(CONFIG_ACPI_SBS) += sbs.o | 60 | obj-$(CONFIG_ACPI_SBS) += sbs.o |
| 61 | obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o | 61 | obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o |
| 62 | obj-$(CONFIG_ACPI_HED) += hed.o | ||
| 62 | 63 | ||
| 63 | # processor has its own "processor." module_param namespace | 64 | # processor has its own "processor." module_param namespace |
| 64 | processor-y := processor_driver.o processor_throttling.o | 65 | processor-y := processor_driver.o processor_throttling.o |
| @@ -66,3 +67,5 @@ processor-y += processor_idle.o processor_thermal.o | |||
| 66 | processor-$(CONFIG_CPU_FREQ) += processor_perflib.o | 67 | processor-$(CONFIG_CPU_FREQ) += processor_perflib.o |
| 67 | 68 | ||
| 68 | obj-$(CONFIG_ACPI_PROCESSOR_AGGREGATOR) += acpi_pad.o | 69 | obj-$(CONFIG_ACPI_PROCESSOR_AGGREGATOR) += acpi_pad.o |
| 70 | |||
| 71 | obj-$(CONFIG_ACPI_APEI) += apei/ | ||
diff --git a/drivers/acpi/acpica/evxfevnt.c b/drivers/acpi/acpica/evxfevnt.c index 5ff32c78ea2d..bfbe291d572e 100644 --- a/drivers/acpi/acpica/evxfevnt.c +++ b/drivers/acpi/acpica/evxfevnt.c | |||
| @@ -69,7 +69,7 @@ acpi_ev_get_gpe_device(struct acpi_gpe_xrupt_info *gpe_xrupt_info, | |||
| 69 | 69 | ||
| 70 | acpi_status acpi_enable(void) | 70 | acpi_status acpi_enable(void) |
| 71 | { | 71 | { |
| 72 | acpi_status status = AE_OK; | 72 | acpi_status status; |
| 73 | 73 | ||
| 74 | ACPI_FUNCTION_TRACE(acpi_enable); | 74 | ACPI_FUNCTION_TRACE(acpi_enable); |
| 75 | 75 | ||
| @@ -84,21 +84,30 @@ acpi_status acpi_enable(void) | |||
| 84 | if (acpi_hw_get_mode() == ACPI_SYS_MODE_ACPI) { | 84 | if (acpi_hw_get_mode() == ACPI_SYS_MODE_ACPI) { |
| 85 | ACPI_DEBUG_PRINT((ACPI_DB_INIT, | 85 | ACPI_DEBUG_PRINT((ACPI_DB_INIT, |
| 86 | "System is already in ACPI mode\n")); | 86 | "System is already in ACPI mode\n")); |
| 87 | } else { | 87 | return_ACPI_STATUS(AE_OK); |
| 88 | /* Transition to ACPI mode */ | 88 | } |
| 89 | 89 | ||
| 90 | status = acpi_hw_set_mode(ACPI_SYS_MODE_ACPI); | 90 | /* Transition to ACPI mode */ |
| 91 | if (ACPI_FAILURE(status)) { | ||
| 92 | ACPI_ERROR((AE_INFO, | ||
| 93 | "Could not transition to ACPI mode")); | ||
| 94 | return_ACPI_STATUS(status); | ||
| 95 | } | ||
| 96 | 91 | ||
| 97 | ACPI_DEBUG_PRINT((ACPI_DB_INIT, | 92 | status = acpi_hw_set_mode(ACPI_SYS_MODE_ACPI); |
| 98 | "Transition to ACPI mode successful\n")); | 93 | if (ACPI_FAILURE(status)) { |
| 94 | ACPI_ERROR((AE_INFO, | ||
| 95 | "Could not transition to ACPI mode")); | ||
| 96 | return_ACPI_STATUS(status); | ||
| 99 | } | 97 | } |
| 100 | 98 | ||
| 101 | return_ACPI_STATUS(status); | 99 | /* Sanity check that transition succeeded */ |
| 100 | |||
| 101 | if (acpi_hw_get_mode() != ACPI_SYS_MODE_ACPI) { | ||
| 102 | ACPI_ERROR((AE_INFO, | ||
| 103 | "Hardware did not enter ACPI mode")); | ||
| 104 | return_ACPI_STATUS(AE_NO_HARDWARE_RESPONSE); | ||
| 105 | } | ||
| 106 | |||
| 107 | ACPI_DEBUG_PRINT((ACPI_DB_INIT, | ||
| 108 | "Transition to ACPI mode successful\n")); | ||
| 109 | |||
| 110 | return_ACPI_STATUS(AE_OK); | ||
| 102 | } | 111 | } |
| 103 | 112 | ||
| 104 | ACPI_EXPORT_SYMBOL(acpi_enable) | 113 | ACPI_EXPORT_SYMBOL(acpi_enable) |
diff --git a/drivers/acpi/acpica/hwacpi.c b/drivers/acpi/acpica/hwacpi.c index 679a112a7d26..b44274a0b62c 100644 --- a/drivers/acpi/acpica/hwacpi.c +++ b/drivers/acpi/acpica/hwacpi.c | |||
| @@ -63,7 +63,6 @@ acpi_status acpi_hw_set_mode(u32 mode) | |||
| 63 | { | 63 | { |
| 64 | 64 | ||
| 65 | acpi_status status; | 65 | acpi_status status; |
| 66 | u32 retry; | ||
| 67 | 66 | ||
| 68 | ACPI_FUNCTION_TRACE(hw_set_mode); | 67 | ACPI_FUNCTION_TRACE(hw_set_mode); |
| 69 | 68 | ||
| @@ -125,24 +124,7 @@ acpi_status acpi_hw_set_mode(u32 mode) | |||
| 125 | return_ACPI_STATUS(status); | 124 | return_ACPI_STATUS(status); |
| 126 | } | 125 | } |
| 127 | 126 | ||
| 128 | /* | 127 | return_ACPI_STATUS(AE_OK); |
| 129 | * Some hardware takes a LONG time to switch modes. Give them 3 sec to | ||
| 130 | * do so, but allow faster systems to proceed more quickly. | ||
| 131 | */ | ||
| 132 | retry = 3000; | ||
| 133 | while (retry) { | ||
| 134 | if (acpi_hw_get_mode() == mode) { | ||
| 135 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
| 136 | "Mode %X successfully enabled\n", | ||
| 137 | mode)); | ||
| 138 | return_ACPI_STATUS(AE_OK); | ||
| 139 | } | ||
| 140 | acpi_os_stall(1000); | ||
| 141 | retry--; | ||
| 142 | } | ||
| 143 | |||
| 144 | ACPI_ERROR((AE_INFO, "Hardware did not change modes")); | ||
| 145 | return_ACPI_STATUS(AE_NO_HARDWARE_RESPONSE); | ||
| 146 | } | 128 | } |
| 147 | 129 | ||
| 148 | /******************************************************************************* | 130 | /******************************************************************************* |
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig new file mode 100644 index 000000000000..f8c668f27b5a --- /dev/null +++ b/drivers/acpi/apei/Kconfig | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | config ACPI_APEI | ||
| 2 | bool "ACPI Platform Error Interface (APEI)" | ||
| 3 | depends on X86 | ||
| 4 | help | ||
| 5 | APEI allows to report errors (for example from the chipset) | ||
| 6 | to the operating system. This improves NMI handling | ||
| 7 | especially. In addition it supports error serialization and | ||
| 8 | error injection. | ||
| 9 | |||
| 10 | config ACPI_APEI_GHES | ||
| 11 | tristate "APEI Generic Hardware Error Source" | ||
| 12 | depends on ACPI_APEI && X86 | ||
| 13 | select ACPI_HED | ||
| 14 | help | ||
| 15 | Generic Hardware Error Source provides a way to report | ||
| 16 | platform hardware errors (such as that from chipset). It | ||
| 17 | works in so called "Firmware First" mode, that is, hardware | ||
| 18 | errors are reported to firmware firstly, then reported to | ||
| 19 | Linux by firmware. This way, some non-standard hardware | ||
| 20 | error registers or non-standard hardware link can be checked | ||
| 21 | by firmware to produce more valuable hardware error | ||
| 22 | information for Linux. | ||
| 23 | |||
| 24 | config ACPI_APEI_EINJ | ||
| 25 | tristate "APEI Error INJection (EINJ)" | ||
| 26 | depends on ACPI_APEI && DEBUG_FS | ||
| 27 | help | ||
| 28 | EINJ provides a hardware error injection mechanism, it is | ||
| 29 | mainly used for debugging and testing the other parts of | ||
| 30 | APEI and some other RAS features. | ||
diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile new file mode 100644 index 000000000000..b13b03a17789 --- /dev/null +++ b/drivers/acpi/apei/Makefile | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | obj-$(CONFIG_ACPI_APEI) += apei.o | ||
| 2 | obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o | ||
| 3 | obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o | ||
| 4 | |||
| 5 | 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 new file mode 100644 index 000000000000..db3946e9c66b --- /dev/null +++ b/drivers/acpi/apei/apei-base.c | |||
| @@ -0,0 +1,593 @@ | |||
| 1 | /* | ||
| 2 | * apei-base.c - ACPI Platform Error Interface (APEI) supporting | ||
| 3 | * infrastructure | ||
| 4 | * | ||
| 5 | * APEI allows to report errors (for example from the chipset) to the | ||
| 6 | * the operating system. This improves NMI handling especially. In | ||
| 7 | * addition it supports error serialization and error injection. | ||
| 8 | * | ||
| 9 | * For more information about APEI, please refer to ACPI Specification | ||
| 10 | * version 4.0, chapter 17. | ||
| 11 | * | ||
| 12 | * This file has Common functions used by more than one APEI table, | ||
| 13 | * including framework of interpreter for ERST and EINJ; resource | ||
| 14 | * management for APEI registers. | ||
| 15 | * | ||
| 16 | * Copyright (C) 2009, Intel Corp. | ||
| 17 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 18 | * | ||
| 19 | * This program is free software; you can redistribute it and/or | ||
| 20 | * modify it under the terms of the GNU General Public License version | ||
| 21 | * 2 as published by the Free Software Foundation. | ||
| 22 | * | ||
| 23 | * This program is distributed in the hope that it will be useful, | ||
| 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 26 | * GNU General Public License for more details. | ||
| 27 | * | ||
| 28 | * You should have received a copy of the GNU General Public License | ||
| 29 | * along with this program; if not, write to the Free Software | ||
| 30 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 31 | */ | ||
| 32 | |||
| 33 | #include <linux/kernel.h> | ||
| 34 | #include <linux/module.h> | ||
| 35 | #include <linux/init.h> | ||
| 36 | #include <linux/acpi.h> | ||
| 37 | #include <linux/io.h> | ||
| 38 | #include <linux/kref.h> | ||
| 39 | #include <linux/rculist.h> | ||
| 40 | #include <linux/interrupt.h> | ||
| 41 | #include <linux/debugfs.h> | ||
| 42 | #include <acpi/atomicio.h> | ||
| 43 | |||
| 44 | #include "apei-internal.h" | ||
| 45 | |||
| 46 | #define APEI_PFX "APEI: " | ||
| 47 | |||
| 48 | /* | ||
| 49 | * APEI ERST (Error Record Serialization Table) and EINJ (Error | ||
| 50 | * INJection) interpreter framework. | ||
| 51 | */ | ||
| 52 | |||
| 53 | #define APEI_EXEC_PRESERVE_REGISTER 0x1 | ||
| 54 | |||
| 55 | void apei_exec_ctx_init(struct apei_exec_context *ctx, | ||
| 56 | struct apei_exec_ins_type *ins_table, | ||
| 57 | u32 instructions, | ||
| 58 | struct acpi_whea_header *action_table, | ||
| 59 | u32 entries) | ||
| 60 | { | ||
| 61 | ctx->ins_table = ins_table; | ||
| 62 | ctx->instructions = instructions; | ||
| 63 | ctx->action_table = action_table; | ||
| 64 | ctx->entries = entries; | ||
| 65 | } | ||
| 66 | EXPORT_SYMBOL_GPL(apei_exec_ctx_init); | ||
| 67 | |||
| 68 | int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val) | ||
| 69 | { | ||
| 70 | int rc; | ||
| 71 | |||
| 72 | rc = acpi_atomic_read(val, &entry->register_region); | ||
| 73 | if (rc) | ||
| 74 | return rc; | ||
| 75 | *val >>= entry->register_region.bit_offset; | ||
| 76 | *val &= entry->mask; | ||
| 77 | |||
| 78 | return 0; | ||
| 79 | } | ||
| 80 | |||
| 81 | int apei_exec_read_register(struct apei_exec_context *ctx, | ||
| 82 | struct acpi_whea_header *entry) | ||
| 83 | { | ||
| 84 | int rc; | ||
| 85 | u64 val = 0; | ||
| 86 | |||
| 87 | rc = __apei_exec_read_register(entry, &val); | ||
| 88 | if (rc) | ||
| 89 | return rc; | ||
| 90 | ctx->value = val; | ||
| 91 | |||
| 92 | return 0; | ||
| 93 | } | ||
| 94 | EXPORT_SYMBOL_GPL(apei_exec_read_register); | ||
| 95 | |||
| 96 | int apei_exec_read_register_value(struct apei_exec_context *ctx, | ||
| 97 | struct acpi_whea_header *entry) | ||
| 98 | { | ||
| 99 | int rc; | ||
| 100 | |||
| 101 | rc = apei_exec_read_register(ctx, entry); | ||
| 102 | if (rc) | ||
| 103 | return rc; | ||
| 104 | ctx->value = (ctx->value == entry->value); | ||
| 105 | |||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | EXPORT_SYMBOL_GPL(apei_exec_read_register_value); | ||
| 109 | |||
| 110 | int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val) | ||
| 111 | { | ||
| 112 | int rc; | ||
| 113 | |||
| 114 | val &= entry->mask; | ||
| 115 | val <<= entry->register_region.bit_offset; | ||
| 116 | if (entry->flags & APEI_EXEC_PRESERVE_REGISTER) { | ||
| 117 | u64 valr = 0; | ||
| 118 | rc = acpi_atomic_read(&valr, &entry->register_region); | ||
| 119 | if (rc) | ||
| 120 | return rc; | ||
| 121 | valr &= ~(entry->mask << entry->register_region.bit_offset); | ||
| 122 | val |= valr; | ||
| 123 | } | ||
| 124 | rc = acpi_atomic_write(val, &entry->register_region); | ||
| 125 | |||
| 126 | return rc; | ||
| 127 | } | ||
| 128 | |||
| 129 | int apei_exec_write_register(struct apei_exec_context *ctx, | ||
| 130 | struct acpi_whea_header *entry) | ||
| 131 | { | ||
| 132 | return __apei_exec_write_register(entry, ctx->value); | ||
| 133 | } | ||
| 134 | EXPORT_SYMBOL_GPL(apei_exec_write_register); | ||
| 135 | |||
| 136 | int apei_exec_write_register_value(struct apei_exec_context *ctx, | ||
| 137 | struct acpi_whea_header *entry) | ||
| 138 | { | ||
| 139 | int rc; | ||
| 140 | |||
| 141 | ctx->value = entry->value; | ||
| 142 | rc = apei_exec_write_register(ctx, entry); | ||
| 143 | |||
| 144 | return rc; | ||
| 145 | } | ||
| 146 | EXPORT_SYMBOL_GPL(apei_exec_write_register_value); | ||
| 147 | |||
| 148 | int apei_exec_noop(struct apei_exec_context *ctx, | ||
| 149 | struct acpi_whea_header *entry) | ||
| 150 | { | ||
| 151 | return 0; | ||
| 152 | } | ||
| 153 | EXPORT_SYMBOL_GPL(apei_exec_noop); | ||
| 154 | |||
| 155 | /* | ||
| 156 | * Interpret the specified action. Go through whole action table, | ||
| 157 | * execute all instructions belong to the action. | ||
| 158 | */ | ||
| 159 | int apei_exec_run(struct apei_exec_context *ctx, u8 action) | ||
| 160 | { | ||
| 161 | int rc; | ||
| 162 | u32 i, ip; | ||
| 163 | struct acpi_whea_header *entry; | ||
| 164 | apei_exec_ins_func_t run; | ||
| 165 | |||
| 166 | ctx->ip = 0; | ||
| 167 | |||
| 168 | /* | ||
| 169 | * "ip" is the instruction pointer of current instruction, | ||
| 170 | * "ctx->ip" specifies the next instruction to executed, | ||
| 171 | * instruction "run" function may change the "ctx->ip" to | ||
| 172 | * implement "goto" semantics. | ||
| 173 | */ | ||
| 174 | rewind: | ||
| 175 | ip = 0; | ||
| 176 | for (i = 0; i < ctx->entries; i++) { | ||
| 177 | entry = &ctx->action_table[i]; | ||
| 178 | if (entry->action != action) | ||
| 179 | continue; | ||
| 180 | if (ip == ctx->ip) { | ||
| 181 | if (entry->instruction >= ctx->instructions || | ||
| 182 | !ctx->ins_table[entry->instruction].run) { | ||
| 183 | pr_warning(FW_WARN APEI_PFX | ||
| 184 | "Invalid action table, unknown instruction type: %d\n", | ||
| 185 | entry->instruction); | ||
| 186 | return -EINVAL; | ||
| 187 | } | ||
| 188 | run = ctx->ins_table[entry->instruction].run; | ||
| 189 | rc = run(ctx, entry); | ||
| 190 | if (rc < 0) | ||
| 191 | return rc; | ||
| 192 | else if (rc != APEI_EXEC_SET_IP) | ||
| 193 | ctx->ip++; | ||
| 194 | } | ||
| 195 | ip++; | ||
| 196 | if (ctx->ip < ip) | ||
| 197 | goto rewind; | ||
| 198 | } | ||
| 199 | |||
| 200 | return 0; | ||
| 201 | } | ||
| 202 | EXPORT_SYMBOL_GPL(apei_exec_run); | ||
| 203 | |||
| 204 | typedef int (*apei_exec_entry_func_t)(struct apei_exec_context *ctx, | ||
| 205 | struct acpi_whea_header *entry, | ||
| 206 | void *data); | ||
| 207 | |||
| 208 | static int apei_exec_for_each_entry(struct apei_exec_context *ctx, | ||
| 209 | apei_exec_entry_func_t func, | ||
| 210 | void *data, | ||
| 211 | int *end) | ||
| 212 | { | ||
| 213 | u8 ins; | ||
| 214 | int i, rc; | ||
| 215 | struct acpi_whea_header *entry; | ||
| 216 | struct apei_exec_ins_type *ins_table = ctx->ins_table; | ||
| 217 | |||
| 218 | for (i = 0; i < ctx->entries; i++) { | ||
| 219 | entry = ctx->action_table + i; | ||
| 220 | ins = entry->instruction; | ||
| 221 | if (end) | ||
| 222 | *end = i; | ||
| 223 | if (ins >= ctx->instructions || !ins_table[ins].run) { | ||
| 224 | pr_warning(FW_WARN APEI_PFX | ||
| 225 | "Invalid action table, unknown instruction type: %d\n", | ||
| 226 | ins); | ||
| 227 | return -EINVAL; | ||
| 228 | } | ||
| 229 | rc = func(ctx, entry, data); | ||
| 230 | if (rc) | ||
| 231 | return rc; | ||
| 232 | } | ||
| 233 | |||
| 234 | return 0; | ||
| 235 | } | ||
| 236 | |||
| 237 | static int pre_map_gar_callback(struct apei_exec_context *ctx, | ||
| 238 | struct acpi_whea_header *entry, | ||
| 239 | void *data) | ||
| 240 | { | ||
| 241 | u8 ins = entry->instruction; | ||
| 242 | |||
| 243 | if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER) | ||
| 244 | return acpi_pre_map_gar(&entry->register_region); | ||
| 245 | |||
| 246 | return 0; | ||
| 247 | } | ||
| 248 | |||
| 249 | /* | ||
| 250 | * Pre-map all GARs in action table to make it possible to access them | ||
| 251 | * in NMI handler. | ||
| 252 | */ | ||
| 253 | int apei_exec_pre_map_gars(struct apei_exec_context *ctx) | ||
| 254 | { | ||
| 255 | int rc, end; | ||
| 256 | |||
| 257 | rc = apei_exec_for_each_entry(ctx, pre_map_gar_callback, | ||
| 258 | NULL, &end); | ||
| 259 | if (rc) { | ||
| 260 | struct apei_exec_context ctx_unmap; | ||
| 261 | memcpy(&ctx_unmap, ctx, sizeof(*ctx)); | ||
| 262 | ctx_unmap.entries = end; | ||
| 263 | apei_exec_post_unmap_gars(&ctx_unmap); | ||
| 264 | } | ||
| 265 | |||
| 266 | return rc; | ||
| 267 | } | ||
| 268 | EXPORT_SYMBOL_GPL(apei_exec_pre_map_gars); | ||
| 269 | |||
| 270 | static int post_unmap_gar_callback(struct apei_exec_context *ctx, | ||
| 271 | struct acpi_whea_header *entry, | ||
| 272 | void *data) | ||
| 273 | { | ||
| 274 | u8 ins = entry->instruction; | ||
| 275 | |||
| 276 | if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER) | ||
| 277 | acpi_post_unmap_gar(&entry->register_region); | ||
| 278 | |||
| 279 | return 0; | ||
| 280 | } | ||
| 281 | |||
| 282 | /* Post-unmap all GAR in action table. */ | ||
| 283 | int apei_exec_post_unmap_gars(struct apei_exec_context *ctx) | ||
| 284 | { | ||
| 285 | return apei_exec_for_each_entry(ctx, post_unmap_gar_callback, | ||
| 286 | NULL, NULL); | ||
| 287 | } | ||
| 288 | EXPORT_SYMBOL_GPL(apei_exec_post_unmap_gars); | ||
| 289 | |||
| 290 | /* | ||
| 291 | * Resource management for GARs in APEI | ||
| 292 | */ | ||
| 293 | struct apei_res { | ||
| 294 | struct list_head list; | ||
| 295 | unsigned long start; | ||
| 296 | unsigned long end; | ||
| 297 | }; | ||
| 298 | |||
| 299 | /* Collect all resources requested, to avoid conflict */ | ||
| 300 | struct apei_resources apei_resources_all = { | ||
| 301 | .iomem = LIST_HEAD_INIT(apei_resources_all.iomem), | ||
| 302 | .ioport = LIST_HEAD_INIT(apei_resources_all.ioport), | ||
| 303 | }; | ||
| 304 | |||
| 305 | static int apei_res_add(struct list_head *res_list, | ||
| 306 | unsigned long start, unsigned long size) | ||
| 307 | { | ||
| 308 | struct apei_res *res, *resn, *res_ins = NULL; | ||
| 309 | unsigned long end = start + size; | ||
| 310 | |||
| 311 | if (end <= start) | ||
| 312 | return 0; | ||
| 313 | repeat: | ||
| 314 | list_for_each_entry_safe(res, resn, res_list, list) { | ||
| 315 | if (res->start > end || res->end < start) | ||
| 316 | continue; | ||
| 317 | else if (end <= res->end && start >= res->start) { | ||
| 318 | kfree(res_ins); | ||
| 319 | return 0; | ||
| 320 | } | ||
| 321 | list_del(&res->list); | ||
| 322 | res->start = start = min(res->start, start); | ||
| 323 | res->end = end = max(res->end, end); | ||
| 324 | kfree(res_ins); | ||
| 325 | res_ins = res; | ||
| 326 | goto repeat; | ||
| 327 | } | ||
| 328 | |||
| 329 | if (res_ins) | ||
| 330 | list_add(&res_ins->list, res_list); | ||
| 331 | else { | ||
| 332 | res_ins = kmalloc(sizeof(*res), GFP_KERNEL); | ||
| 333 | if (!res_ins) | ||
| 334 | return -ENOMEM; | ||
| 335 | res_ins->start = start; | ||
| 336 | res_ins->end = end; | ||
| 337 | list_add(&res_ins->list, res_list); | ||
| 338 | } | ||
| 339 | |||
| 340 | return 0; | ||
| 341 | } | ||
| 342 | |||
| 343 | static int apei_res_sub(struct list_head *res_list1, | ||
| 344 | struct list_head *res_list2) | ||
| 345 | { | ||
| 346 | struct apei_res *res1, *resn1, *res2, *res; | ||
| 347 | res1 = list_entry(res_list1->next, struct apei_res, list); | ||
| 348 | resn1 = list_entry(res1->list.next, struct apei_res, list); | ||
| 349 | while (&res1->list != res_list1) { | ||
| 350 | list_for_each_entry(res2, res_list2, list) { | ||
| 351 | if (res1->start >= res2->end || | ||
| 352 | res1->end <= res2->start) | ||
| 353 | continue; | ||
| 354 | else if (res1->end <= res2->end && | ||
| 355 | res1->start >= res2->start) { | ||
| 356 | list_del(&res1->list); | ||
| 357 | kfree(res1); | ||
| 358 | break; | ||
| 359 | } else if (res1->end > res2->end && | ||
| 360 | res1->start < res2->start) { | ||
| 361 | res = kmalloc(sizeof(*res), GFP_KERNEL); | ||
| 362 | if (!res) | ||
| 363 | return -ENOMEM; | ||
| 364 | res->start = res2->end; | ||
| 365 | res->end = res1->end; | ||
| 366 | res1->end = res2->start; | ||
| 367 | list_add(&res->list, &res1->list); | ||
| 368 | resn1 = res; | ||
| 369 | } else { | ||
| 370 | if (res1->start < res2->start) | ||
| 371 | res1->end = res2->start; | ||
| 372 | else | ||
| 373 | res1->start = res2->end; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | res1 = resn1; | ||
| 377 | resn1 = list_entry(resn1->list.next, struct apei_res, list); | ||
| 378 | } | ||
| 379 | |||
| 380 | return 0; | ||
| 381 | } | ||
| 382 | |||
| 383 | static void apei_res_clean(struct list_head *res_list) | ||
| 384 | { | ||
| 385 | struct apei_res *res, *resn; | ||
| 386 | |||
| 387 | list_for_each_entry_safe(res, resn, res_list, list) { | ||
| 388 | list_del(&res->list); | ||
| 389 | kfree(res); | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | void apei_resources_fini(struct apei_resources *resources) | ||
| 394 | { | ||
| 395 | apei_res_clean(&resources->iomem); | ||
| 396 | apei_res_clean(&resources->ioport); | ||
| 397 | } | ||
| 398 | EXPORT_SYMBOL_GPL(apei_resources_fini); | ||
| 399 | |||
| 400 | static int apei_resources_merge(struct apei_resources *resources1, | ||
| 401 | struct apei_resources *resources2) | ||
| 402 | { | ||
| 403 | int rc; | ||
| 404 | struct apei_res *res; | ||
| 405 | |||
| 406 | list_for_each_entry(res, &resources2->iomem, list) { | ||
| 407 | rc = apei_res_add(&resources1->iomem, res->start, | ||
| 408 | res->end - res->start); | ||
| 409 | if (rc) | ||
| 410 | return rc; | ||
| 411 | } | ||
| 412 | list_for_each_entry(res, &resources2->ioport, list) { | ||
| 413 | rc = apei_res_add(&resources1->ioport, res->start, | ||
| 414 | res->end - res->start); | ||
| 415 | if (rc) | ||
| 416 | return rc; | ||
| 417 | } | ||
| 418 | |||
| 419 | return 0; | ||
| 420 | } | ||
| 421 | |||
| 422 | /* | ||
| 423 | * EINJ has two groups of GARs (EINJ table entry and trigger table | ||
| 424 | * entry), so common resources are subtracted from the trigger table | ||
| 425 | * resources before the second requesting. | ||
| 426 | */ | ||
| 427 | int apei_resources_sub(struct apei_resources *resources1, | ||
| 428 | struct apei_resources *resources2) | ||
| 429 | { | ||
| 430 | int rc; | ||
| 431 | |||
| 432 | rc = apei_res_sub(&resources1->iomem, &resources2->iomem); | ||
| 433 | if (rc) | ||
| 434 | return rc; | ||
| 435 | return apei_res_sub(&resources1->ioport, &resources2->ioport); | ||
| 436 | } | ||
| 437 | EXPORT_SYMBOL_GPL(apei_resources_sub); | ||
| 438 | |||
| 439 | /* | ||
| 440 | * IO memory/port rersource management mechanism is used to check | ||
| 441 | * whether memory/port area used by GARs conflicts with normal memory | ||
| 442 | * or IO memory/port of devices. | ||
| 443 | */ | ||
| 444 | int apei_resources_request(struct apei_resources *resources, | ||
| 445 | const char *desc) | ||
| 446 | { | ||
| 447 | struct apei_res *res, *res_bak; | ||
| 448 | struct resource *r; | ||
| 449 | |||
| 450 | apei_resources_sub(resources, &apei_resources_all); | ||
| 451 | |||
| 452 | list_for_each_entry(res, &resources->iomem, list) { | ||
| 453 | r = request_mem_region(res->start, res->end - res->start, | ||
| 454 | desc); | ||
| 455 | if (!r) { | ||
| 456 | pr_err(APEI_PFX | ||
| 457 | "Can not request iomem region <%016llx-%016llx> for GARs.\n", | ||
| 458 | (unsigned long long)res->start, | ||
| 459 | (unsigned long long)res->end); | ||
| 460 | res_bak = res; | ||
| 461 | goto err_unmap_iomem; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | list_for_each_entry(res, &resources->ioport, list) { | ||
| 466 | r = request_region(res->start, res->end - res->start, desc); | ||
| 467 | if (!r) { | ||
| 468 | pr_err(APEI_PFX | ||
| 469 | "Can not request ioport region <%016llx-%016llx> for GARs.\n", | ||
| 470 | (unsigned long long)res->start, | ||
| 471 | (unsigned long long)res->end); | ||
| 472 | res_bak = res; | ||
| 473 | goto err_unmap_ioport; | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | apei_resources_merge(&apei_resources_all, resources); | ||
| 478 | |||
| 479 | return 0; | ||
| 480 | err_unmap_ioport: | ||
| 481 | list_for_each_entry(res, &resources->ioport, list) { | ||
| 482 | if (res == res_bak) | ||
| 483 | break; | ||
| 484 | release_mem_region(res->start, res->end - res->start); | ||
| 485 | } | ||
| 486 | res_bak = NULL; | ||
| 487 | err_unmap_iomem: | ||
| 488 | list_for_each_entry(res, &resources->iomem, list) { | ||
| 489 | if (res == res_bak) | ||
| 490 | break; | ||
| 491 | release_region(res->start, res->end - res->start); | ||
| 492 | } | ||
| 493 | return -EINVAL; | ||
| 494 | } | ||
| 495 | EXPORT_SYMBOL_GPL(apei_resources_request); | ||
| 496 | |||
| 497 | void apei_resources_release(struct apei_resources *resources) | ||
| 498 | { | ||
| 499 | struct apei_res *res; | ||
| 500 | |||
| 501 | list_for_each_entry(res, &resources->iomem, list) | ||
| 502 | release_mem_region(res->start, res->end - res->start); | ||
| 503 | list_for_each_entry(res, &resources->ioport, list) | ||
| 504 | release_region(res->start, res->end - res->start); | ||
| 505 | |||
| 506 | apei_resources_sub(&apei_resources_all, resources); | ||
| 507 | } | ||
| 508 | EXPORT_SYMBOL_GPL(apei_resources_release); | ||
| 509 | |||
| 510 | static int apei_check_gar(struct acpi_generic_address *reg, u64 *paddr) | ||
| 511 | { | ||
| 512 | u32 width, space_id; | ||
| 513 | |||
| 514 | width = reg->bit_width; | ||
| 515 | space_id = reg->space_id; | ||
| 516 | /* Handle possible alignment issues */ | ||
| 517 | memcpy(paddr, ®->address, sizeof(*paddr)); | ||
| 518 | if (!*paddr) { | ||
| 519 | pr_warning(FW_BUG APEI_PFX | ||
| 520 | "Invalid physical address in GAR [0x%llx/%u/%u]\n", | ||
| 521 | *paddr, width, space_id); | ||
| 522 | return -EINVAL; | ||
| 523 | } | ||
| 524 | |||
| 525 | if ((width != 8) && (width != 16) && (width != 32) && (width != 64)) { | ||
| 526 | pr_warning(FW_BUG APEI_PFX | ||
| 527 | "Invalid bit width in GAR [0x%llx/%u/%u]\n", | ||
| 528 | *paddr, width, space_id); | ||
| 529 | return -EINVAL; | ||
| 530 | } | ||
| 531 | |||
| 532 | if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY && | ||
| 533 | space_id != ACPI_ADR_SPACE_SYSTEM_IO) { | ||
| 534 | pr_warning(FW_BUG APEI_PFX | ||
| 535 | "Invalid address space type in GAR [0x%llx/%u/%u]\n", | ||
| 536 | *paddr, width, space_id); | ||
| 537 | return -EINVAL; | ||
| 538 | } | ||
| 539 | |||
| 540 | return 0; | ||
| 541 | } | ||
| 542 | |||
| 543 | static int collect_res_callback(struct apei_exec_context *ctx, | ||
| 544 | struct acpi_whea_header *entry, | ||
| 545 | void *data) | ||
| 546 | { | ||
| 547 | struct apei_resources *resources = data; | ||
| 548 | struct acpi_generic_address *reg = &entry->register_region; | ||
| 549 | u8 ins = entry->instruction; | ||
| 550 | u64 paddr; | ||
| 551 | int rc; | ||
| 552 | |||
| 553 | if (!(ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER)) | ||
| 554 | return 0; | ||
| 555 | |||
| 556 | rc = apei_check_gar(reg, &paddr); | ||
| 557 | if (rc) | ||
| 558 | return rc; | ||
| 559 | |||
| 560 | switch (reg->space_id) { | ||
| 561 | case ACPI_ADR_SPACE_SYSTEM_MEMORY: | ||
| 562 | return apei_res_add(&resources->iomem, paddr, | ||
| 563 | reg->bit_width / 8); | ||
| 564 | case ACPI_ADR_SPACE_SYSTEM_IO: | ||
| 565 | return apei_res_add(&resources->ioport, paddr, | ||
| 566 | reg->bit_width / 8); | ||
| 567 | default: | ||
| 568 | return -EINVAL; | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | /* | ||
| 573 | * Same register may be used by multiple instructions in GARs, so | ||
| 574 | * resources are collected before requesting. | ||
| 575 | */ | ||
| 576 | int apei_exec_collect_resources(struct apei_exec_context *ctx, | ||
| 577 | struct apei_resources *resources) | ||
| 578 | { | ||
| 579 | return apei_exec_for_each_entry(ctx, collect_res_callback, | ||
| 580 | resources, NULL); | ||
| 581 | } | ||
| 582 | EXPORT_SYMBOL_GPL(apei_exec_collect_resources); | ||
| 583 | |||
| 584 | struct dentry *apei_get_debugfs_dir(void) | ||
| 585 | { | ||
| 586 | static struct dentry *dapei; | ||
| 587 | |||
| 588 | if (!dapei) | ||
| 589 | dapei = debugfs_create_dir("apei", NULL); | ||
| 590 | |||
| 591 | return dapei; | ||
| 592 | } | ||
| 593 | EXPORT_SYMBOL_GPL(apei_get_debugfs_dir); | ||
diff --git a/drivers/acpi/apei/apei-internal.h b/drivers/acpi/apei/apei-internal.h new file mode 100644 index 000000000000..18df1e940276 --- /dev/null +++ b/drivers/acpi/apei/apei-internal.h | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | /* | ||
| 2 | * apei-internal.h - ACPI Platform Error Interface internal | ||
| 3 | * definations. | ||
| 4 | */ | ||
| 5 | |||
| 6 | #ifndef APEI_INTERNAL_H | ||
| 7 | #define APEI_INTERNAL_H | ||
| 8 | |||
| 9 | #include <linux/cper.h> | ||
| 10 | |||
| 11 | struct apei_exec_context; | ||
| 12 | |||
| 13 | typedef int (*apei_exec_ins_func_t)(struct apei_exec_context *ctx, | ||
| 14 | struct acpi_whea_header *entry); | ||
| 15 | |||
| 16 | #define APEI_EXEC_INS_ACCESS_REGISTER 0x0001 | ||
| 17 | |||
| 18 | struct apei_exec_ins_type { | ||
| 19 | u32 flags; | ||
| 20 | apei_exec_ins_func_t run; | ||
| 21 | }; | ||
| 22 | |||
| 23 | struct apei_exec_context { | ||
| 24 | u32 ip; | ||
| 25 | u64 value; | ||
| 26 | u64 var1; | ||
| 27 | u64 var2; | ||
| 28 | u64 src_base; | ||
| 29 | u64 dst_base; | ||
| 30 | struct apei_exec_ins_type *ins_table; | ||
| 31 | u32 instructions; | ||
| 32 | struct acpi_whea_header *action_table; | ||
| 33 | u32 entries; | ||
| 34 | }; | ||
| 35 | |||
| 36 | void apei_exec_ctx_init(struct apei_exec_context *ctx, | ||
| 37 | struct apei_exec_ins_type *ins_table, | ||
| 38 | u32 instructions, | ||
| 39 | struct acpi_whea_header *action_table, | ||
| 40 | u32 entries); | ||
| 41 | |||
| 42 | static inline void apei_exec_ctx_set_input(struct apei_exec_context *ctx, | ||
| 43 | u64 input) | ||
| 44 | { | ||
| 45 | ctx->value = input; | ||
| 46 | } | ||
| 47 | |||
| 48 | static inline u64 apei_exec_ctx_get_output(struct apei_exec_context *ctx) | ||
| 49 | { | ||
| 50 | return ctx->value; | ||
| 51 | } | ||
| 52 | |||
| 53 | int apei_exec_run(struct apei_exec_context *ctx, u8 action); | ||
| 54 | |||
| 55 | /* Common instruction implementation */ | ||
| 56 | |||
| 57 | /* IP has been set in instruction function */ | ||
| 58 | #define APEI_EXEC_SET_IP 1 | ||
| 59 | |||
| 60 | int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val); | ||
| 61 | int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val); | ||
| 62 | int apei_exec_read_register(struct apei_exec_context *ctx, | ||
| 63 | struct acpi_whea_header *entry); | ||
| 64 | int apei_exec_read_register_value(struct apei_exec_context *ctx, | ||
| 65 | struct acpi_whea_header *entry); | ||
| 66 | int apei_exec_write_register(struct apei_exec_context *ctx, | ||
| 67 | struct acpi_whea_header *entry); | ||
| 68 | int apei_exec_write_register_value(struct apei_exec_context *ctx, | ||
| 69 | struct acpi_whea_header *entry); | ||
| 70 | int apei_exec_noop(struct apei_exec_context *ctx, | ||
| 71 | struct acpi_whea_header *entry); | ||
| 72 | int apei_exec_pre_map_gars(struct apei_exec_context *ctx); | ||
| 73 | int apei_exec_post_unmap_gars(struct apei_exec_context *ctx); | ||
| 74 | |||
| 75 | struct apei_resources { | ||
| 76 | struct list_head iomem; | ||
| 77 | struct list_head ioport; | ||
| 78 | }; | ||
| 79 | |||
| 80 | static inline void apei_resources_init(struct apei_resources *resources) | ||
| 81 | { | ||
| 82 | INIT_LIST_HEAD(&resources->iomem); | ||
| 83 | INIT_LIST_HEAD(&resources->ioport); | ||
| 84 | } | ||
| 85 | |||
| 86 | void apei_resources_fini(struct apei_resources *resources); | ||
| 87 | int apei_resources_sub(struct apei_resources *resources1, | ||
| 88 | struct apei_resources *resources2); | ||
| 89 | int apei_resources_request(struct apei_resources *resources, | ||
| 90 | const char *desc); | ||
| 91 | void apei_resources_release(struct apei_resources *resources); | ||
| 92 | int apei_exec_collect_resources(struct apei_exec_context *ctx, | ||
| 93 | struct apei_resources *resources); | ||
| 94 | |||
| 95 | struct dentry; | ||
| 96 | struct dentry *apei_get_debugfs_dir(void); | ||
| 97 | |||
| 98 | #define apei_estatus_for_each_section(estatus, section) \ | ||
| 99 | for (section = (struct acpi_hest_generic_data *)(estatus + 1); \ | ||
| 100 | (void *)section - (void *)estatus < estatus->data_length; \ | ||
| 101 | section = (void *)(section+1) + section->error_data_length) | ||
| 102 | |||
| 103 | static inline u32 apei_estatus_len(struct acpi_hest_generic_status *estatus) | ||
| 104 | { | ||
| 105 | if (estatus->raw_data_length) | ||
| 106 | return estatus->raw_data_offset + \ | ||
| 107 | estatus->raw_data_length; | ||
| 108 | else | ||
| 109 | return sizeof(*estatus) + estatus->data_length; | ||
| 110 | } | ||
| 111 | |||
| 112 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus); | ||
| 113 | int apei_estatus_check(const struct acpi_hest_generic_status *estatus); | ||
| 114 | #endif | ||
diff --git a/drivers/acpi/apei/cper.c b/drivers/acpi/apei/cper.c new file mode 100644 index 000000000000..f4cf2fc4c8c1 --- /dev/null +++ b/drivers/acpi/apei/cper.c | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | /* | ||
| 2 | * UEFI Common Platform Error Record (CPER) support | ||
| 3 | * | ||
| 4 | * Copyright (C) 2010, Intel Corp. | ||
| 5 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * CPER is the format used to describe platform hardware error by | ||
| 8 | * various APEI tables, such as ERST, BERT and HEST etc. | ||
| 9 | * | ||
| 10 | * For more information about CPER, please refer to Appendix N of UEFI | ||
| 11 | * Specification version 2.3. | ||
| 12 | * | ||
| 13 | * This program is free software; you can redistribute it and/or | ||
| 14 | * modify it under the terms of the GNU General Public License version | ||
| 15 | * 2 as published by the Free Software Foundation. | ||
| 16 | * | ||
| 17 | * This program is distributed in the hope that it will be useful, | ||
| 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | * GNU General Public License for more details. | ||
| 21 | * | ||
| 22 | * You should have received a copy of the GNU General Public License | ||
| 23 | * along with this program; if not, write to the Free Software | ||
| 24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include <linux/kernel.h> | ||
| 28 | #include <linux/module.h> | ||
| 29 | #include <linux/time.h> | ||
| 30 | #include <linux/cper.h> | ||
| 31 | #include <linux/acpi.h> | ||
| 32 | |||
| 33 | /* | ||
| 34 | * CPER record ID need to be unique even after reboot, because record | ||
| 35 | * ID is used as index for ERST storage, while CPER records from | ||
| 36 | * multiple boot may co-exist in ERST. | ||
| 37 | */ | ||
| 38 | u64 cper_next_record_id(void) | ||
| 39 | { | ||
| 40 | static atomic64_t seq; | ||
| 41 | |||
| 42 | if (!atomic64_read(&seq)) | ||
| 43 | atomic64_set(&seq, ((u64)get_seconds()) << 32); | ||
| 44 | |||
| 45 | return atomic64_inc_return(&seq); | ||
| 46 | } | ||
| 47 | EXPORT_SYMBOL_GPL(cper_next_record_id); | ||
| 48 | |||
| 49 | int apei_estatus_check_header(const struct acpi_hest_generic_status *estatus) | ||
| 50 | { | ||
| 51 | if (estatus->data_length && | ||
| 52 | estatus->data_length < sizeof(struct acpi_hest_generic_data)) | ||
| 53 | return -EINVAL; | ||
| 54 | if (estatus->raw_data_length && | ||
| 55 | estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) | ||
| 56 | return -EINVAL; | ||
| 57 | |||
| 58 | return 0; | ||
| 59 | } | ||
| 60 | EXPORT_SYMBOL_GPL(apei_estatus_check_header); | ||
| 61 | |||
| 62 | int apei_estatus_check(const struct acpi_hest_generic_status *estatus) | ||
| 63 | { | ||
| 64 | struct acpi_hest_generic_data *gdata; | ||
| 65 | unsigned int data_len, gedata_len; | ||
| 66 | int rc; | ||
| 67 | |||
| 68 | rc = apei_estatus_check_header(estatus); | ||
| 69 | if (rc) | ||
| 70 | return rc; | ||
| 71 | data_len = estatus->data_length; | ||
| 72 | gdata = (struct acpi_hest_generic_data *)(estatus + 1); | ||
| 73 | while (data_len > sizeof(*gdata)) { | ||
| 74 | gedata_len = gdata->error_data_length; | ||
| 75 | if (gedata_len > data_len - sizeof(*gdata)) | ||
| 76 | return -EINVAL; | ||
| 77 | data_len -= gedata_len + sizeof(*gdata); | ||
| 78 | } | ||
| 79 | if (data_len) | ||
| 80 | return -EINVAL; | ||
| 81 | |||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | EXPORT_SYMBOL_GPL(apei_estatus_check); | ||
diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c new file mode 100644 index 000000000000..465c885938ee --- /dev/null +++ b/drivers/acpi/apei/einj.c | |||
| @@ -0,0 +1,548 @@ | |||
| 1 | /* | ||
| 2 | * APEI Error INJection support | ||
| 3 | * | ||
| 4 | * EINJ provides a hardware error injection mechanism, this is useful | ||
| 5 | * for debugging and testing of other APEI and RAS features. | ||
| 6 | * | ||
| 7 | * For more information about EINJ, please refer to ACPI Specification | ||
| 8 | * version 4.0, section 17.5. | ||
| 9 | * | ||
| 10 | * Copyright 2009-2010 Intel Corp. | ||
| 11 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 12 | * | ||
| 13 | * This program is free software; you can redistribute it and/or | ||
| 14 | * modify it under the terms of the GNU General Public License version | ||
| 15 | * 2 as published by the Free Software Foundation. | ||
| 16 | * | ||
| 17 | * This program is distributed in the hope that it will be useful, | ||
| 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | * GNU General Public License for more details. | ||
| 21 | * | ||
| 22 | * You should have received a copy of the GNU General Public License | ||
| 23 | * along with this program; if not, write to the Free Software | ||
| 24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include <linux/kernel.h> | ||
| 28 | #include <linux/module.h> | ||
| 29 | #include <linux/init.h> | ||
| 30 | #include <linux/io.h> | ||
| 31 | #include <linux/debugfs.h> | ||
| 32 | #include <linux/seq_file.h> | ||
| 33 | #include <linux/nmi.h> | ||
| 34 | #include <linux/delay.h> | ||
| 35 | #include <acpi/acpi.h> | ||
| 36 | |||
| 37 | #include "apei-internal.h" | ||
| 38 | |||
| 39 | #define EINJ_PFX "EINJ: " | ||
| 40 | |||
| 41 | #define SPIN_UNIT 100 /* 100ns */ | ||
| 42 | /* Firmware should respond within 1 miliseconds */ | ||
| 43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | ||
| 44 | |||
| 45 | /* | ||
| 46 | * Some BIOSes allow parameters to the SET_ERROR_TYPE entries in the | ||
| 47 | * EINJ table through an unpublished extension. Use with caution as | ||
| 48 | * most will ignore the parameter and make their own choice of address | ||
| 49 | * for error injection. | ||
| 50 | */ | ||
| 51 | struct einj_parameter { | ||
| 52 | u64 type; | ||
| 53 | u64 reserved1; | ||
| 54 | u64 reserved2; | ||
| 55 | u64 param1; | ||
| 56 | u64 param2; | ||
| 57 | }; | ||
| 58 | |||
| 59 | #define EINJ_OP_BUSY 0x1 | ||
| 60 | #define EINJ_STATUS_SUCCESS 0x0 | ||
| 61 | #define EINJ_STATUS_FAIL 0x1 | ||
| 62 | #define EINJ_STATUS_INVAL 0x2 | ||
| 63 | |||
| 64 | #define EINJ_TAB_ENTRY(tab) \ | ||
| 65 | ((struct acpi_whea_header *)((char *)(tab) + \ | ||
| 66 | sizeof(struct acpi_table_einj))) | ||
| 67 | |||
| 68 | static struct acpi_table_einj *einj_tab; | ||
| 69 | |||
| 70 | static struct apei_resources einj_resources; | ||
| 71 | |||
| 72 | static struct apei_exec_ins_type einj_ins_type[] = { | ||
| 73 | [ACPI_EINJ_READ_REGISTER] = { | ||
| 74 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 75 | .run = apei_exec_read_register, | ||
| 76 | }, | ||
| 77 | [ACPI_EINJ_READ_REGISTER_VALUE] = { | ||
| 78 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 79 | .run = apei_exec_read_register_value, | ||
| 80 | }, | ||
| 81 | [ACPI_EINJ_WRITE_REGISTER] = { | ||
| 82 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 83 | .run = apei_exec_write_register, | ||
| 84 | }, | ||
| 85 | [ACPI_EINJ_WRITE_REGISTER_VALUE] = { | ||
| 86 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 87 | .run = apei_exec_write_register_value, | ||
| 88 | }, | ||
| 89 | [ACPI_EINJ_NOOP] = { | ||
| 90 | .flags = 0, | ||
| 91 | .run = apei_exec_noop, | ||
| 92 | }, | ||
| 93 | }; | ||
| 94 | |||
| 95 | /* | ||
| 96 | * Prevent EINJ interpreter to run simultaneously, because the | ||
| 97 | * corresponding firmware implementation may not work properly when | ||
| 98 | * invoked simultaneously. | ||
| 99 | */ | ||
| 100 | static DEFINE_MUTEX(einj_mutex); | ||
| 101 | |||
| 102 | static struct einj_parameter *einj_param; | ||
| 103 | |||
| 104 | static void einj_exec_ctx_init(struct apei_exec_context *ctx) | ||
| 105 | { | ||
| 106 | apei_exec_ctx_init(ctx, einj_ins_type, ARRAY_SIZE(einj_ins_type), | ||
| 107 | EINJ_TAB_ENTRY(einj_tab), einj_tab->entries); | ||
| 108 | } | ||
| 109 | |||
| 110 | static int __einj_get_available_error_type(u32 *type) | ||
| 111 | { | ||
| 112 | struct apei_exec_context ctx; | ||
| 113 | int rc; | ||
| 114 | |||
| 115 | einj_exec_ctx_init(&ctx); | ||
| 116 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_ERROR_TYPE); | ||
| 117 | if (rc) | ||
| 118 | return rc; | ||
| 119 | *type = apei_exec_ctx_get_output(&ctx); | ||
| 120 | |||
| 121 | return 0; | ||
| 122 | } | ||
| 123 | |||
| 124 | /* Get error injection capabilities of the platform */ | ||
| 125 | static int einj_get_available_error_type(u32 *type) | ||
| 126 | { | ||
| 127 | int rc; | ||
| 128 | |||
| 129 | mutex_lock(&einj_mutex); | ||
| 130 | rc = __einj_get_available_error_type(type); | ||
| 131 | mutex_unlock(&einj_mutex); | ||
| 132 | |||
| 133 | return rc; | ||
| 134 | } | ||
| 135 | |||
| 136 | static int einj_timedout(u64 *t) | ||
| 137 | { | ||
| 138 | if ((s64)*t < SPIN_UNIT) { | ||
| 139 | pr_warning(FW_WARN EINJ_PFX | ||
| 140 | "Firmware does not respond in time\n"); | ||
| 141 | return 1; | ||
| 142 | } | ||
| 143 | *t -= SPIN_UNIT; | ||
| 144 | ndelay(SPIN_UNIT); | ||
| 145 | touch_nmi_watchdog(); | ||
| 146 | return 0; | ||
| 147 | } | ||
| 148 | |||
| 149 | static u64 einj_get_parameter_address(void) | ||
| 150 | { | ||
| 151 | int i; | ||
| 152 | u64 paddr = 0; | ||
| 153 | struct acpi_whea_header *entry; | ||
| 154 | |||
| 155 | entry = EINJ_TAB_ENTRY(einj_tab); | ||
| 156 | for (i = 0; i < einj_tab->entries; i++) { | ||
| 157 | if (entry->action == ACPI_EINJ_SET_ERROR_TYPE && | ||
| 158 | entry->instruction == ACPI_EINJ_WRITE_REGISTER && | ||
| 159 | entry->register_region.space_id == | ||
| 160 | ACPI_ADR_SPACE_SYSTEM_MEMORY) | ||
| 161 | memcpy(&paddr, &entry->register_region.address, | ||
| 162 | sizeof(paddr)); | ||
| 163 | entry++; | ||
| 164 | } | ||
| 165 | |||
| 166 | return paddr; | ||
| 167 | } | ||
| 168 | |||
| 169 | /* do sanity check to trigger table */ | ||
| 170 | static int einj_check_trigger_header(struct acpi_einj_trigger *trigger_tab) | ||
| 171 | { | ||
| 172 | if (trigger_tab->header_size != sizeof(struct acpi_einj_trigger)) | ||
| 173 | return -EINVAL; | ||
| 174 | if (trigger_tab->table_size > PAGE_SIZE || | ||
| 175 | trigger_tab->table_size <= trigger_tab->header_size) | ||
| 176 | return -EINVAL; | ||
| 177 | if (trigger_tab->entry_count != | ||
| 178 | (trigger_tab->table_size - trigger_tab->header_size) / | ||
| 179 | sizeof(struct acpi_einj_entry)) | ||
| 180 | return -EINVAL; | ||
| 181 | |||
| 182 | return 0; | ||
| 183 | } | ||
| 184 | |||
| 185 | /* Execute instructions in trigger error action table */ | ||
| 186 | static int __einj_error_trigger(u64 trigger_paddr) | ||
| 187 | { | ||
| 188 | struct acpi_einj_trigger *trigger_tab = NULL; | ||
| 189 | struct apei_exec_context trigger_ctx; | ||
| 190 | struct apei_resources trigger_resources; | ||
| 191 | struct acpi_whea_header *trigger_entry; | ||
| 192 | struct resource *r; | ||
| 193 | u32 table_size; | ||
| 194 | int rc = -EIO; | ||
| 195 | |||
| 196 | r = request_mem_region(trigger_paddr, sizeof(*trigger_tab), | ||
| 197 | "APEI EINJ Trigger Table"); | ||
| 198 | if (!r) { | ||
| 199 | pr_err(EINJ_PFX | ||
| 200 | "Can not request iomem region <%016llx-%016llx> for Trigger table.\n", | ||
| 201 | (unsigned long long)trigger_paddr, | ||
| 202 | (unsigned long long)trigger_paddr+sizeof(*trigger_tab)); | ||
| 203 | goto out; | ||
| 204 | } | ||
| 205 | trigger_tab = ioremap_cache(trigger_paddr, sizeof(*trigger_tab)); | ||
| 206 | if (!trigger_tab) { | ||
| 207 | pr_err(EINJ_PFX "Failed to map trigger table!\n"); | ||
| 208 | goto out_rel_header; | ||
| 209 | } | ||
| 210 | rc = einj_check_trigger_header(trigger_tab); | ||
| 211 | if (rc) { | ||
| 212 | pr_warning(FW_BUG EINJ_PFX | ||
| 213 | "The trigger error action table is invalid\n"); | ||
| 214 | goto out_rel_header; | ||
| 215 | } | ||
| 216 | rc = -EIO; | ||
| 217 | table_size = trigger_tab->table_size; | ||
| 218 | r = request_mem_region(trigger_paddr + sizeof(*trigger_tab), | ||
| 219 | table_size - sizeof(*trigger_tab), | ||
| 220 | "APEI EINJ Trigger Table"); | ||
| 221 | if (!r) { | ||
| 222 | pr_err(EINJ_PFX | ||
| 223 | "Can not request iomem region <%016llx-%016llx> for Trigger Table Entry.\n", | ||
| 224 | (unsigned long long)trigger_paddr+sizeof(*trigger_tab), | ||
| 225 | (unsigned long long)trigger_paddr + table_size); | ||
| 226 | goto out_rel_header; | ||
| 227 | } | ||
| 228 | iounmap(trigger_tab); | ||
| 229 | trigger_tab = ioremap_cache(trigger_paddr, table_size); | ||
| 230 | if (!trigger_tab) { | ||
| 231 | pr_err(EINJ_PFX "Failed to map trigger table!\n"); | ||
| 232 | goto out_rel_entry; | ||
| 233 | } | ||
| 234 | trigger_entry = (struct acpi_whea_header *) | ||
| 235 | ((char *)trigger_tab + sizeof(struct acpi_einj_trigger)); | ||
| 236 | apei_resources_init(&trigger_resources); | ||
| 237 | apei_exec_ctx_init(&trigger_ctx, einj_ins_type, | ||
| 238 | ARRAY_SIZE(einj_ins_type), | ||
| 239 | trigger_entry, trigger_tab->entry_count); | ||
| 240 | rc = apei_exec_collect_resources(&trigger_ctx, &trigger_resources); | ||
| 241 | if (rc) | ||
| 242 | goto out_fini; | ||
| 243 | rc = apei_resources_sub(&trigger_resources, &einj_resources); | ||
| 244 | if (rc) | ||
| 245 | goto out_fini; | ||
| 246 | rc = apei_resources_request(&trigger_resources, "APEI EINJ Trigger"); | ||
| 247 | if (rc) | ||
| 248 | goto out_fini; | ||
| 249 | rc = apei_exec_pre_map_gars(&trigger_ctx); | ||
| 250 | if (rc) | ||
| 251 | goto out_release; | ||
| 252 | |||
| 253 | rc = apei_exec_run(&trigger_ctx, ACPI_EINJ_TRIGGER_ERROR); | ||
| 254 | |||
| 255 | apei_exec_post_unmap_gars(&trigger_ctx); | ||
| 256 | out_release: | ||
| 257 | apei_resources_release(&trigger_resources); | ||
| 258 | out_fini: | ||
| 259 | apei_resources_fini(&trigger_resources); | ||
| 260 | out_rel_entry: | ||
| 261 | release_mem_region(trigger_paddr + sizeof(*trigger_tab), | ||
| 262 | table_size - sizeof(*trigger_tab)); | ||
| 263 | out_rel_header: | ||
| 264 | release_mem_region(trigger_paddr, sizeof(*trigger_tab)); | ||
| 265 | out: | ||
| 266 | if (trigger_tab) | ||
| 267 | iounmap(trigger_tab); | ||
| 268 | |||
| 269 | return rc; | ||
| 270 | } | ||
| 271 | |||
| 272 | static int __einj_error_inject(u32 type, u64 param1, u64 param2) | ||
| 273 | { | ||
| 274 | struct apei_exec_context ctx; | ||
| 275 | u64 val, trigger_paddr, timeout = FIRMWARE_TIMEOUT; | ||
| 276 | int rc; | ||
| 277 | |||
| 278 | einj_exec_ctx_init(&ctx); | ||
| 279 | |||
| 280 | rc = apei_exec_run(&ctx, ACPI_EINJ_BEGIN_OPERATION); | ||
| 281 | if (rc) | ||
| 282 | return rc; | ||
| 283 | apei_exec_ctx_set_input(&ctx, type); | ||
| 284 | rc = apei_exec_run(&ctx, ACPI_EINJ_SET_ERROR_TYPE); | ||
| 285 | if (rc) | ||
| 286 | return rc; | ||
| 287 | if (einj_param) { | ||
| 288 | writeq(param1, &einj_param->param1); | ||
| 289 | writeq(param2, &einj_param->param2); | ||
| 290 | } | ||
| 291 | rc = apei_exec_run(&ctx, ACPI_EINJ_EXECUTE_OPERATION); | ||
| 292 | if (rc) | ||
| 293 | return rc; | ||
| 294 | for (;;) { | ||
| 295 | rc = apei_exec_run(&ctx, ACPI_EINJ_CHECK_BUSY_STATUS); | ||
| 296 | if (rc) | ||
| 297 | return rc; | ||
| 298 | val = apei_exec_ctx_get_output(&ctx); | ||
| 299 | if (!(val & EINJ_OP_BUSY)) | ||
| 300 | break; | ||
| 301 | if (einj_timedout(&timeout)) | ||
| 302 | return -EIO; | ||
| 303 | } | ||
| 304 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_COMMAND_STATUS); | ||
| 305 | if (rc) | ||
| 306 | return rc; | ||
| 307 | val = apei_exec_ctx_get_output(&ctx); | ||
| 308 | if (val != EINJ_STATUS_SUCCESS) | ||
| 309 | return -EBUSY; | ||
| 310 | |||
| 311 | rc = apei_exec_run(&ctx, ACPI_EINJ_GET_TRIGGER_TABLE); | ||
| 312 | if (rc) | ||
| 313 | return rc; | ||
| 314 | trigger_paddr = apei_exec_ctx_get_output(&ctx); | ||
| 315 | rc = __einj_error_trigger(trigger_paddr); | ||
| 316 | if (rc) | ||
| 317 | return rc; | ||
| 318 | rc = apei_exec_run(&ctx, ACPI_EINJ_END_OPERATION); | ||
| 319 | |||
| 320 | return rc; | ||
| 321 | } | ||
| 322 | |||
| 323 | /* Inject the specified hardware error */ | ||
| 324 | static int einj_error_inject(u32 type, u64 param1, u64 param2) | ||
| 325 | { | ||
| 326 | int rc; | ||
| 327 | |||
| 328 | mutex_lock(&einj_mutex); | ||
| 329 | rc = __einj_error_inject(type, param1, param2); | ||
| 330 | mutex_unlock(&einj_mutex); | ||
| 331 | |||
| 332 | return rc; | ||
| 333 | } | ||
| 334 | |||
| 335 | static u32 error_type; | ||
| 336 | static u64 error_param1; | ||
| 337 | static u64 error_param2; | ||
| 338 | static struct dentry *einj_debug_dir; | ||
| 339 | |||
| 340 | static int available_error_type_show(struct seq_file *m, void *v) | ||
| 341 | { | ||
| 342 | int rc; | ||
| 343 | u32 available_error_type = 0; | ||
| 344 | |||
| 345 | rc = einj_get_available_error_type(&available_error_type); | ||
| 346 | if (rc) | ||
| 347 | return rc; | ||
| 348 | if (available_error_type & 0x0001) | ||
| 349 | seq_printf(m, "0x00000001\tProcessor Correctable\n"); | ||
| 350 | if (available_error_type & 0x0002) | ||
| 351 | seq_printf(m, "0x00000002\tProcessor Uncorrectable non-fatal\n"); | ||
| 352 | if (available_error_type & 0x0004) | ||
| 353 | seq_printf(m, "0x00000004\tProcessor Uncorrectable fatal\n"); | ||
| 354 | if (available_error_type & 0x0008) | ||
| 355 | seq_printf(m, "0x00000008\tMemory Correctable\n"); | ||
| 356 | if (available_error_type & 0x0010) | ||
| 357 | seq_printf(m, "0x00000010\tMemory Uncorrectable non-fatal\n"); | ||
| 358 | if (available_error_type & 0x0020) | ||
| 359 | seq_printf(m, "0x00000020\tMemory Uncorrectable fatal\n"); | ||
| 360 | if (available_error_type & 0x0040) | ||
| 361 | seq_printf(m, "0x00000040\tPCI Express Correctable\n"); | ||
| 362 | if (available_error_type & 0x0080) | ||
| 363 | seq_printf(m, "0x00000080\tPCI Express Uncorrectable non-fatal\n"); | ||
| 364 | if (available_error_type & 0x0100) | ||
| 365 | seq_printf(m, "0x00000100\tPCI Express Uncorrectable fatal\n"); | ||
| 366 | if (available_error_type & 0x0200) | ||
| 367 | seq_printf(m, "0x00000200\tPlatform Correctable\n"); | ||
| 368 | if (available_error_type & 0x0400) | ||
| 369 | seq_printf(m, "0x00000400\tPlatform Uncorrectable non-fatal\n"); | ||
| 370 | if (available_error_type & 0x0800) | ||
| 371 | seq_printf(m, "0x00000800\tPlatform Uncorrectable fatal\n"); | ||
| 372 | |||
| 373 | return 0; | ||
| 374 | } | ||
| 375 | |||
| 376 | static int available_error_type_open(struct inode *inode, struct file *file) | ||
| 377 | { | ||
| 378 | return single_open(file, available_error_type_show, NULL); | ||
| 379 | } | ||
| 380 | |||
| 381 | static const struct file_operations available_error_type_fops = { | ||
| 382 | .open = available_error_type_open, | ||
| 383 | .read = seq_read, | ||
| 384 | .llseek = seq_lseek, | ||
| 385 | .release = single_release, | ||
| 386 | }; | ||
| 387 | |||
| 388 | static int error_type_get(void *data, u64 *val) | ||
| 389 | { | ||
| 390 | *val = error_type; | ||
| 391 | |||
| 392 | return 0; | ||
| 393 | } | ||
| 394 | |||
| 395 | static int error_type_set(void *data, u64 val) | ||
| 396 | { | ||
| 397 | int rc; | ||
| 398 | u32 available_error_type = 0; | ||
| 399 | |||
| 400 | /* Only one error type can be specified */ | ||
| 401 | if (val & (val - 1)) | ||
| 402 | return -EINVAL; | ||
| 403 | rc = einj_get_available_error_type(&available_error_type); | ||
| 404 | if (rc) | ||
| 405 | return rc; | ||
| 406 | if (!(val & available_error_type)) | ||
| 407 | return -EINVAL; | ||
| 408 | error_type = val; | ||
| 409 | |||
| 410 | return 0; | ||
| 411 | } | ||
| 412 | |||
| 413 | DEFINE_SIMPLE_ATTRIBUTE(error_type_fops, error_type_get, | ||
| 414 | error_type_set, "0x%llx\n"); | ||
| 415 | |||
| 416 | static int error_inject_set(void *data, u64 val) | ||
| 417 | { | ||
| 418 | if (!error_type) | ||
| 419 | return -EINVAL; | ||
| 420 | |||
| 421 | return einj_error_inject(error_type, error_param1, error_param2); | ||
| 422 | } | ||
| 423 | |||
| 424 | DEFINE_SIMPLE_ATTRIBUTE(error_inject_fops, NULL, | ||
| 425 | error_inject_set, "%llu\n"); | ||
| 426 | |||
| 427 | static int einj_check_table(struct acpi_table_einj *einj_tab) | ||
| 428 | { | ||
| 429 | if (einj_tab->header_length != sizeof(struct acpi_table_einj)) | ||
| 430 | return -EINVAL; | ||
| 431 | if (einj_tab->header.length < sizeof(struct acpi_table_einj)) | ||
| 432 | return -EINVAL; | ||
| 433 | if (einj_tab->entries != | ||
| 434 | (einj_tab->header.length - sizeof(struct acpi_table_einj)) / | ||
| 435 | sizeof(struct acpi_einj_entry)) | ||
| 436 | return -EINVAL; | ||
| 437 | |||
| 438 | return 0; | ||
| 439 | } | ||
| 440 | |||
| 441 | static int __init einj_init(void) | ||
| 442 | { | ||
| 443 | int rc; | ||
| 444 | u64 param_paddr; | ||
| 445 | acpi_status status; | ||
| 446 | struct dentry *fentry; | ||
| 447 | struct apei_exec_context ctx; | ||
| 448 | |||
| 449 | if (acpi_disabled) | ||
| 450 | return -ENODEV; | ||
| 451 | |||
| 452 | status = acpi_get_table(ACPI_SIG_EINJ, 0, | ||
| 453 | (struct acpi_table_header **)&einj_tab); | ||
| 454 | if (status == AE_NOT_FOUND) { | ||
| 455 | pr_info(EINJ_PFX "Table is not found!\n"); | ||
| 456 | return -ENODEV; | ||
| 457 | } else if (ACPI_FAILURE(status)) { | ||
| 458 | const char *msg = acpi_format_exception(status); | ||
| 459 | pr_err(EINJ_PFX "Failed to get table, %s\n", msg); | ||
| 460 | return -EINVAL; | ||
| 461 | } | ||
| 462 | |||
| 463 | rc = einj_check_table(einj_tab); | ||
| 464 | if (rc) { | ||
| 465 | pr_warning(FW_BUG EINJ_PFX "EINJ table is invalid\n"); | ||
| 466 | return -EINVAL; | ||
| 467 | } | ||
| 468 | |||
| 469 | rc = -ENOMEM; | ||
| 470 | einj_debug_dir = debugfs_create_dir("einj", apei_get_debugfs_dir()); | ||
| 471 | if (!einj_debug_dir) | ||
| 472 | goto err_cleanup; | ||
| 473 | fentry = debugfs_create_file("available_error_type", S_IRUSR, | ||
| 474 | einj_debug_dir, NULL, | ||
| 475 | &available_error_type_fops); | ||
| 476 | if (!fentry) | ||
| 477 | goto err_cleanup; | ||
| 478 | fentry = debugfs_create_file("error_type", S_IRUSR | S_IWUSR, | ||
| 479 | einj_debug_dir, NULL, &error_type_fops); | ||
| 480 | if (!fentry) | ||
| 481 | goto err_cleanup; | ||
| 482 | fentry = debugfs_create_x64("param1", S_IRUSR | S_IWUSR, | ||
| 483 | einj_debug_dir, &error_param1); | ||
| 484 | if (!fentry) | ||
| 485 | goto err_cleanup; | ||
| 486 | fentry = debugfs_create_x64("param2", S_IRUSR | S_IWUSR, | ||
| 487 | einj_debug_dir, &error_param2); | ||
| 488 | if (!fentry) | ||
| 489 | goto err_cleanup; | ||
| 490 | fentry = debugfs_create_file("error_inject", S_IWUSR, | ||
| 491 | einj_debug_dir, NULL, &error_inject_fops); | ||
| 492 | if (!fentry) | ||
| 493 | goto err_cleanup; | ||
| 494 | |||
| 495 | apei_resources_init(&einj_resources); | ||
| 496 | einj_exec_ctx_init(&ctx); | ||
| 497 | rc = apei_exec_collect_resources(&ctx, &einj_resources); | ||
| 498 | if (rc) | ||
| 499 | goto err_fini; | ||
| 500 | rc = apei_resources_request(&einj_resources, "APEI EINJ"); | ||
| 501 | if (rc) | ||
| 502 | goto err_fini; | ||
| 503 | rc = apei_exec_pre_map_gars(&ctx); | ||
| 504 | if (rc) | ||
| 505 | goto err_release; | ||
| 506 | param_paddr = einj_get_parameter_address(); | ||
| 507 | if (param_paddr) { | ||
| 508 | einj_param = ioremap(param_paddr, sizeof(*einj_param)); | ||
| 509 | rc = -ENOMEM; | ||
| 510 | if (!einj_param) | ||
| 511 | goto err_unmap; | ||
| 512 | } | ||
| 513 | |||
| 514 | pr_info(EINJ_PFX "Error INJection is initialized.\n"); | ||
| 515 | |||
| 516 | return 0; | ||
| 517 | |||
| 518 | err_unmap: | ||
| 519 | apei_exec_post_unmap_gars(&ctx); | ||
| 520 | err_release: | ||
| 521 | apei_resources_release(&einj_resources); | ||
| 522 | err_fini: | ||
| 523 | apei_resources_fini(&einj_resources); | ||
| 524 | err_cleanup: | ||
| 525 | debugfs_remove_recursive(einj_debug_dir); | ||
| 526 | |||
| 527 | return rc; | ||
| 528 | } | ||
| 529 | |||
| 530 | static void __exit einj_exit(void) | ||
| 531 | { | ||
| 532 | struct apei_exec_context ctx; | ||
| 533 | |||
| 534 | if (einj_param) | ||
| 535 | iounmap(einj_param); | ||
| 536 | einj_exec_ctx_init(&ctx); | ||
| 537 | apei_exec_post_unmap_gars(&ctx); | ||
| 538 | apei_resources_release(&einj_resources); | ||
| 539 | apei_resources_fini(&einj_resources); | ||
| 540 | debugfs_remove_recursive(einj_debug_dir); | ||
| 541 | } | ||
| 542 | |||
| 543 | module_init(einj_init); | ||
| 544 | module_exit(einj_exit); | ||
| 545 | |||
| 546 | MODULE_AUTHOR("Huang Ying"); | ||
| 547 | MODULE_DESCRIPTION("APEI Error INJection support"); | ||
| 548 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c new file mode 100644 index 000000000000..2ebc39115507 --- /dev/null +++ b/drivers/acpi/apei/erst.c | |||
| @@ -0,0 +1,855 @@ | |||
| 1 | /* | ||
| 2 | * APEI Error Record Serialization Table support | ||
| 3 | * | ||
| 4 | * ERST is a way provided by APEI to save and retrieve hardware error | ||
| 5 | * infomation to and from a persistent store. | ||
| 6 | * | ||
| 7 | * For more information about ERST, please refer to ACPI Specification | ||
| 8 | * version 4.0, section 17.4. | ||
| 9 | * | ||
| 10 | * Copyright 2010 Intel Corp. | ||
| 11 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 12 | * | ||
| 13 | * This program is free software; you can redistribute it and/or | ||
| 14 | * modify it under the terms of the GNU General Public License version | ||
| 15 | * 2 as published by the Free Software Foundation. | ||
| 16 | * | ||
| 17 | * This program is distributed in the hope that it will be useful, | ||
| 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | * GNU General Public License for more details. | ||
| 21 | * | ||
| 22 | * You should have received a copy of the GNU General Public License | ||
| 23 | * along with this program; if not, write to the Free Software | ||
| 24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include <linux/kernel.h> | ||
| 28 | #include <linux/module.h> | ||
| 29 | #include <linux/init.h> | ||
| 30 | #include <linux/delay.h> | ||
| 31 | #include <linux/io.h> | ||
| 32 | #include <linux/acpi.h> | ||
| 33 | #include <linux/uaccess.h> | ||
| 34 | #include <linux/cper.h> | ||
| 35 | #include <linux/nmi.h> | ||
| 36 | #include <acpi/apei.h> | ||
| 37 | |||
| 38 | #include "apei-internal.h" | ||
| 39 | |||
| 40 | #define ERST_PFX "ERST: " | ||
| 41 | |||
| 42 | /* ERST command status */ | ||
| 43 | #define ERST_STATUS_SUCCESS 0x0 | ||
| 44 | #define ERST_STATUS_NOT_ENOUGH_SPACE 0x1 | ||
| 45 | #define ERST_STATUS_HARDWARE_NOT_AVAILABLE 0x2 | ||
| 46 | #define ERST_STATUS_FAILED 0x3 | ||
| 47 | #define ERST_STATUS_RECORD_STORE_EMPTY 0x4 | ||
| 48 | #define ERST_STATUS_RECORD_NOT_FOUND 0x5 | ||
| 49 | |||
| 50 | #define ERST_TAB_ENTRY(tab) \ | ||
| 51 | ((struct acpi_whea_header *)((char *)(tab) + \ | ||
| 52 | sizeof(struct acpi_table_erst))) | ||
| 53 | |||
| 54 | #define SPIN_UNIT 100 /* 100ns */ | ||
| 55 | /* Firmware should respond within 1 miliseconds */ | ||
| 56 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | ||
| 57 | #define FIRMWARE_MAX_STALL 50 /* 50us */ | ||
| 58 | |||
| 59 | int erst_disable; | ||
| 60 | EXPORT_SYMBOL_GPL(erst_disable); | ||
| 61 | |||
| 62 | static struct acpi_table_erst *erst_tab; | ||
| 63 | |||
| 64 | /* ERST Error Log Address Range atrributes */ | ||
| 65 | #define ERST_RANGE_RESERVED 0x0001 | ||
| 66 | #define ERST_RANGE_NVRAM 0x0002 | ||
| 67 | #define ERST_RANGE_SLOW 0x0004 | ||
| 68 | |||
| 69 | /* | ||
| 70 | * ERST Error Log Address Range, used as buffer for reading/writing | ||
| 71 | * error records. | ||
| 72 | */ | ||
| 73 | static struct erst_erange { | ||
| 74 | u64 base; | ||
| 75 | u64 size; | ||
| 76 | void __iomem *vaddr; | ||
| 77 | u32 attr; | ||
| 78 | } erst_erange; | ||
| 79 | |||
| 80 | /* | ||
| 81 | * Prevent ERST interpreter to run simultaneously, because the | ||
| 82 | * corresponding firmware implementation may not work properly when | ||
| 83 | * invoked simultaneously. | ||
| 84 | * | ||
| 85 | * It is used to provide exclusive accessing for ERST Error Log | ||
| 86 | * Address Range too. | ||
| 87 | */ | ||
| 88 | static DEFINE_SPINLOCK(erst_lock); | ||
| 89 | |||
| 90 | static inline int erst_errno(int command_status) | ||
| 91 | { | ||
| 92 | switch (command_status) { | ||
| 93 | case ERST_STATUS_SUCCESS: | ||
| 94 | return 0; | ||
| 95 | case ERST_STATUS_HARDWARE_NOT_AVAILABLE: | ||
| 96 | return -ENODEV; | ||
| 97 | case ERST_STATUS_NOT_ENOUGH_SPACE: | ||
| 98 | return -ENOSPC; | ||
| 99 | case ERST_STATUS_RECORD_STORE_EMPTY: | ||
| 100 | case ERST_STATUS_RECORD_NOT_FOUND: | ||
| 101 | return -ENOENT; | ||
| 102 | default: | ||
| 103 | return -EINVAL; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | static int erst_timedout(u64 *t, u64 spin_unit) | ||
| 108 | { | ||
| 109 | if ((s64)*t < spin_unit) { | ||
| 110 | pr_warning(FW_WARN ERST_PFX | ||
| 111 | "Firmware does not respond in time\n"); | ||
| 112 | return 1; | ||
| 113 | } | ||
| 114 | *t -= spin_unit; | ||
| 115 | ndelay(spin_unit); | ||
| 116 | touch_nmi_watchdog(); | ||
| 117 | return 0; | ||
| 118 | } | ||
| 119 | |||
| 120 | static int erst_exec_load_var1(struct apei_exec_context *ctx, | ||
| 121 | struct acpi_whea_header *entry) | ||
| 122 | { | ||
| 123 | return __apei_exec_read_register(entry, &ctx->var1); | ||
| 124 | } | ||
| 125 | |||
| 126 | static int erst_exec_load_var2(struct apei_exec_context *ctx, | ||
| 127 | struct acpi_whea_header *entry) | ||
| 128 | { | ||
| 129 | return __apei_exec_read_register(entry, &ctx->var2); | ||
| 130 | } | ||
| 131 | |||
| 132 | static int erst_exec_store_var1(struct apei_exec_context *ctx, | ||
| 133 | struct acpi_whea_header *entry) | ||
| 134 | { | ||
| 135 | return __apei_exec_write_register(entry, ctx->var1); | ||
| 136 | } | ||
| 137 | |||
| 138 | static int erst_exec_add(struct apei_exec_context *ctx, | ||
| 139 | struct acpi_whea_header *entry) | ||
| 140 | { | ||
| 141 | ctx->var1 += ctx->var2; | ||
| 142 | return 0; | ||
| 143 | } | ||
| 144 | |||
| 145 | static int erst_exec_subtract(struct apei_exec_context *ctx, | ||
| 146 | struct acpi_whea_header *entry) | ||
| 147 | { | ||
| 148 | ctx->var1 -= ctx->var2; | ||
| 149 | return 0; | ||
| 150 | } | ||
| 151 | |||
| 152 | static int erst_exec_add_value(struct apei_exec_context *ctx, | ||
| 153 | struct acpi_whea_header *entry) | ||
| 154 | { | ||
| 155 | int rc; | ||
| 156 | u64 val; | ||
| 157 | |||
| 158 | rc = __apei_exec_read_register(entry, &val); | ||
| 159 | if (rc) | ||
| 160 | return rc; | ||
| 161 | val += ctx->value; | ||
| 162 | rc = __apei_exec_write_register(entry, val); | ||
| 163 | return rc; | ||
| 164 | } | ||
| 165 | |||
| 166 | static int erst_exec_subtract_value(struct apei_exec_context *ctx, | ||
| 167 | struct acpi_whea_header *entry) | ||
| 168 | { | ||
| 169 | int rc; | ||
| 170 | u64 val; | ||
| 171 | |||
| 172 | rc = __apei_exec_read_register(entry, &val); | ||
| 173 | if (rc) | ||
| 174 | return rc; | ||
| 175 | val -= ctx->value; | ||
| 176 | rc = __apei_exec_write_register(entry, val); | ||
| 177 | return rc; | ||
| 178 | } | ||
| 179 | |||
| 180 | static int erst_exec_stall(struct apei_exec_context *ctx, | ||
| 181 | struct acpi_whea_header *entry) | ||
| 182 | { | ||
| 183 | u64 stall_time; | ||
| 184 | |||
| 185 | if (ctx->value > FIRMWARE_MAX_STALL) { | ||
| 186 | if (!in_nmi()) | ||
| 187 | pr_warning(FW_WARN ERST_PFX | ||
| 188 | "Too long stall time for stall instruction: %llx.\n", | ||
| 189 | ctx->value); | ||
| 190 | stall_time = FIRMWARE_MAX_STALL; | ||
| 191 | } else | ||
| 192 | stall_time = ctx->value; | ||
| 193 | udelay(stall_time); | ||
| 194 | return 0; | ||
| 195 | } | ||
| 196 | |||
| 197 | static int erst_exec_stall_while_true(struct apei_exec_context *ctx, | ||
| 198 | struct acpi_whea_header *entry) | ||
| 199 | { | ||
| 200 | int rc; | ||
| 201 | u64 val; | ||
| 202 | u64 timeout = FIRMWARE_TIMEOUT; | ||
| 203 | u64 stall_time; | ||
| 204 | |||
| 205 | if (ctx->var1 > FIRMWARE_MAX_STALL) { | ||
| 206 | if (!in_nmi()) | ||
| 207 | pr_warning(FW_WARN ERST_PFX | ||
| 208 | "Too long stall time for stall while true instruction: %llx.\n", | ||
| 209 | ctx->var1); | ||
| 210 | stall_time = FIRMWARE_MAX_STALL; | ||
| 211 | } else | ||
| 212 | stall_time = ctx->var1; | ||
| 213 | |||
| 214 | for (;;) { | ||
| 215 | rc = __apei_exec_read_register(entry, &val); | ||
| 216 | if (rc) | ||
| 217 | return rc; | ||
| 218 | if (val != ctx->value) | ||
| 219 | break; | ||
| 220 | if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC)) | ||
| 221 | return -EIO; | ||
| 222 | } | ||
| 223 | return 0; | ||
| 224 | } | ||
| 225 | |||
| 226 | static int erst_exec_skip_next_instruction_if_true( | ||
| 227 | struct apei_exec_context *ctx, | ||
| 228 | struct acpi_whea_header *entry) | ||
| 229 | { | ||
| 230 | int rc; | ||
| 231 | u64 val; | ||
| 232 | |||
| 233 | rc = __apei_exec_read_register(entry, &val); | ||
| 234 | if (rc) | ||
| 235 | return rc; | ||
| 236 | if (val == ctx->value) { | ||
| 237 | ctx->ip += 2; | ||
| 238 | return APEI_EXEC_SET_IP; | ||
| 239 | } | ||
| 240 | |||
| 241 | return 0; | ||
| 242 | } | ||
| 243 | |||
| 244 | static int erst_exec_goto(struct apei_exec_context *ctx, | ||
| 245 | struct acpi_whea_header *entry) | ||
| 246 | { | ||
| 247 | ctx->ip = ctx->value; | ||
| 248 | return APEI_EXEC_SET_IP; | ||
| 249 | } | ||
| 250 | |||
| 251 | static int erst_exec_set_src_address_base(struct apei_exec_context *ctx, | ||
| 252 | struct acpi_whea_header *entry) | ||
| 253 | { | ||
| 254 | return __apei_exec_read_register(entry, &ctx->src_base); | ||
| 255 | } | ||
| 256 | |||
| 257 | static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx, | ||
| 258 | struct acpi_whea_header *entry) | ||
| 259 | { | ||
| 260 | return __apei_exec_read_register(entry, &ctx->dst_base); | ||
| 261 | } | ||
| 262 | |||
| 263 | static int erst_exec_move_data(struct apei_exec_context *ctx, | ||
| 264 | struct acpi_whea_header *entry) | ||
| 265 | { | ||
| 266 | int rc; | ||
| 267 | u64 offset; | ||
| 268 | |||
| 269 | rc = __apei_exec_read_register(entry, &offset); | ||
| 270 | if (rc) | ||
| 271 | return rc; | ||
| 272 | memmove((void *)ctx->dst_base + offset, | ||
| 273 | (void *)ctx->src_base + offset, | ||
| 274 | ctx->var2); | ||
| 275 | |||
| 276 | return 0; | ||
| 277 | } | ||
| 278 | |||
| 279 | static struct apei_exec_ins_type erst_ins_type[] = { | ||
| 280 | [ACPI_ERST_READ_REGISTER] = { | ||
| 281 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 282 | .run = apei_exec_read_register, | ||
| 283 | }, | ||
| 284 | [ACPI_ERST_READ_REGISTER_VALUE] = { | ||
| 285 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 286 | .run = apei_exec_read_register_value, | ||
| 287 | }, | ||
| 288 | [ACPI_ERST_WRITE_REGISTER] = { | ||
| 289 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 290 | .run = apei_exec_write_register, | ||
| 291 | }, | ||
| 292 | [ACPI_ERST_WRITE_REGISTER_VALUE] = { | ||
| 293 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 294 | .run = apei_exec_write_register_value, | ||
| 295 | }, | ||
| 296 | [ACPI_ERST_NOOP] = { | ||
| 297 | .flags = 0, | ||
| 298 | .run = apei_exec_noop, | ||
| 299 | }, | ||
| 300 | [ACPI_ERST_LOAD_VAR1] = { | ||
| 301 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 302 | .run = erst_exec_load_var1, | ||
| 303 | }, | ||
| 304 | [ACPI_ERST_LOAD_VAR2] = { | ||
| 305 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 306 | .run = erst_exec_load_var2, | ||
| 307 | }, | ||
| 308 | [ACPI_ERST_STORE_VAR1] = { | ||
| 309 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 310 | .run = erst_exec_store_var1, | ||
| 311 | }, | ||
| 312 | [ACPI_ERST_ADD] = { | ||
| 313 | .flags = 0, | ||
| 314 | .run = erst_exec_add, | ||
| 315 | }, | ||
| 316 | [ACPI_ERST_SUBTRACT] = { | ||
| 317 | .flags = 0, | ||
| 318 | .run = erst_exec_subtract, | ||
| 319 | }, | ||
| 320 | [ACPI_ERST_ADD_VALUE] = { | ||
| 321 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 322 | .run = erst_exec_add_value, | ||
| 323 | }, | ||
| 324 | [ACPI_ERST_SUBTRACT_VALUE] = { | ||
| 325 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 326 | .run = erst_exec_subtract_value, | ||
| 327 | }, | ||
| 328 | [ACPI_ERST_STALL] = { | ||
| 329 | .flags = 0, | ||
| 330 | .run = erst_exec_stall, | ||
| 331 | }, | ||
| 332 | [ACPI_ERST_STALL_WHILE_TRUE] = { | ||
| 333 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 334 | .run = erst_exec_stall_while_true, | ||
| 335 | }, | ||
| 336 | [ACPI_ERST_SKIP_NEXT_IF_TRUE] = { | ||
| 337 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 338 | .run = erst_exec_skip_next_instruction_if_true, | ||
| 339 | }, | ||
| 340 | [ACPI_ERST_GOTO] = { | ||
| 341 | .flags = 0, | ||
| 342 | .run = erst_exec_goto, | ||
| 343 | }, | ||
| 344 | [ACPI_ERST_SET_SRC_ADDRESS_BASE] = { | ||
| 345 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 346 | .run = erst_exec_set_src_address_base, | ||
| 347 | }, | ||
| 348 | [ACPI_ERST_SET_DST_ADDRESS_BASE] = { | ||
| 349 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 350 | .run = erst_exec_set_dst_address_base, | ||
| 351 | }, | ||
| 352 | [ACPI_ERST_MOVE_DATA] = { | ||
| 353 | .flags = APEI_EXEC_INS_ACCESS_REGISTER, | ||
| 354 | .run = erst_exec_move_data, | ||
| 355 | }, | ||
| 356 | }; | ||
| 357 | |||
| 358 | static inline void erst_exec_ctx_init(struct apei_exec_context *ctx) | ||
| 359 | { | ||
| 360 | apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type), | ||
| 361 | ERST_TAB_ENTRY(erst_tab), erst_tab->entries); | ||
| 362 | } | ||
| 363 | |||
| 364 | static int erst_get_erange(struct erst_erange *range) | ||
| 365 | { | ||
| 366 | struct apei_exec_context ctx; | ||
| 367 | int rc; | ||
| 368 | |||
| 369 | erst_exec_ctx_init(&ctx); | ||
| 370 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE); | ||
| 371 | if (rc) | ||
| 372 | return rc; | ||
| 373 | range->base = apei_exec_ctx_get_output(&ctx); | ||
| 374 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH); | ||
| 375 | if (rc) | ||
| 376 | return rc; | ||
| 377 | range->size = apei_exec_ctx_get_output(&ctx); | ||
| 378 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES); | ||
| 379 | if (rc) | ||
| 380 | return rc; | ||
| 381 | range->attr = apei_exec_ctx_get_output(&ctx); | ||
| 382 | |||
| 383 | return 0; | ||
| 384 | } | ||
| 385 | |||
| 386 | static ssize_t __erst_get_record_count(void) | ||
| 387 | { | ||
| 388 | struct apei_exec_context ctx; | ||
| 389 | int rc; | ||
| 390 | |||
| 391 | erst_exec_ctx_init(&ctx); | ||
| 392 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT); | ||
| 393 | if (rc) | ||
| 394 | return rc; | ||
| 395 | return apei_exec_ctx_get_output(&ctx); | ||
| 396 | } | ||
| 397 | |||
| 398 | ssize_t erst_get_record_count(void) | ||
| 399 | { | ||
| 400 | ssize_t count; | ||
| 401 | unsigned long flags; | ||
| 402 | |||
| 403 | if (erst_disable) | ||
| 404 | return -ENODEV; | ||
| 405 | |||
| 406 | spin_lock_irqsave(&erst_lock, flags); | ||
| 407 | count = __erst_get_record_count(); | ||
| 408 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 409 | |||
| 410 | return count; | ||
| 411 | } | ||
| 412 | EXPORT_SYMBOL_GPL(erst_get_record_count); | ||
| 413 | |||
| 414 | static int __erst_get_next_record_id(u64 *record_id) | ||
| 415 | { | ||
| 416 | struct apei_exec_context ctx; | ||
| 417 | int rc; | ||
| 418 | |||
| 419 | erst_exec_ctx_init(&ctx); | ||
| 420 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID); | ||
| 421 | if (rc) | ||
| 422 | return rc; | ||
| 423 | *record_id = apei_exec_ctx_get_output(&ctx); | ||
| 424 | |||
| 425 | return 0; | ||
| 426 | } | ||
| 427 | |||
| 428 | /* | ||
| 429 | * Get the record ID of an existing error record on the persistent | ||
| 430 | * storage. If there is no error record on the persistent storage, the | ||
| 431 | * returned record_id is APEI_ERST_INVALID_RECORD_ID. | ||
| 432 | */ | ||
| 433 | int erst_get_next_record_id(u64 *record_id) | ||
| 434 | { | ||
| 435 | int rc; | ||
| 436 | unsigned long flags; | ||
| 437 | |||
| 438 | if (erst_disable) | ||
| 439 | return -ENODEV; | ||
| 440 | |||
| 441 | spin_lock_irqsave(&erst_lock, flags); | ||
| 442 | rc = __erst_get_next_record_id(record_id); | ||
| 443 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 444 | |||
| 445 | return rc; | ||
| 446 | } | ||
| 447 | EXPORT_SYMBOL_GPL(erst_get_next_record_id); | ||
| 448 | |||
| 449 | static int __erst_write_to_storage(u64 offset) | ||
| 450 | { | ||
| 451 | struct apei_exec_context ctx; | ||
| 452 | u64 timeout = FIRMWARE_TIMEOUT; | ||
| 453 | u64 val; | ||
| 454 | int rc; | ||
| 455 | |||
| 456 | erst_exec_ctx_init(&ctx); | ||
| 457 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_WRITE); | ||
| 458 | if (rc) | ||
| 459 | return rc; | ||
| 460 | apei_exec_ctx_set_input(&ctx, offset); | ||
| 461 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | ||
| 462 | if (rc) | ||
| 463 | return rc; | ||
| 464 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
| 465 | if (rc) | ||
| 466 | return rc; | ||
| 467 | for (;;) { | ||
| 468 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
| 469 | if (rc) | ||
| 470 | return rc; | ||
| 471 | val = apei_exec_ctx_get_output(&ctx); | ||
| 472 | if (!val) | ||
| 473 | break; | ||
| 474 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
| 475 | return -EIO; | ||
| 476 | } | ||
| 477 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
| 478 | if (rc) | ||
| 479 | return rc; | ||
| 480 | val = apei_exec_ctx_get_output(&ctx); | ||
| 481 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
| 482 | if (rc) | ||
| 483 | return rc; | ||
| 484 | |||
| 485 | return erst_errno(val); | ||
| 486 | } | ||
| 487 | |||
| 488 | static int __erst_read_from_storage(u64 record_id, u64 offset) | ||
| 489 | { | ||
| 490 | struct apei_exec_context ctx; | ||
| 491 | u64 timeout = FIRMWARE_TIMEOUT; | ||
| 492 | u64 val; | ||
| 493 | int rc; | ||
| 494 | |||
| 495 | erst_exec_ctx_init(&ctx); | ||
| 496 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_READ); | ||
| 497 | if (rc) | ||
| 498 | return rc; | ||
| 499 | apei_exec_ctx_set_input(&ctx, offset); | ||
| 500 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | ||
| 501 | if (rc) | ||
| 502 | return rc; | ||
| 503 | apei_exec_ctx_set_input(&ctx, record_id); | ||
| 504 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | ||
| 505 | if (rc) | ||
| 506 | return rc; | ||
| 507 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
| 508 | if (rc) | ||
| 509 | return rc; | ||
| 510 | for (;;) { | ||
| 511 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
| 512 | if (rc) | ||
| 513 | return rc; | ||
| 514 | val = apei_exec_ctx_get_output(&ctx); | ||
| 515 | if (!val) | ||
| 516 | break; | ||
| 517 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
| 518 | return -EIO; | ||
| 519 | }; | ||
| 520 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
| 521 | if (rc) | ||
| 522 | return rc; | ||
| 523 | val = apei_exec_ctx_get_output(&ctx); | ||
| 524 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
| 525 | if (rc) | ||
| 526 | return rc; | ||
| 527 | |||
| 528 | return erst_errno(val); | ||
| 529 | } | ||
| 530 | |||
| 531 | static int __erst_clear_from_storage(u64 record_id) | ||
| 532 | { | ||
| 533 | struct apei_exec_context ctx; | ||
| 534 | u64 timeout = FIRMWARE_TIMEOUT; | ||
| 535 | u64 val; | ||
| 536 | int rc; | ||
| 537 | |||
| 538 | erst_exec_ctx_init(&ctx); | ||
| 539 | rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_CLEAR); | ||
| 540 | if (rc) | ||
| 541 | return rc; | ||
| 542 | apei_exec_ctx_set_input(&ctx, record_id); | ||
| 543 | rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | ||
| 544 | if (rc) | ||
| 545 | return rc; | ||
| 546 | rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | ||
| 547 | if (rc) | ||
| 548 | return rc; | ||
| 549 | for (;;) { | ||
| 550 | rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | ||
| 551 | if (rc) | ||
| 552 | return rc; | ||
| 553 | val = apei_exec_ctx_get_output(&ctx); | ||
| 554 | if (!val) | ||
| 555 | break; | ||
| 556 | if (erst_timedout(&timeout, SPIN_UNIT)) | ||
| 557 | return -EIO; | ||
| 558 | } | ||
| 559 | rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | ||
| 560 | if (rc) | ||
| 561 | return rc; | ||
| 562 | val = apei_exec_ctx_get_output(&ctx); | ||
| 563 | rc = apei_exec_run(&ctx, ACPI_ERST_END); | ||
| 564 | if (rc) | ||
| 565 | return rc; | ||
| 566 | |||
| 567 | return erst_errno(val); | ||
| 568 | } | ||
| 569 | |||
| 570 | /* NVRAM ERST Error Log Address Range is not supported yet */ | ||
| 571 | static void pr_unimpl_nvram(void) | ||
| 572 | { | ||
| 573 | if (printk_ratelimit()) | ||
| 574 | pr_warning(ERST_PFX | ||
| 575 | "NVRAM ERST Log Address Range is not implemented yet\n"); | ||
| 576 | } | ||
| 577 | |||
| 578 | static int __erst_write_to_nvram(const struct cper_record_header *record) | ||
| 579 | { | ||
| 580 | /* do not print message, because printk is not safe for NMI */ | ||
| 581 | return -ENOSYS; | ||
| 582 | } | ||
| 583 | |||
| 584 | static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset) | ||
| 585 | { | ||
| 586 | pr_unimpl_nvram(); | ||
| 587 | return -ENOSYS; | ||
| 588 | } | ||
| 589 | |||
| 590 | static int __erst_clear_from_nvram(u64 record_id) | ||
| 591 | { | ||
| 592 | pr_unimpl_nvram(); | ||
| 593 | return -ENOSYS; | ||
| 594 | } | ||
| 595 | |||
| 596 | int erst_write(const struct cper_record_header *record) | ||
| 597 | { | ||
| 598 | int rc; | ||
| 599 | unsigned long flags; | ||
| 600 | struct cper_record_header *rcd_erange; | ||
| 601 | |||
| 602 | if (erst_disable) | ||
| 603 | return -ENODEV; | ||
| 604 | |||
| 605 | if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE)) | ||
| 606 | return -EINVAL; | ||
| 607 | |||
| 608 | if (erst_erange.attr & ERST_RANGE_NVRAM) { | ||
| 609 | if (!spin_trylock_irqsave(&erst_lock, flags)) | ||
| 610 | return -EBUSY; | ||
| 611 | rc = __erst_write_to_nvram(record); | ||
| 612 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 613 | return rc; | ||
| 614 | } | ||
| 615 | |||
| 616 | if (record->record_length > erst_erange.size) | ||
| 617 | return -EINVAL; | ||
| 618 | |||
| 619 | if (!spin_trylock_irqsave(&erst_lock, flags)) | ||
| 620 | return -EBUSY; | ||
| 621 | memcpy(erst_erange.vaddr, record, record->record_length); | ||
| 622 | rcd_erange = erst_erange.vaddr; | ||
| 623 | /* signature for serialization system */ | ||
| 624 | memcpy(&rcd_erange->persistence_information, "ER", 2); | ||
| 625 | |||
| 626 | rc = __erst_write_to_storage(0); | ||
| 627 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 628 | |||
| 629 | return rc; | ||
| 630 | } | ||
| 631 | EXPORT_SYMBOL_GPL(erst_write); | ||
| 632 | |||
| 633 | static int __erst_read_to_erange(u64 record_id, u64 *offset) | ||
| 634 | { | ||
| 635 | int rc; | ||
| 636 | |||
| 637 | if (erst_erange.attr & ERST_RANGE_NVRAM) | ||
| 638 | return __erst_read_to_erange_from_nvram( | ||
| 639 | record_id, offset); | ||
| 640 | |||
| 641 | rc = __erst_read_from_storage(record_id, 0); | ||
| 642 | if (rc) | ||
| 643 | return rc; | ||
| 644 | *offset = 0; | ||
| 645 | |||
| 646 | return 0; | ||
| 647 | } | ||
| 648 | |||
| 649 | static ssize_t __erst_read(u64 record_id, struct cper_record_header *record, | ||
| 650 | size_t buflen) | ||
| 651 | { | ||
| 652 | int rc; | ||
| 653 | u64 offset, len = 0; | ||
| 654 | struct cper_record_header *rcd_tmp; | ||
| 655 | |||
| 656 | rc = __erst_read_to_erange(record_id, &offset); | ||
| 657 | if (rc) | ||
| 658 | return rc; | ||
| 659 | rcd_tmp = erst_erange.vaddr + offset; | ||
| 660 | len = rcd_tmp->record_length; | ||
| 661 | if (len <= buflen) | ||
| 662 | memcpy(record, rcd_tmp, len); | ||
| 663 | |||
| 664 | return len; | ||
| 665 | } | ||
| 666 | |||
| 667 | /* | ||
| 668 | * If return value > buflen, the buffer size is not big enough, | ||
| 669 | * else if return value < 0, something goes wrong, | ||
| 670 | * else everything is OK, and return value is record length | ||
| 671 | */ | ||
| 672 | ssize_t erst_read(u64 record_id, struct cper_record_header *record, | ||
| 673 | size_t buflen) | ||
| 674 | { | ||
| 675 | ssize_t len; | ||
| 676 | unsigned long flags; | ||
| 677 | |||
| 678 | if (erst_disable) | ||
| 679 | return -ENODEV; | ||
| 680 | |||
| 681 | spin_lock_irqsave(&erst_lock, flags); | ||
| 682 | len = __erst_read(record_id, record, buflen); | ||
| 683 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 684 | return len; | ||
| 685 | } | ||
| 686 | EXPORT_SYMBOL_GPL(erst_read); | ||
| 687 | |||
| 688 | /* | ||
| 689 | * If return value > buflen, the buffer size is not big enough, | ||
| 690 | * else if return value = 0, there is no more record to read, | ||
| 691 | * else if return value < 0, something goes wrong, | ||
| 692 | * else everything is OK, and return value is record length | ||
| 693 | */ | ||
| 694 | ssize_t erst_read_next(struct cper_record_header *record, size_t buflen) | ||
| 695 | { | ||
| 696 | int rc; | ||
| 697 | ssize_t len; | ||
| 698 | unsigned long flags; | ||
| 699 | u64 record_id; | ||
| 700 | |||
| 701 | if (erst_disable) | ||
| 702 | return -ENODEV; | ||
| 703 | |||
| 704 | spin_lock_irqsave(&erst_lock, flags); | ||
| 705 | rc = __erst_get_next_record_id(&record_id); | ||
| 706 | if (rc) { | ||
| 707 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 708 | return rc; | ||
| 709 | } | ||
| 710 | /* no more record */ | ||
| 711 | if (record_id == APEI_ERST_INVALID_RECORD_ID) { | ||
| 712 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 713 | return 0; | ||
| 714 | } | ||
| 715 | |||
| 716 | len = __erst_read(record_id, record, buflen); | ||
| 717 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 718 | |||
| 719 | return len; | ||
| 720 | } | ||
| 721 | EXPORT_SYMBOL_GPL(erst_read_next); | ||
| 722 | |||
| 723 | int erst_clear(u64 record_id) | ||
| 724 | { | ||
| 725 | int rc; | ||
| 726 | unsigned long flags; | ||
| 727 | |||
| 728 | if (erst_disable) | ||
| 729 | return -ENODEV; | ||
| 730 | |||
| 731 | spin_lock_irqsave(&erst_lock, flags); | ||
| 732 | if (erst_erange.attr & ERST_RANGE_NVRAM) | ||
| 733 | rc = __erst_clear_from_nvram(record_id); | ||
| 734 | else | ||
| 735 | rc = __erst_clear_from_storage(record_id); | ||
| 736 | spin_unlock_irqrestore(&erst_lock, flags); | ||
| 737 | |||
| 738 | return rc; | ||
| 739 | } | ||
| 740 | EXPORT_SYMBOL_GPL(erst_clear); | ||
| 741 | |||
| 742 | static int __init setup_erst_disable(char *str) | ||
| 743 | { | ||
| 744 | erst_disable = 1; | ||
| 745 | return 0; | ||
| 746 | } | ||
| 747 | |||
| 748 | __setup("erst_disable", setup_erst_disable); | ||
| 749 | |||
| 750 | static int erst_check_table(struct acpi_table_erst *erst_tab) | ||
| 751 | { | ||
| 752 | if (erst_tab->header_length != sizeof(struct acpi_table_erst)) | ||
| 753 | return -EINVAL; | ||
| 754 | if (erst_tab->header.length < sizeof(struct acpi_table_erst)) | ||
| 755 | return -EINVAL; | ||
| 756 | if (erst_tab->entries != | ||
| 757 | (erst_tab->header.length - sizeof(struct acpi_table_erst)) / | ||
| 758 | sizeof(struct acpi_erst_entry)) | ||
| 759 | return -EINVAL; | ||
| 760 | |||
| 761 | return 0; | ||
| 762 | } | ||
| 763 | |||
| 764 | static int __init erst_init(void) | ||
| 765 | { | ||
| 766 | int rc = 0; | ||
| 767 | acpi_status status; | ||
| 768 | struct apei_exec_context ctx; | ||
| 769 | struct apei_resources erst_resources; | ||
| 770 | struct resource *r; | ||
| 771 | |||
| 772 | if (acpi_disabled) | ||
| 773 | goto err; | ||
| 774 | |||
| 775 | if (erst_disable) { | ||
| 776 | pr_info(ERST_PFX | ||
| 777 | "Error Record Serialization Table (ERST) support is disabled.\n"); | ||
| 778 | goto err; | ||
| 779 | } | ||
| 780 | |||
| 781 | status = acpi_get_table(ACPI_SIG_ERST, 0, | ||
| 782 | (struct acpi_table_header **)&erst_tab); | ||
| 783 | if (status == AE_NOT_FOUND) { | ||
| 784 | pr_err(ERST_PFX "Table is not found!\n"); | ||
| 785 | goto err; | ||
| 786 | } else if (ACPI_FAILURE(status)) { | ||
| 787 | const char *msg = acpi_format_exception(status); | ||
| 788 | pr_err(ERST_PFX "Failed to get table, %s\n", msg); | ||
| 789 | rc = -EINVAL; | ||
| 790 | goto err; | ||
| 791 | } | ||
| 792 | |||
| 793 | rc = erst_check_table(erst_tab); | ||
| 794 | if (rc) { | ||
| 795 | pr_err(FW_BUG ERST_PFX "ERST table is invalid\n"); | ||
| 796 | goto err; | ||
| 797 | } | ||
| 798 | |||
| 799 | apei_resources_init(&erst_resources); | ||
| 800 | erst_exec_ctx_init(&ctx); | ||
| 801 | rc = apei_exec_collect_resources(&ctx, &erst_resources); | ||
| 802 | if (rc) | ||
| 803 | goto err_fini; | ||
| 804 | rc = apei_resources_request(&erst_resources, "APEI ERST"); | ||
| 805 | if (rc) | ||
| 806 | goto err_fini; | ||
| 807 | rc = apei_exec_pre_map_gars(&ctx); | ||
| 808 | if (rc) | ||
| 809 | goto err_release; | ||
| 810 | rc = erst_get_erange(&erst_erange); | ||
| 811 | if (rc) { | ||
| 812 | if (rc == -ENODEV) | ||
| 813 | pr_info(ERST_PFX | ||
| 814 | "The corresponding hardware device or firmware implementation " | ||
| 815 | "is not available.\n"); | ||
| 816 | else | ||
| 817 | pr_err(ERST_PFX | ||
| 818 | "Failed to get Error Log Address Range.\n"); | ||
| 819 | goto err_unmap_reg; | ||
| 820 | } | ||
| 821 | |||
| 822 | r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST"); | ||
| 823 | if (!r) { | ||
| 824 | pr_err(ERST_PFX | ||
| 825 | "Can not request iomem region <0x%16llx-0x%16llx> for ERST.\n", | ||
| 826 | (unsigned long long)erst_erange.base, | ||
| 827 | (unsigned long long)erst_erange.base + erst_erange.size); | ||
| 828 | rc = -EIO; | ||
| 829 | goto err_unmap_reg; | ||
| 830 | } | ||
| 831 | rc = -ENOMEM; | ||
| 832 | erst_erange.vaddr = ioremap_cache(erst_erange.base, | ||
| 833 | erst_erange.size); | ||
| 834 | if (!erst_erange.vaddr) | ||
| 835 | goto err_release_erange; | ||
| 836 | |||
| 837 | pr_info(ERST_PFX | ||
| 838 | "Error Record Serialization Table (ERST) support is initialized.\n"); | ||
| 839 | |||
| 840 | return 0; | ||
| 841 | |||
| 842 | err_release_erange: | ||
| 843 | release_mem_region(erst_erange.base, erst_erange.size); | ||
| 844 | err_unmap_reg: | ||
| 845 | apei_exec_post_unmap_gars(&ctx); | ||
| 846 | err_release: | ||
| 847 | apei_resources_release(&erst_resources); | ||
| 848 | err_fini: | ||
| 849 | apei_resources_fini(&erst_resources); | ||
| 850 | err: | ||
| 851 | erst_disable = 1; | ||
| 852 | return rc; | ||
| 853 | } | ||
| 854 | |||
| 855 | device_initcall(erst_init); | ||
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c new file mode 100644 index 000000000000..fd0cc016a099 --- /dev/null +++ b/drivers/acpi/apei/ghes.c | |||
| @@ -0,0 +1,427 @@ | |||
| 1 | /* | ||
| 2 | * APEI Generic Hardware Error Source support | ||
| 3 | * | ||
| 4 | * Generic Hardware Error Source provides a way to report platform | ||
| 5 | * hardware errors (such as that from chipset). It works in so called | ||
| 6 | * "Firmware First" mode, that is, hardware errors are reported to | ||
| 7 | * firmware firstly, then reported to Linux by firmware. This way, | ||
| 8 | * some non-standard hardware error registers or non-standard hardware | ||
| 9 | * link can be checked by firmware to produce more hardware error | ||
| 10 | * information for Linux. | ||
| 11 | * | ||
| 12 | * For more information about Generic Hardware Error Source, please | ||
| 13 | * refer to ACPI Specification version 4.0, section 17.3.2.6 | ||
| 14 | * | ||
| 15 | * Now, only SCI notification type and memory errors are | ||
| 16 | * supported. More notification type and hardware error type will be | ||
| 17 | * added later. | ||
| 18 | * | ||
| 19 | * Copyright 2010 Intel Corp. | ||
| 20 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 21 | * | ||
| 22 | * This program is free software; you can redistribute it and/or | ||
| 23 | * modify it under the terms of the GNU General Public License version | ||
| 24 | * 2 as published by the Free Software Foundation; | ||
| 25 | * | ||
| 26 | * This program is distributed in the hope that it will be useful, | ||
| 27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 29 | * GNU General Public License for more details. | ||
| 30 | * | ||
| 31 | * You should have received a copy of the GNU General Public License | ||
| 32 | * along with this program; if not, write to the Free Software | ||
| 33 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 34 | */ | ||
| 35 | |||
| 36 | #include <linux/kernel.h> | ||
| 37 | #include <linux/module.h> | ||
| 38 | #include <linux/init.h> | ||
| 39 | #include <linux/acpi.h> | ||
| 40 | #include <linux/io.h> | ||
| 41 | #include <linux/interrupt.h> | ||
| 42 | #include <linux/cper.h> | ||
| 43 | #include <linux/kdebug.h> | ||
| 44 | #include <acpi/apei.h> | ||
| 45 | #include <acpi/atomicio.h> | ||
| 46 | #include <acpi/hed.h> | ||
| 47 | #include <asm/mce.h> | ||
| 48 | |||
| 49 | #include "apei-internal.h" | ||
| 50 | |||
| 51 | #define GHES_PFX "GHES: " | ||
| 52 | |||
| 53 | #define GHES_ESTATUS_MAX_SIZE 65536 | ||
| 54 | |||
| 55 | /* | ||
| 56 | * One struct ghes is created for each generic hardware error | ||
| 57 | * source. | ||
| 58 | * | ||
| 59 | * It provides the context for APEI hardware error timer/IRQ/SCI/NMI | ||
| 60 | * handler. Handler for one generic hardware error source is only | ||
| 61 | * triggered after the previous one is done. So handler can uses | ||
| 62 | * struct ghes without locking. | ||
| 63 | * | ||
| 64 | * estatus: memory buffer for error status block, allocated during | ||
| 65 | * HEST parsing. | ||
| 66 | */ | ||
| 67 | #define GHES_TO_CLEAR 0x0001 | ||
| 68 | |||
| 69 | struct ghes { | ||
| 70 | struct acpi_hest_generic *generic; | ||
| 71 | struct acpi_hest_generic_status *estatus; | ||
| 72 | struct list_head list; | ||
| 73 | u64 buffer_paddr; | ||
| 74 | unsigned long flags; | ||
| 75 | }; | ||
| 76 | |||
| 77 | /* | ||
| 78 | * Error source lists, one list for each notification method. The | ||
| 79 | * members in lists are struct ghes. | ||
| 80 | * | ||
| 81 | * The list members are only added in HEST parsing and deleted during | ||
| 82 | * module_exit, that is, single-threaded. So no lock is needed for | ||
| 83 | * that. | ||
| 84 | * | ||
| 85 | * But the mutual exclusion is needed between members adding/deleting | ||
| 86 | * and timer/IRQ/SCI/NMI handler, which may traverse the list. RCU is | ||
| 87 | * used for that. | ||
| 88 | */ | ||
| 89 | static LIST_HEAD(ghes_sci); | ||
| 90 | |||
| 91 | static struct ghes *ghes_new(struct acpi_hest_generic *generic) | ||
| 92 | { | ||
| 93 | struct ghes *ghes; | ||
| 94 | unsigned int error_block_length; | ||
| 95 | int rc; | ||
| 96 | |||
| 97 | ghes = kzalloc(sizeof(*ghes), GFP_KERNEL); | ||
| 98 | if (!ghes) | ||
| 99 | return ERR_PTR(-ENOMEM); | ||
| 100 | ghes->generic = generic; | ||
| 101 | INIT_LIST_HEAD(&ghes->list); | ||
| 102 | rc = acpi_pre_map_gar(&generic->error_status_address); | ||
| 103 | if (rc) | ||
| 104 | goto err_free; | ||
| 105 | error_block_length = generic->error_block_length; | ||
| 106 | if (error_block_length > GHES_ESTATUS_MAX_SIZE) { | ||
| 107 | pr_warning(FW_WARN GHES_PFX | ||
| 108 | "Error status block length is too long: %u for " | ||
| 109 | "generic hardware error source: %d.\n", | ||
| 110 | error_block_length, generic->header.source_id); | ||
| 111 | error_block_length = GHES_ESTATUS_MAX_SIZE; | ||
| 112 | } | ||
| 113 | ghes->estatus = kmalloc(error_block_length, GFP_KERNEL); | ||
| 114 | if (!ghes->estatus) { | ||
| 115 | rc = -ENOMEM; | ||
| 116 | goto err_unmap; | ||
| 117 | } | ||
| 118 | |||
| 119 | return ghes; | ||
| 120 | |||
| 121 | err_unmap: | ||
| 122 | acpi_post_unmap_gar(&generic->error_status_address); | ||
| 123 | err_free: | ||
| 124 | kfree(ghes); | ||
| 125 | return ERR_PTR(rc); | ||
| 126 | } | ||
| 127 | |||
| 128 | static void ghes_fini(struct ghes *ghes) | ||
| 129 | { | ||
| 130 | kfree(ghes->estatus); | ||
| 131 | acpi_post_unmap_gar(&ghes->generic->error_status_address); | ||
| 132 | } | ||
| 133 | |||
| 134 | enum { | ||
| 135 | GHES_SER_NO = 0x0, | ||
| 136 | GHES_SER_CORRECTED = 0x1, | ||
| 137 | GHES_SER_RECOVERABLE = 0x2, | ||
| 138 | GHES_SER_PANIC = 0x3, | ||
| 139 | }; | ||
| 140 | |||
| 141 | static inline int ghes_severity(int severity) | ||
| 142 | { | ||
| 143 | switch (severity) { | ||
| 144 | case CPER_SER_INFORMATIONAL: | ||
| 145 | return GHES_SER_NO; | ||
| 146 | case CPER_SER_CORRECTED: | ||
| 147 | return GHES_SER_CORRECTED; | ||
| 148 | case CPER_SER_RECOVERABLE: | ||
| 149 | return GHES_SER_RECOVERABLE; | ||
| 150 | case CPER_SER_FATAL: | ||
| 151 | return GHES_SER_PANIC; | ||
| 152 | default: | ||
| 153 | /* Unkown, go panic */ | ||
| 154 | return GHES_SER_PANIC; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | /* SCI handler run in work queue, so ioremap can be used here */ | ||
| 159 | static int ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, | ||
| 160 | int from_phys) | ||
| 161 | { | ||
| 162 | void *vaddr; | ||
| 163 | |||
| 164 | vaddr = ioremap_cache(paddr, len); | ||
| 165 | if (!vaddr) | ||
| 166 | return -ENOMEM; | ||
| 167 | if (from_phys) | ||
| 168 | memcpy(buffer, vaddr, len); | ||
| 169 | else | ||
| 170 | memcpy(vaddr, buffer, len); | ||
| 171 | iounmap(vaddr); | ||
| 172 | |||
| 173 | return 0; | ||
| 174 | } | ||
| 175 | |||
| 176 | static int ghes_read_estatus(struct ghes *ghes, int silent) | ||
| 177 | { | ||
| 178 | struct acpi_hest_generic *g = ghes->generic; | ||
| 179 | u64 buf_paddr; | ||
| 180 | u32 len; | ||
| 181 | int rc; | ||
| 182 | |||
| 183 | rc = acpi_atomic_read(&buf_paddr, &g->error_status_address); | ||
| 184 | if (rc) { | ||
| 185 | if (!silent && printk_ratelimit()) | ||
| 186 | pr_warning(FW_WARN GHES_PFX | ||
| 187 | "Failed to read error status block address for hardware error source: %d.\n", | ||
| 188 | g->header.source_id); | ||
| 189 | return -EIO; | ||
| 190 | } | ||
| 191 | if (!buf_paddr) | ||
| 192 | return -ENOENT; | ||
| 193 | |||
| 194 | rc = ghes_copy_tofrom_phys(ghes->estatus, buf_paddr, | ||
| 195 | sizeof(*ghes->estatus), 1); | ||
| 196 | if (rc) | ||
| 197 | return rc; | ||
| 198 | if (!ghes->estatus->block_status) | ||
| 199 | return -ENOENT; | ||
| 200 | |||
| 201 | ghes->buffer_paddr = buf_paddr; | ||
| 202 | ghes->flags |= GHES_TO_CLEAR; | ||
| 203 | |||
| 204 | rc = -EIO; | ||
| 205 | len = apei_estatus_len(ghes->estatus); | ||
| 206 | if (len < sizeof(*ghes->estatus)) | ||
| 207 | goto err_read_block; | ||
| 208 | if (len > ghes->generic->error_block_length) | ||
| 209 | goto err_read_block; | ||
| 210 | if (apei_estatus_check_header(ghes->estatus)) | ||
| 211 | goto err_read_block; | ||
| 212 | rc = ghes_copy_tofrom_phys(ghes->estatus + 1, | ||
| 213 | buf_paddr + sizeof(*ghes->estatus), | ||
| 214 | len - sizeof(*ghes->estatus), 1); | ||
| 215 | if (rc) | ||
| 216 | return rc; | ||
| 217 | if (apei_estatus_check(ghes->estatus)) | ||
| 218 | goto err_read_block; | ||
| 219 | rc = 0; | ||
| 220 | |||
| 221 | err_read_block: | ||
| 222 | if (rc && !silent) | ||
| 223 | pr_warning(FW_WARN GHES_PFX | ||
| 224 | "Failed to read error status block!\n"); | ||
| 225 | return rc; | ||
| 226 | } | ||
| 227 | |||
| 228 | static void ghes_clear_estatus(struct ghes *ghes) | ||
| 229 | { | ||
| 230 | ghes->estatus->block_status = 0; | ||
| 231 | if (!(ghes->flags & GHES_TO_CLEAR)) | ||
| 232 | return; | ||
| 233 | ghes_copy_tofrom_phys(ghes->estatus, ghes->buffer_paddr, | ||
| 234 | sizeof(ghes->estatus->block_status), 0); | ||
| 235 | ghes->flags &= ~GHES_TO_CLEAR; | ||
| 236 | } | ||
| 237 | |||
| 238 | static void ghes_do_proc(struct ghes *ghes) | ||
| 239 | { | ||
| 240 | int ser, processed = 0; | ||
| 241 | struct acpi_hest_generic_data *gdata; | ||
| 242 | |||
| 243 | ser = ghes_severity(ghes->estatus->error_severity); | ||
| 244 | apei_estatus_for_each_section(ghes->estatus, gdata) { | ||
| 245 | #ifdef CONFIG_X86_MCE | ||
| 246 | if (!uuid_le_cmp(*(uuid_le *)gdata->section_type, | ||
| 247 | CPER_SEC_PLATFORM_MEM)) { | ||
| 248 | apei_mce_report_mem_error( | ||
| 249 | ser == GHES_SER_CORRECTED, | ||
| 250 | (struct cper_sec_mem_err *)(gdata+1)); | ||
| 251 | processed = 1; | ||
| 252 | } | ||
| 253 | #endif | ||
| 254 | } | ||
| 255 | |||
| 256 | if (!processed && printk_ratelimit()) | ||
| 257 | pr_warning(GHES_PFX | ||
| 258 | "Unknown error record from generic hardware error source: %d\n", | ||
| 259 | ghes->generic->header.source_id); | ||
| 260 | } | ||
| 261 | |||
| 262 | static int ghes_proc(struct ghes *ghes) | ||
| 263 | { | ||
| 264 | int rc; | ||
| 265 | |||
| 266 | rc = ghes_read_estatus(ghes, 0); | ||
| 267 | if (rc) | ||
| 268 | goto out; | ||
| 269 | ghes_do_proc(ghes); | ||
| 270 | |||
| 271 | out: | ||
| 272 | ghes_clear_estatus(ghes); | ||
| 273 | return 0; | ||
| 274 | } | ||
| 275 | |||
| 276 | static int ghes_notify_sci(struct notifier_block *this, | ||
| 277 | unsigned long event, void *data) | ||
| 278 | { | ||
| 279 | struct ghes *ghes; | ||
| 280 | int ret = NOTIFY_DONE; | ||
| 281 | |||
| 282 | rcu_read_lock(); | ||
| 283 | list_for_each_entry_rcu(ghes, &ghes_sci, list) { | ||
| 284 | if (!ghes_proc(ghes)) | ||
| 285 | ret = NOTIFY_OK; | ||
| 286 | } | ||
| 287 | rcu_read_unlock(); | ||
| 288 | |||
| 289 | return ret; | ||
| 290 | } | ||
| 291 | |||
| 292 | static struct notifier_block ghes_notifier_sci = { | ||
| 293 | .notifier_call = ghes_notify_sci, | ||
| 294 | }; | ||
| 295 | |||
| 296 | static int hest_ghes_parse(struct acpi_hest_header *hest_hdr, void *data) | ||
| 297 | { | ||
| 298 | struct acpi_hest_generic *generic; | ||
| 299 | struct ghes *ghes = NULL; | ||
| 300 | int rc = 0; | ||
| 301 | |||
| 302 | if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR) | ||
| 303 | return 0; | ||
| 304 | |||
| 305 | generic = (struct acpi_hest_generic *)hest_hdr; | ||
| 306 | if (!generic->enabled) | ||
| 307 | return 0; | ||
| 308 | |||
| 309 | if (generic->error_block_length < | ||
| 310 | sizeof(struct acpi_hest_generic_status)) { | ||
| 311 | pr_warning(FW_BUG GHES_PFX | ||
| 312 | "Invalid error block length: %u for generic hardware error source: %d\n", | ||
| 313 | generic->error_block_length, | ||
| 314 | generic->header.source_id); | ||
| 315 | goto err; | ||
| 316 | } | ||
| 317 | if (generic->records_to_preallocate == 0) { | ||
| 318 | pr_warning(FW_BUG GHES_PFX | ||
| 319 | "Invalid records to preallocate: %u for generic hardware error source: %d\n", | ||
| 320 | generic->records_to_preallocate, | ||
| 321 | generic->header.source_id); | ||
| 322 | goto err; | ||
| 323 | } | ||
| 324 | ghes = ghes_new(generic); | ||
| 325 | if (IS_ERR(ghes)) { | ||
| 326 | rc = PTR_ERR(ghes); | ||
| 327 | ghes = NULL; | ||
| 328 | goto err; | ||
| 329 | } | ||
| 330 | switch (generic->notify.type) { | ||
| 331 | case ACPI_HEST_NOTIFY_POLLED: | ||
| 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)) | ||
| 344 | register_acpi_hed_notifier(&ghes_notifier_sci); | ||
| 345 | list_add_rcu(&ghes->list, &ghes_sci); | ||
| 346 | break; | ||
| 347 | case ACPI_HEST_NOTIFY_NMI: | ||
| 348 | pr_warning(GHES_PFX | ||
| 349 | "Generic hardware error source: %d notified via NMI is not supported!\n", | ||
| 350 | generic->header.source_id); | ||
| 351 | break; | ||
| 352 | default: | ||
| 353 | pr_warning(FW_WARN GHES_PFX | ||
| 354 | "Unknown notification type: %u for generic hardware error source: %d\n", | ||
| 355 | generic->notify.type, generic->header.source_id); | ||
| 356 | break; | ||
| 357 | } | ||
| 358 | |||
| 359 | return 0; | ||
| 360 | err: | ||
| 361 | if (ghes) | ||
| 362 | ghes_fini(ghes); | ||
| 363 | return rc; | ||
| 364 | } | ||
| 365 | |||
| 366 | static void ghes_cleanup(void) | ||
| 367 | { | ||
| 368 | struct ghes *ghes, *nghes; | ||
| 369 | |||
| 370 | if (!list_empty(&ghes_sci)) | ||
| 371 | unregister_acpi_hed_notifier(&ghes_notifier_sci); | ||
| 372 | |||
| 373 | synchronize_rcu(); | ||
| 374 | |||
| 375 | list_for_each_entry_safe(ghes, nghes, &ghes_sci, list) { | ||
| 376 | list_del(&ghes->list); | ||
| 377 | ghes_fini(ghes); | ||
| 378 | kfree(ghes); | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | static int __init ghes_init(void) | ||
| 383 | { | ||
| 384 | int rc; | ||
| 385 | |||
| 386 | if (acpi_disabled) | ||
| 387 | return -ENODEV; | ||
| 388 | |||
| 389 | if (hest_disable) { | ||
| 390 | pr_info(GHES_PFX "HEST is not enabled!\n"); | ||
| 391 | return -EINVAL; | ||
| 392 | } | ||
| 393 | |||
| 394 | rc = apei_hest_parse(hest_ghes_parse, NULL); | ||
| 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 | } | ||
| 416 | |||
| 417 | static void __exit ghes_exit(void) | ||
| 418 | { | ||
| 419 | ghes_cleanup(); | ||
| 420 | } | ||
| 421 | |||
| 422 | module_init(ghes_init); | ||
| 423 | module_exit(ghes_exit); | ||
| 424 | |||
| 425 | MODULE_AUTHOR("Huang Ying"); | ||
| 426 | MODULE_DESCRIPTION("APEI Generic Hardware Error Source support"); | ||
| 427 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/acpi/apei/hest.c b/drivers/acpi/apei/hest.c new file mode 100644 index 000000000000..e7f40d362cb3 --- /dev/null +++ b/drivers/acpi/apei/hest.c | |||
| @@ -0,0 +1,173 @@ | |||
| 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 | * You should have received a copy of the GNU General Public License | ||
| 26 | * along with this program; if not, write to the Free Software | ||
| 27 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 28 | */ | ||
| 29 | |||
| 30 | #include <linux/kernel.h> | ||
| 31 | #include <linux/module.h> | ||
| 32 | #include <linux/init.h> | ||
| 33 | #include <linux/acpi.h> | ||
| 34 | #include <linux/kdebug.h> | ||
| 35 | #include <linux/highmem.h> | ||
| 36 | #include <linux/io.h> | ||
| 37 | #include <acpi/apei.h> | ||
| 38 | |||
| 39 | #include "apei-internal.h" | ||
| 40 | |||
| 41 | #define HEST_PFX "HEST: " | ||
| 42 | |||
| 43 | int hest_disable; | ||
| 44 | EXPORT_SYMBOL_GPL(hest_disable); | ||
| 45 | |||
| 46 | /* HEST table parsing */ | ||
| 47 | |||
| 48 | static struct acpi_table_hest *hest_tab; | ||
| 49 | |||
| 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] = { | ||
| 56 | [ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */ | ||
| 57 | [ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1, | ||
| 58 | [ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi), | ||
| 59 | [ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root), | ||
| 60 | [ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer), | ||
| 61 | [ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge), | ||
| 62 | [ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic), | ||
| 63 | }; | ||
| 64 | |||
| 65 | static int hest_esrc_len(struct acpi_hest_header *hest_hdr) | ||
| 66 | { | ||
| 67 | u16 hest_type = hest_hdr->type; | ||
| 68 | int len; | ||
| 69 | |||
| 70 | if (hest_type >= ACPI_HEST_TYPE_RESERVED) | ||
| 71 | return 0; | ||
| 72 | |||
| 73 | len = hest_esrc_len_tab[hest_type]; | ||
| 74 | |||
| 75 | if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) { | ||
| 76 | struct acpi_hest_ia_corrected *cmc; | ||
| 77 | cmc = (struct acpi_hest_ia_corrected *)hest_hdr; | ||
| 78 | len = sizeof(*cmc) + cmc->num_hardware_banks * | ||
| 79 | sizeof(struct acpi_hest_ia_error_bank); | ||
| 80 | } else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) { | ||
| 81 | struct acpi_hest_ia_machine_check *mc; | ||
| 82 | mc = (struct acpi_hest_ia_machine_check *)hest_hdr; | ||
| 83 | len = sizeof(*mc) + mc->num_hardware_banks * | ||
| 84 | sizeof(struct acpi_hest_ia_error_bank); | ||
| 85 | } | ||
| 86 | BUG_ON(len == -1); | ||
| 87 | |||
| 88 | return len; | ||
| 89 | }; | ||
| 90 | |||
| 91 | int apei_hest_parse(apei_hest_func_t func, void *data) | ||
| 92 | { | ||
| 93 | struct acpi_hest_header *hest_hdr; | ||
| 94 | int i, rc, len; | ||
| 95 | |||
| 96 | if (hest_disable) | ||
| 97 | return -EINVAL; | ||
| 98 | |||
| 99 | hest_hdr = (struct acpi_hest_header *)(hest_tab + 1); | ||
| 100 | for (i = 0; i < hest_tab->error_source_count; i++) { | ||
| 101 | len = hest_esrc_len(hest_hdr); | ||
| 102 | if (!len) { | ||
| 103 | pr_warning(FW_WARN HEST_PFX | ||
| 104 | "Unknown or unused hardware error source " | ||
| 105 | "type: %d for hardware error source: %d.\n", | ||
| 106 | hest_hdr->type, hest_hdr->source_id); | ||
| 107 | return -EINVAL; | ||
| 108 | } | ||
| 109 | if ((void *)hest_hdr + len > | ||
| 110 | (void *)hest_tab + hest_tab->header.length) { | ||
| 111 | pr_warning(FW_BUG HEST_PFX | ||
| 112 | "Table contents overflow for hardware error source: %d.\n", | ||
| 113 | hest_hdr->source_id); | ||
| 114 | return -EINVAL; | ||
| 115 | } | ||
| 116 | |||
| 117 | rc = func(hest_hdr, data); | ||
| 118 | if (rc) | ||
| 119 | return rc; | ||
| 120 | |||
| 121 | hest_hdr = (void *)hest_hdr + len; | ||
| 122 | } | ||
| 123 | |||
| 124 | return 0; | ||
| 125 | } | ||
| 126 | EXPORT_SYMBOL_GPL(apei_hest_parse); | ||
| 127 | |||
| 128 | static int __init setup_hest_disable(char *str) | ||
| 129 | { | ||
| 130 | hest_disable = 1; | ||
| 131 | return 0; | ||
| 132 | } | ||
| 133 | |||
| 134 | __setup("hest_disable", setup_hest_disable); | ||
| 135 | |||
| 136 | static int __init hest_init(void) | ||
| 137 | { | ||
| 138 | acpi_status status; | ||
| 139 | int rc = -ENODEV; | ||
| 140 | |||
| 141 | if (acpi_disabled) | ||
| 142 | goto err; | ||
| 143 | |||
| 144 | if (hest_disable) { | ||
| 145 | pr_info(HEST_PFX "HEST tabling parsing is disabled.\n"); | ||
| 146 | goto err; | ||
| 147 | } | ||
| 148 | |||
| 149 | status = acpi_get_table(ACPI_SIG_HEST, 0, | ||
| 150 | (struct acpi_table_header **)&hest_tab); | ||
| 151 | if (status == AE_NOT_FOUND) { | ||
| 152 | pr_info(HEST_PFX "Table is not found!\n"); | ||
| 153 | goto err; | ||
| 154 | } else if (ACPI_FAILURE(status)) { | ||
| 155 | const char *msg = acpi_format_exception(status); | ||
| 156 | pr_err(HEST_PFX "Failed to get table, %s\n", msg); | ||
| 157 | rc = -EINVAL; | ||
| 158 | goto err; | ||
| 159 | } | ||
| 160 | |||
| 161 | rc = apei_hest_parse(hest_void_parse, NULL); | ||
| 162 | if (rc) | ||
| 163 | goto err; | ||
| 164 | |||
| 165 | pr_info(HEST_PFX "HEST table parsing is initialized.\n"); | ||
| 166 | |||
| 167 | return 0; | ||
| 168 | err: | ||
| 169 | hest_disable = 1; | ||
| 170 | return rc; | ||
| 171 | } | ||
| 172 | |||
| 173 | subsys_initcall(hest_init); | ||
diff --git a/drivers/acpi/atomicio.c b/drivers/acpi/atomicio.c new file mode 100644 index 000000000000..814b19249616 --- /dev/null +++ b/drivers/acpi/atomicio.c | |||
| @@ -0,0 +1,360 @@ | |||
| 1 | /* | ||
| 2 | * atomicio.c - ACPI IO memory pre-mapping/post-unmapping, then | ||
| 3 | * accessing in atomic context. | ||
| 4 | * | ||
| 5 | * This is used for NMI handler to access IO memory area, because | ||
| 6 | * ioremap/iounmap can not be used in NMI handler. The IO memory area | ||
| 7 | * is pre-mapped in process context and accessed in NMI handler. | ||
| 8 | * | ||
| 9 | * Copyright (C) 2009-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/init.h> | ||
| 29 | #include <linux/acpi.h> | ||
| 30 | #include <linux/io.h> | ||
| 31 | #include <linux/kref.h> | ||
| 32 | #include <linux/rculist.h> | ||
| 33 | #include <linux/interrupt.h> | ||
| 34 | #include <acpi/atomicio.h> | ||
| 35 | |||
| 36 | #define ACPI_PFX "ACPI: " | ||
| 37 | |||
| 38 | static LIST_HEAD(acpi_iomaps); | ||
| 39 | /* | ||
| 40 | * Used for mutual exclusion between writers of acpi_iomaps list, for | ||
| 41 | * synchronization between readers and writer, RCU is used. | ||
| 42 | */ | ||
| 43 | static DEFINE_SPINLOCK(acpi_iomaps_lock); | ||
| 44 | |||
| 45 | struct acpi_iomap { | ||
| 46 | struct list_head list; | ||
| 47 | void __iomem *vaddr; | ||
| 48 | unsigned long size; | ||
| 49 | phys_addr_t paddr; | ||
| 50 | struct kref ref; | ||
| 51 | }; | ||
| 52 | |||
| 53 | /* acpi_iomaps_lock or RCU read lock must be held before calling */ | ||
| 54 | static struct acpi_iomap *__acpi_find_iomap(phys_addr_t paddr, | ||
| 55 | unsigned long size) | ||
| 56 | { | ||
| 57 | struct acpi_iomap *map; | ||
| 58 | |||
| 59 | list_for_each_entry_rcu(map, &acpi_iomaps, list) { | ||
| 60 | if (map->paddr + map->size >= paddr + size && | ||
| 61 | map->paddr <= paddr) | ||
| 62 | return map; | ||
| 63 | } | ||
| 64 | return NULL; | ||
| 65 | } | ||
| 66 | |||
| 67 | /* | ||
| 68 | * Atomic "ioremap" used by NMI handler, if the specified IO memory | ||
| 69 | * area is not pre-mapped, NULL will be returned. | ||
| 70 | * | ||
| 71 | * acpi_iomaps_lock or RCU read lock must be held before calling | ||
| 72 | */ | ||
| 73 | static void __iomem *__acpi_ioremap_fast(phys_addr_t paddr, | ||
| 74 | unsigned long size) | ||
| 75 | { | ||
| 76 | struct acpi_iomap *map; | ||
| 77 | |||
| 78 | map = __acpi_find_iomap(paddr, size); | ||
| 79 | if (map) | ||
| 80 | return map->vaddr + (paddr - map->paddr); | ||
| 81 | else | ||
| 82 | return NULL; | ||
| 83 | } | ||
| 84 | |||
| 85 | /* acpi_iomaps_lock must be held before calling */ | ||
| 86 | static void __iomem *__acpi_try_ioremap(phys_addr_t paddr, | ||
| 87 | unsigned long size) | ||
| 88 | { | ||
| 89 | struct acpi_iomap *map; | ||
| 90 | |||
| 91 | map = __acpi_find_iomap(paddr, size); | ||
| 92 | if (map) { | ||
| 93 | kref_get(&map->ref); | ||
| 94 | return map->vaddr + (paddr - map->paddr); | ||
| 95 | } else | ||
| 96 | return NULL; | ||
| 97 | } | ||
| 98 | |||
| 99 | /* | ||
| 100 | * Used to pre-map the specified IO memory area. First try to find | ||
| 101 | * whether the area is already pre-mapped, if it is, increase the | ||
| 102 | * reference count (in __acpi_try_ioremap) and return; otherwise, do | ||
| 103 | * the real ioremap, and add the mapping into acpi_iomaps list. | ||
| 104 | */ | ||
| 105 | static void __iomem *acpi_pre_map(phys_addr_t paddr, | ||
| 106 | unsigned long size) | ||
| 107 | { | ||
| 108 | void __iomem *vaddr; | ||
| 109 | struct acpi_iomap *map; | ||
| 110 | unsigned long pg_sz, flags; | ||
| 111 | phys_addr_t pg_off; | ||
| 112 | |||
| 113 | spin_lock_irqsave(&acpi_iomaps_lock, flags); | ||
| 114 | vaddr = __acpi_try_ioremap(paddr, size); | ||
| 115 | spin_unlock_irqrestore(&acpi_iomaps_lock, flags); | ||
| 116 | if (vaddr) | ||
| 117 | return vaddr; | ||
| 118 | |||
| 119 | pg_off = paddr & PAGE_MASK; | ||
| 120 | pg_sz = ((paddr + size + PAGE_SIZE - 1) & PAGE_MASK) - pg_off; | ||
| 121 | vaddr = ioremap(pg_off, pg_sz); | ||
| 122 | if (!vaddr) | ||
| 123 | return NULL; | ||
| 124 | map = kmalloc(sizeof(*map), GFP_KERNEL); | ||
| 125 | if (!map) | ||
| 126 | goto err_unmap; | ||
| 127 | INIT_LIST_HEAD(&map->list); | ||
| 128 | map->paddr = pg_off; | ||
| 129 | map->size = pg_sz; | ||
| 130 | map->vaddr = vaddr; | ||
| 131 | kref_init(&map->ref); | ||
| 132 | |||
| 133 | spin_lock_irqsave(&acpi_iomaps_lock, flags); | ||
| 134 | vaddr = __acpi_try_ioremap(paddr, size); | ||
| 135 | if (vaddr) { | ||
| 136 | spin_unlock_irqrestore(&acpi_iomaps_lock, flags); | ||
| 137 | iounmap(map->vaddr); | ||
| 138 | kfree(map); | ||
| 139 | return vaddr; | ||
| 140 | } | ||
| 141 | list_add_tail_rcu(&map->list, &acpi_iomaps); | ||
| 142 | spin_unlock_irqrestore(&acpi_iomaps_lock, flags); | ||
| 143 | |||
| 144 | return vaddr + (paddr - pg_off); | ||
| 145 | err_unmap: | ||
| 146 | iounmap(vaddr); | ||
| 147 | return NULL; | ||
| 148 | } | ||
| 149 | |||
| 150 | /* acpi_iomaps_lock must be held before calling */ | ||
| 151 | static void __acpi_kref_del_iomap(struct kref *ref) | ||
| 152 | { | ||
| 153 | struct acpi_iomap *map; | ||
| 154 | |||
| 155 | map = container_of(ref, struct acpi_iomap, ref); | ||
| 156 | list_del_rcu(&map->list); | ||
| 157 | } | ||
| 158 | |||
| 159 | /* | ||
| 160 | * Used to post-unmap the specified IO memory area. The iounmap is | ||
| 161 | * done only if the reference count goes zero. | ||
| 162 | */ | ||
| 163 | static void acpi_post_unmap(phys_addr_t paddr, unsigned long size) | ||
| 164 | { | ||
| 165 | struct acpi_iomap *map; | ||
| 166 | unsigned long flags; | ||
| 167 | int del; | ||
| 168 | |||
| 169 | spin_lock_irqsave(&acpi_iomaps_lock, flags); | ||
| 170 | map = __acpi_find_iomap(paddr, size); | ||
| 171 | BUG_ON(!map); | ||
| 172 | del = kref_put(&map->ref, __acpi_kref_del_iomap); | ||
| 173 | spin_unlock_irqrestore(&acpi_iomaps_lock, flags); | ||
| 174 | |||
| 175 | if (!del) | ||
| 176 | return; | ||
| 177 | |||
| 178 | synchronize_rcu(); | ||
| 179 | iounmap(map->vaddr); | ||
| 180 | kfree(map); | ||
| 181 | } | ||
| 182 | |||
| 183 | /* In NMI handler, should set silent = 1 */ | ||
| 184 | static int acpi_check_gar(struct acpi_generic_address *reg, | ||
| 185 | u64 *paddr, int silent) | ||
| 186 | { | ||
| 187 | u32 width, space_id; | ||
| 188 | |||
| 189 | width = reg->bit_width; | ||
| 190 | space_id = reg->space_id; | ||
| 191 | /* Handle possible alignment issues */ | ||
| 192 | memcpy(paddr, ®->address, sizeof(*paddr)); | ||
| 193 | if (!*paddr) { | ||
| 194 | if (!silent) | ||
| 195 | pr_warning(FW_BUG ACPI_PFX | ||
| 196 | "Invalid physical address in GAR [0x%llx/%u/%u]\n", | ||
| 197 | *paddr, width, space_id); | ||
| 198 | return -EINVAL; | ||
| 199 | } | ||
| 200 | |||
| 201 | if ((width != 8) && (width != 16) && (width != 32) && (width != 64)) { | ||
| 202 | if (!silent) | ||
| 203 | pr_warning(FW_BUG ACPI_PFX | ||
| 204 | "Invalid bit width in GAR [0x%llx/%u/%u]\n", | ||
| 205 | *paddr, width, space_id); | ||
| 206 | return -EINVAL; | ||
| 207 | } | ||
| 208 | |||
| 209 | if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY && | ||
| 210 | space_id != ACPI_ADR_SPACE_SYSTEM_IO) { | ||
| 211 | if (!silent) | ||
| 212 | pr_warning(FW_BUG ACPI_PFX | ||
| 213 | "Invalid address space type in GAR [0x%llx/%u/%u]\n", | ||
| 214 | *paddr, width, space_id); | ||
| 215 | return -EINVAL; | ||
| 216 | } | ||
| 217 | |||
| 218 | return 0; | ||
| 219 | } | ||
| 220 | |||
| 221 | /* Pre-map, working on GAR */ | ||
| 222 | int acpi_pre_map_gar(struct acpi_generic_address *reg) | ||
| 223 | { | ||
| 224 | u64 paddr; | ||
| 225 | void __iomem *vaddr; | ||
| 226 | int rc; | ||
| 227 | |||
| 228 | if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) | ||
| 229 | return 0; | ||
| 230 | |||
| 231 | rc = acpi_check_gar(reg, &paddr, 0); | ||
| 232 | if (rc) | ||
| 233 | return rc; | ||
| 234 | |||
| 235 | vaddr = acpi_pre_map(paddr, reg->bit_width / 8); | ||
| 236 | if (!vaddr) | ||
| 237 | return -EIO; | ||
| 238 | |||
| 239 | return 0; | ||
| 240 | } | ||
| 241 | EXPORT_SYMBOL_GPL(acpi_pre_map_gar); | ||
| 242 | |||
| 243 | /* Post-unmap, working on GAR */ | ||
| 244 | int acpi_post_unmap_gar(struct acpi_generic_address *reg) | ||
| 245 | { | ||
| 246 | u64 paddr; | ||
| 247 | int rc; | ||
| 248 | |||
| 249 | if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) | ||
| 250 | return 0; | ||
| 251 | |||
| 252 | rc = acpi_check_gar(reg, &paddr, 0); | ||
| 253 | if (rc) | ||
| 254 | return rc; | ||
| 255 | |||
| 256 | acpi_post_unmap(paddr, reg->bit_width / 8); | ||
| 257 | |||
| 258 | return 0; | ||
| 259 | } | ||
| 260 | EXPORT_SYMBOL_GPL(acpi_post_unmap_gar); | ||
| 261 | |||
| 262 | /* | ||
| 263 | * Can be used in atomic (including NMI) or process context. RCU read | ||
| 264 | * lock can only be released after the IO memory area accessing. | ||
| 265 | */ | ||
| 266 | static int acpi_atomic_read_mem(u64 paddr, u64 *val, u32 width) | ||
| 267 | { | ||
| 268 | void __iomem *addr; | ||
| 269 | |||
| 270 | rcu_read_lock(); | ||
| 271 | addr = __acpi_ioremap_fast(paddr, width); | ||
| 272 | switch (width) { | ||
| 273 | case 8: | ||
| 274 | *val = readb(addr); | ||
| 275 | break; | ||
| 276 | case 16: | ||
| 277 | *val = readw(addr); | ||
| 278 | break; | ||
| 279 | case 32: | ||
| 280 | *val = readl(addr); | ||
| 281 | break; | ||
| 282 | case 64: | ||
| 283 | *val = readq(addr); | ||
| 284 | break; | ||
| 285 | default: | ||
| 286 | return -EINVAL; | ||
| 287 | } | ||
| 288 | rcu_read_unlock(); | ||
| 289 | |||
| 290 | return 0; | ||
| 291 | } | ||
| 292 | |||
| 293 | static int acpi_atomic_write_mem(u64 paddr, u64 val, u32 width) | ||
| 294 | { | ||
| 295 | void __iomem *addr; | ||
| 296 | |||
| 297 | rcu_read_lock(); | ||
| 298 | addr = __acpi_ioremap_fast(paddr, width); | ||
| 299 | switch (width) { | ||
| 300 | case 8: | ||
| 301 | writeb(val, addr); | ||
| 302 | break; | ||
| 303 | case 16: | ||
| 304 | writew(val, addr); | ||
| 305 | break; | ||
| 306 | case 32: | ||
| 307 | writel(val, addr); | ||
| 308 | break; | ||
| 309 | case 64: | ||
| 310 | writeq(val, addr); | ||
| 311 | break; | ||
| 312 | default: | ||
| 313 | return -EINVAL; | ||
| 314 | } | ||
| 315 | rcu_read_unlock(); | ||
| 316 | |||
| 317 | return 0; | ||
| 318 | } | ||
| 319 | |||
| 320 | /* GAR accessing in atomic (including NMI) or process context */ | ||
| 321 | int acpi_atomic_read(u64 *val, struct acpi_generic_address *reg) | ||
| 322 | { | ||
| 323 | u64 paddr; | ||
| 324 | int rc; | ||
| 325 | |||
| 326 | rc = acpi_check_gar(reg, &paddr, 1); | ||
| 327 | if (rc) | ||
| 328 | return rc; | ||
| 329 | |||
| 330 | *val = 0; | ||
| 331 | switch (reg->space_id) { | ||
| 332 | case ACPI_ADR_SPACE_SYSTEM_MEMORY: | ||
| 333 | return acpi_atomic_read_mem(paddr, val, reg->bit_width); | ||
| 334 | case ACPI_ADR_SPACE_SYSTEM_IO: | ||
| 335 | return acpi_os_read_port(paddr, (u32 *)val, reg->bit_width); | ||
| 336 | default: | ||
| 337 | return -EINVAL; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | EXPORT_SYMBOL_GPL(acpi_atomic_read); | ||
| 341 | |||
| 342 | int acpi_atomic_write(u64 val, struct acpi_generic_address *reg) | ||
| 343 | { | ||
| 344 | u64 paddr; | ||
| 345 | int rc; | ||
| 346 | |||
| 347 | rc = acpi_check_gar(reg, &paddr, 1); | ||
| 348 | if (rc) | ||
| 349 | return rc; | ||
| 350 | |||
| 351 | switch (reg->space_id) { | ||
| 352 | case ACPI_ADR_SPACE_SYSTEM_MEMORY: | ||
| 353 | return acpi_atomic_write_mem(paddr, val, reg->bit_width); | ||
| 354 | case ACPI_ADR_SPACE_SYSTEM_IO: | ||
| 355 | return acpi_os_write_port(paddr, val, reg->bit_width); | ||
| 356 | default: | ||
| 357 | return -EINVAL; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | EXPORT_SYMBOL_GPL(acpi_atomic_write); | ||
diff --git a/drivers/acpi/hed.c b/drivers/acpi/hed.c new file mode 100644 index 000000000000..d0c1967f7597 --- /dev/null +++ b/drivers/acpi/hed.c | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | /* | ||
| 2 | * ACPI Hardware Error Device (PNP0C33) Driver | ||
| 3 | * | ||
| 4 | * Copyright (C) 2010, Intel Corp. | ||
| 5 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * ACPI Hardware Error Device is used to report some hardware errors | ||
| 8 | * notified via SCI, mainly the corrected errors. | ||
| 9 | * | ||
| 10 | * This program is free software; you can redistribute it and/or | ||
| 11 | * modify it under the terms of the GNU General Public License version | ||
| 12 | * 2 as published by the Free Software Foundation; | ||
| 13 | * | ||
| 14 | * This program is distributed in the hope that it will be useful, | ||
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | * GNU General Public License for more details. | ||
| 18 | * | ||
| 19 | * You should have received a copy of the GNU General Public License | ||
| 20 | * along with this program; if not, write to the Free Software | ||
| 21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 22 | */ | ||
| 23 | |||
| 24 | #include <linux/kernel.h> | ||
| 25 | #include <linux/module.h> | ||
| 26 | #include <linux/init.h> | ||
| 27 | #include <linux/acpi.h> | ||
| 28 | #include <acpi/acpi_bus.h> | ||
| 29 | #include <acpi/acpi_drivers.h> | ||
| 30 | #include <acpi/hed.h> | ||
| 31 | |||
| 32 | static struct acpi_device_id acpi_hed_ids[] = { | ||
| 33 | {"PNP0C33", 0}, | ||
| 34 | {"", 0}, | ||
| 35 | }; | ||
| 36 | MODULE_DEVICE_TABLE(acpi, acpi_hed_ids); | ||
| 37 | |||
| 38 | static acpi_handle hed_handle; | ||
| 39 | |||
| 40 | static BLOCKING_NOTIFIER_HEAD(acpi_hed_notify_list); | ||
| 41 | |||
| 42 | int register_acpi_hed_notifier(struct notifier_block *nb) | ||
| 43 | { | ||
| 44 | return blocking_notifier_chain_register(&acpi_hed_notify_list, nb); | ||
| 45 | } | ||
| 46 | EXPORT_SYMBOL_GPL(register_acpi_hed_notifier); | ||
| 47 | |||
| 48 | void unregister_acpi_hed_notifier(struct notifier_block *nb) | ||
| 49 | { | ||
| 50 | blocking_notifier_chain_unregister(&acpi_hed_notify_list, nb); | ||
| 51 | } | ||
| 52 | EXPORT_SYMBOL_GPL(unregister_acpi_hed_notifier); | ||
| 53 | |||
| 54 | /* | ||
| 55 | * SCI to report hardware error is forwarded to the listeners of HED, | ||
| 56 | * it is used by HEST Generic Hardware Error Source with notify type | ||
| 57 | * SCI. | ||
| 58 | */ | ||
| 59 | static void acpi_hed_notify(struct acpi_device *device, u32 event) | ||
| 60 | { | ||
| 61 | blocking_notifier_call_chain(&acpi_hed_notify_list, 0, NULL); | ||
| 62 | } | ||
| 63 | |||
| 64 | static int __devinit acpi_hed_add(struct acpi_device *device) | ||
| 65 | { | ||
| 66 | /* Only one hardware error device */ | ||
| 67 | if (hed_handle) | ||
| 68 | return -EINVAL; | ||
| 69 | hed_handle = device->handle; | ||
| 70 | return 0; | ||
| 71 | } | ||
| 72 | |||
| 73 | static int __devexit acpi_hed_remove(struct acpi_device *device, int type) | ||
| 74 | { | ||
| 75 | hed_handle = NULL; | ||
| 76 | return 0; | ||
| 77 | } | ||
| 78 | |||
| 79 | static struct acpi_driver acpi_hed_driver = { | ||
| 80 | .name = "hardware_error_device", | ||
| 81 | .class = "hardware_error", | ||
| 82 | .ids = acpi_hed_ids, | ||
| 83 | .ops = { | ||
| 84 | .add = acpi_hed_add, | ||
| 85 | .remove = acpi_hed_remove, | ||
| 86 | .notify = acpi_hed_notify, | ||
| 87 | }, | ||
| 88 | }; | ||
| 89 | |||
| 90 | static int __init acpi_hed_init(void) | ||
| 91 | { | ||
| 92 | if (acpi_disabled) | ||
| 93 | return -ENODEV; | ||
| 94 | |||
| 95 | if (acpi_bus_register_driver(&acpi_hed_driver) < 0) | ||
| 96 | return -ENODEV; | ||
| 97 | |||
| 98 | return 0; | ||
| 99 | } | ||
| 100 | |||
| 101 | static void __exit acpi_hed_exit(void) | ||
| 102 | { | ||
| 103 | acpi_bus_unregister_driver(&acpi_hed_driver); | ||
| 104 | } | ||
| 105 | |||
| 106 | module_init(acpi_hed_init); | ||
| 107 | module_exit(acpi_hed_exit); | ||
| 108 | |||
| 109 | ACPI_MODULE_NAME("hed"); | ||
| 110 | MODULE_AUTHOR("Huang Ying"); | ||
| 111 | MODULE_DESCRIPTION("ACPI Hardware Error Device Driver"); | ||
| 112 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/acpi/hest.c b/drivers/acpi/hest.c deleted file mode 100644 index 1c527a192872..000000000000 --- a/drivers/acpi/hest.c +++ /dev/null | |||
| @@ -1,139 +0,0 @@ | |||
| 1 | #include <linux/acpi.h> | ||
| 2 | #include <linux/pci.h> | ||
| 3 | |||
| 4 | #define PREFIX "ACPI: " | ||
| 5 | |||
| 6 | static inline unsigned long parse_acpi_hest_ia_machine_check(struct acpi_hest_ia_machine_check *p) | ||
| 7 | { | ||
| 8 | return sizeof(*p) + | ||
| 9 | (sizeof(struct acpi_hest_ia_error_bank) * p->num_hardware_banks); | ||
| 10 | } | ||
| 11 | |||
| 12 | static inline unsigned long parse_acpi_hest_ia_corrected(struct acpi_hest_ia_corrected *p) | ||
| 13 | { | ||
| 14 | return sizeof(*p) + | ||
| 15 | (sizeof(struct acpi_hest_ia_error_bank) * p->num_hardware_banks); | ||
| 16 | } | ||
| 17 | |||
| 18 | static inline unsigned long parse_acpi_hest_ia_nmi(struct acpi_hest_ia_nmi *p) | ||
| 19 | { | ||
| 20 | return sizeof(*p); | ||
| 21 | } | ||
| 22 | |||
| 23 | static inline unsigned long parse_acpi_hest_generic(struct acpi_hest_generic *p) | ||
| 24 | { | ||
| 25 | return sizeof(*p); | ||
| 26 | } | ||
| 27 | |||
| 28 | static inline unsigned int hest_match_pci(struct acpi_hest_aer_common *p, struct pci_dev *pci) | ||
| 29 | { | ||
| 30 | return (0 == pci_domain_nr(pci->bus) && | ||
| 31 | p->bus == pci->bus->number && | ||
| 32 | p->device == PCI_SLOT(pci->devfn) && | ||
| 33 | p->function == PCI_FUNC(pci->devfn)); | ||
| 34 | } | ||
| 35 | |||
| 36 | static unsigned long parse_acpi_hest_aer(void *hdr, int type, struct pci_dev *pci, int *firmware_first) | ||
| 37 | { | ||
| 38 | struct acpi_hest_aer_common *p = hdr + sizeof(struct acpi_hest_header); | ||
| 39 | unsigned long rc=0; | ||
| 40 | u8 pcie_type = 0; | ||
| 41 | u8 bridge = 0; | ||
| 42 | switch (type) { | ||
| 43 | case ACPI_HEST_TYPE_AER_ROOT_PORT: | ||
| 44 | rc = sizeof(struct acpi_hest_aer_root); | ||
| 45 | pcie_type = PCI_EXP_TYPE_ROOT_PORT; | ||
| 46 | break; | ||
| 47 | case ACPI_HEST_TYPE_AER_ENDPOINT: | ||
| 48 | rc = sizeof(struct acpi_hest_aer); | ||
| 49 | pcie_type = PCI_EXP_TYPE_ENDPOINT; | ||
| 50 | break; | ||
| 51 | case ACPI_HEST_TYPE_AER_BRIDGE: | ||
| 52 | rc = sizeof(struct acpi_hest_aer_bridge); | ||
| 53 | if ((pci->class >> 16) == PCI_BASE_CLASS_BRIDGE) | ||
| 54 | bridge = 1; | ||
| 55 | break; | ||
| 56 | } | ||
| 57 | |||
| 58 | if (p->flags & ACPI_HEST_GLOBAL) { | ||
| 59 | if ((pci->is_pcie && (pci->pcie_type == pcie_type)) || bridge) | ||
| 60 | *firmware_first = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); | ||
| 61 | } | ||
| 62 | else | ||
| 63 | if (hest_match_pci(p, pci)) | ||
| 64 | *firmware_first = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); | ||
| 65 | return rc; | ||
| 66 | } | ||
| 67 | |||
| 68 | static int acpi_hest_firmware_first(struct acpi_table_header *stdheader, struct pci_dev *pci) | ||
| 69 | { | ||
| 70 | struct acpi_table_hest *hest = (struct acpi_table_hest *)stdheader; | ||
| 71 | void *p = (void *)hest + sizeof(*hest); /* defined by the ACPI 4.0 spec */ | ||
| 72 | struct acpi_hest_header *hdr = p; | ||
| 73 | |||
| 74 | int i; | ||
| 75 | int firmware_first = 0; | ||
| 76 | static unsigned char printed_unused = 0; | ||
| 77 | static unsigned char printed_reserved = 0; | ||
| 78 | |||
| 79 | for (i=0, hdr=p; p < (((void *)hest) + hest->header.length) && i < hest->error_source_count; i++) { | ||
| 80 | switch (hdr->type) { | ||
| 81 | case ACPI_HEST_TYPE_IA32_CHECK: | ||
| 82 | p += parse_acpi_hest_ia_machine_check(p); | ||
| 83 | break; | ||
| 84 | case ACPI_HEST_TYPE_IA32_CORRECTED_CHECK: | ||
| 85 | p += parse_acpi_hest_ia_corrected(p); | ||
| 86 | break; | ||
| 87 | case ACPI_HEST_TYPE_IA32_NMI: | ||
| 88 | p += parse_acpi_hest_ia_nmi(p); | ||
| 89 | break; | ||
| 90 | /* These three should never appear */ | ||
| 91 | case ACPI_HEST_TYPE_NOT_USED3: | ||
| 92 | case ACPI_HEST_TYPE_NOT_USED4: | ||
| 93 | case ACPI_HEST_TYPE_NOT_USED5: | ||
| 94 | if (!printed_unused) { | ||
| 95 | printk(KERN_DEBUG PREFIX | ||
| 96 | "HEST Error Source list contains an obsolete type (%d).\n", hdr->type); | ||
| 97 | printed_unused = 1; | ||
| 98 | } | ||
| 99 | break; | ||
| 100 | case ACPI_HEST_TYPE_AER_ROOT_PORT: | ||
| 101 | case ACPI_HEST_TYPE_AER_ENDPOINT: | ||
| 102 | case ACPI_HEST_TYPE_AER_BRIDGE: | ||
| 103 | p += parse_acpi_hest_aer(p, hdr->type, pci, &firmware_first); | ||
| 104 | break; | ||
| 105 | case ACPI_HEST_TYPE_GENERIC_ERROR: | ||
| 106 | p += parse_acpi_hest_generic(p); | ||
| 107 | break; | ||
| 108 | /* These should never appear either */ | ||
| 109 | case ACPI_HEST_TYPE_RESERVED: | ||
| 110 | default: | ||
| 111 | if (!printed_reserved) { | ||
| 112 | printk(KERN_DEBUG PREFIX | ||
| 113 | "HEST Error Source list contains a reserved type (%d).\n", hdr->type); | ||
| 114 | printed_reserved = 1; | ||
| 115 | } | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | return firmware_first; | ||
| 120 | } | ||
| 121 | |||
| 122 | int acpi_hest_firmware_first_pci(struct pci_dev *pci) | ||
| 123 | { | ||
| 124 | acpi_status status = AE_NOT_FOUND; | ||
| 125 | struct acpi_table_header *hest = NULL; | ||
| 126 | |||
| 127 | if (acpi_disabled) | ||
| 128 | return 0; | ||
| 129 | |||
| 130 | status = acpi_get_table(ACPI_SIG_HEST, 1, &hest); | ||
| 131 | |||
| 132 | if (ACPI_SUCCESS(status)) { | ||
| 133 | if (acpi_hest_firmware_first(hest, pci)) { | ||
| 134 | return 1; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | return 0; | ||
| 138 | } | ||
| 139 | EXPORT_SYMBOL_GPL(acpi_hest_firmware_first_pci); | ||
diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index aefce33f2a09..4eac59393edc 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c | |||
| @@ -120,7 +120,8 @@ acpi_handle acpi_get_pci_rootbridge_handle(unsigned int seg, unsigned int bus) | |||
| 120 | struct acpi_pci_root *root; | 120 | struct acpi_pci_root *root; |
| 121 | 121 | ||
| 122 | list_for_each_entry(root, &acpi_pci_roots, node) | 122 | list_for_each_entry(root, &acpi_pci_roots, node) |
| 123 | if ((root->segment == (u16) seg) && (root->bus_nr == (u16) bus)) | 123 | if ((root->segment == (u16) seg) && |
| 124 | (root->secondary.start == (u16) bus)) | ||
| 124 | return root->device->handle; | 125 | return root->device->handle; |
| 125 | return NULL; | 126 | return NULL; |
| 126 | } | 127 | } |
| @@ -154,7 +155,7 @@ EXPORT_SYMBOL_GPL(acpi_is_root_bridge); | |||
| 154 | static acpi_status | 155 | static acpi_status |
| 155 | get_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) | 156 | get_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) |
| 156 | { | 157 | { |
| 157 | int *busnr = data; | 158 | struct resource *res = data; |
| 158 | struct acpi_resource_address64 address; | 159 | struct acpi_resource_address64 address; |
| 159 | 160 | ||
| 160 | if (resource->type != ACPI_RESOURCE_TYPE_ADDRESS16 && | 161 | if (resource->type != ACPI_RESOURCE_TYPE_ADDRESS16 && |
| @@ -164,28 +165,27 @@ get_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) | |||
| 164 | 165 | ||
| 165 | acpi_resource_to_address64(resource, &address); | 166 | acpi_resource_to_address64(resource, &address); |
| 166 | if ((address.address_length > 0) && | 167 | if ((address.address_length > 0) && |
| 167 | (address.resource_type == ACPI_BUS_NUMBER_RANGE)) | 168 | (address.resource_type == ACPI_BUS_NUMBER_RANGE)) { |
| 168 | *busnr = address.minimum; | 169 | res->start = address.minimum; |
| 170 | res->end = address.minimum + address.address_length - 1; | ||
| 171 | } | ||
| 169 | 172 | ||
| 170 | return AE_OK; | 173 | return AE_OK; |
| 171 | } | 174 | } |
| 172 | 175 | ||
| 173 | static acpi_status try_get_root_bridge_busnr(acpi_handle handle, | 176 | static acpi_status try_get_root_bridge_busnr(acpi_handle handle, |
| 174 | unsigned long long *bus) | 177 | struct resource *res) |
| 175 | { | 178 | { |
| 176 | acpi_status status; | 179 | acpi_status status; |
| 177 | int busnum; | ||
| 178 | 180 | ||
| 179 | busnum = -1; | 181 | res->start = -1; |
| 180 | status = | 182 | status = |
| 181 | acpi_walk_resources(handle, METHOD_NAME__CRS, | 183 | acpi_walk_resources(handle, METHOD_NAME__CRS, |
| 182 | get_root_bridge_busnr_callback, &busnum); | 184 | get_root_bridge_busnr_callback, res); |
| 183 | if (ACPI_FAILURE(status)) | 185 | if (ACPI_FAILURE(status)) |
| 184 | return status; | 186 | return status; |
| 185 | /* Check if we really get a bus number from _CRS */ | 187 | if (res->start == -1) |
| 186 | if (busnum == -1) | ||
| 187 | return AE_ERROR; | 188 | return AE_ERROR; |
| 188 | *bus = busnum; | ||
| 189 | return AE_OK; | 189 | return AE_OK; |
| 190 | } | 190 | } |
| 191 | 191 | ||
| @@ -429,34 +429,47 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) | |||
| 429 | struct acpi_device *child; | 429 | struct acpi_device *child; |
| 430 | u32 flags, base_flags; | 430 | u32 flags, base_flags; |
| 431 | 431 | ||
| 432 | root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); | ||
| 433 | if (!root) | ||
| 434 | return -ENOMEM; | ||
| 435 | |||
| 432 | segment = 0; | 436 | segment = 0; |
| 433 | status = acpi_evaluate_integer(device->handle, METHOD_NAME__SEG, NULL, | 437 | status = acpi_evaluate_integer(device->handle, METHOD_NAME__SEG, NULL, |
| 434 | &segment); | 438 | &segment); |
| 435 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { | 439 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { |
| 436 | printk(KERN_ERR PREFIX "can't evaluate _SEG\n"); | 440 | printk(KERN_ERR PREFIX "can't evaluate _SEG\n"); |
| 437 | return -ENODEV; | 441 | result = -ENODEV; |
| 442 | goto end; | ||
| 438 | } | 443 | } |
| 439 | 444 | ||
| 440 | /* Check _CRS first, then _BBN. If no _BBN, default to zero. */ | 445 | /* Check _CRS first, then _BBN. If no _BBN, default to zero. */ |
| 441 | bus = 0; | 446 | root->secondary.flags = IORESOURCE_BUS; |
| 442 | status = try_get_root_bridge_busnr(device->handle, &bus); | 447 | status = try_get_root_bridge_busnr(device->handle, &root->secondary); |
| 443 | if (ACPI_FAILURE(status)) { | 448 | if (ACPI_FAILURE(status)) { |
| 449 | /* | ||
| 450 | * We need both the start and end of the downstream bus range | ||
| 451 | * to interpret _CBA (MMCONFIG base address), so it really is | ||
| 452 | * supposed to be in _CRS. If we don't find it there, all we | ||
| 453 | * can do is assume [_BBN-0xFF] or [0-0xFF]. | ||
| 454 | */ | ||
| 455 | root->secondary.end = 0xFF; | ||
| 456 | printk(KERN_WARNING FW_BUG PREFIX | ||
| 457 | "no secondary bus range in _CRS\n"); | ||
| 444 | status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, &bus); | 458 | status = acpi_evaluate_integer(device->handle, METHOD_NAME__BBN, NULL, &bus); |
| 445 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { | 459 | if (ACPI_SUCCESS(status)) |
| 446 | printk(KERN_ERR PREFIX | 460 | root->secondary.start = bus; |
| 447 | "no bus number in _CRS and can't evaluate _BBN\n"); | 461 | else if (status == AE_NOT_FOUND) |
| 448 | return -ENODEV; | 462 | root->secondary.start = 0; |
| 463 | else { | ||
| 464 | printk(KERN_ERR PREFIX "can't evaluate _BBN\n"); | ||
| 465 | result = -ENODEV; | ||
| 466 | goto end; | ||
| 449 | } | 467 | } |
| 450 | } | 468 | } |
| 451 | 469 | ||
| 452 | root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); | ||
| 453 | if (!root) | ||
| 454 | return -ENOMEM; | ||
| 455 | |||
| 456 | INIT_LIST_HEAD(&root->node); | 470 | INIT_LIST_HEAD(&root->node); |
| 457 | root->device = device; | 471 | root->device = device; |
| 458 | root->segment = segment & 0xFFFF; | 472 | root->segment = segment & 0xFFFF; |
| 459 | root->bus_nr = bus & 0xFF; | ||
| 460 | strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); | 473 | strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); |
| 461 | strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); | 474 | strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); |
| 462 | device->driver_data = root; | 475 | device->driver_data = root; |
| @@ -475,9 +488,9 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) | |||
| 475 | /* TBD: Locking */ | 488 | /* TBD: Locking */ |
| 476 | list_add_tail(&root->node, &acpi_pci_roots); | 489 | list_add_tail(&root->node, &acpi_pci_roots); |
| 477 | 490 | ||
| 478 | printk(KERN_INFO PREFIX "%s [%s] (%04x:%02x)\n", | 491 | printk(KERN_INFO PREFIX "%s [%s] (domain %04x %pR)\n", |
| 479 | acpi_device_name(device), acpi_device_bid(device), | 492 | acpi_device_name(device), acpi_device_bid(device), |
| 480 | root->segment, root->bus_nr); | 493 | root->segment, &root->secondary); |
| 481 | 494 | ||
| 482 | /* | 495 | /* |
| 483 | * Scan the Root Bridge | 496 | * Scan the Root Bridge |
| @@ -486,11 +499,11 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) | |||
| 486 | * PCI namespace does not get created until this call is made (and | 499 | * PCI namespace does not get created until this call is made (and |
| 487 | * thus the root bridge's pci_dev does not exist). | 500 | * thus the root bridge's pci_dev does not exist). |
| 488 | */ | 501 | */ |
| 489 | root->bus = pci_acpi_scan_root(device, segment, bus); | 502 | root->bus = pci_acpi_scan_root(root); |
| 490 | if (!root->bus) { | 503 | if (!root->bus) { |
| 491 | printk(KERN_ERR PREFIX | 504 | printk(KERN_ERR PREFIX |
| 492 | "Bus %04x:%02x not present in PCI namespace\n", | 505 | "Bus %04x:%02x not present in PCI namespace\n", |
| 493 | root->segment, root->bus_nr); | 506 | root->segment, (unsigned int)root->secondary.start); |
| 494 | result = -ENODEV; | 507 | result = -ENODEV; |
| 495 | goto end; | 508 | goto end; |
| 496 | } | 509 | } |
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index baa76bbf244a..4ab2275b4461 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c | |||
| @@ -80,22 +80,6 @@ static int acpi_sleep_prepare(u32 acpi_state) | |||
| 80 | 80 | ||
| 81 | #ifdef CONFIG_ACPI_SLEEP | 81 | #ifdef CONFIG_ACPI_SLEEP |
| 82 | static u32 acpi_target_sleep_state = ACPI_STATE_S0; | 82 | static u32 acpi_target_sleep_state = ACPI_STATE_S0; |
| 83 | /* | ||
| 84 | * According to the ACPI specification the BIOS should make sure that ACPI is | ||
| 85 | * enabled and SCI_EN bit is set on wake-up from S1 - S3 sleep states. Still, | ||
| 86 | * some BIOSes don't do that and therefore we use acpi_enable() to enable ACPI | ||
| 87 | * on such systems during resume. Unfortunately that doesn't help in | ||
| 88 | * particularly pathological cases in which SCI_EN has to be set directly on | ||
| 89 | * resume, although the specification states very clearly that this flag is | ||
| 90 | * owned by the hardware. The set_sci_en_on_resume variable will be set in such | ||
| 91 | * cases. | ||
| 92 | */ | ||
| 93 | static bool set_sci_en_on_resume; | ||
| 94 | |||
| 95 | void __init acpi_set_sci_en_on_resume(void) | ||
| 96 | { | ||
| 97 | set_sci_en_on_resume = true; | ||
| 98 | } | ||
| 99 | 83 | ||
| 100 | /* | 84 | /* |
| 101 | * ACPI 1.0 wants us to execute _PTS before suspending devices, so we allow the | 85 | * ACPI 1.0 wants us to execute _PTS before suspending devices, so we allow the |
| @@ -253,11 +237,8 @@ static int acpi_suspend_enter(suspend_state_t pm_state) | |||
| 253 | break; | 237 | break; |
| 254 | } | 238 | } |
| 255 | 239 | ||
| 256 | /* If ACPI is not enabled by the BIOS, we need to enable it here. */ | 240 | /* This violates the spec but is required for bug compatibility. */ |
| 257 | if (set_sci_en_on_resume) | 241 | acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); |
| 258 | acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); | ||
| 259 | else | ||
| 260 | acpi_enable(); | ||
| 261 | 242 | ||
| 262 | /* Reprogram control registers and execute _BFS */ | 243 | /* Reprogram control registers and execute _BFS */ |
| 263 | acpi_leave_sleep_state_prep(acpi_state); | 244 | acpi_leave_sleep_state_prep(acpi_state); |
| @@ -346,12 +327,6 @@ static int __init init_old_suspend_ordering(const struct dmi_system_id *d) | |||
| 346 | return 0; | 327 | return 0; |
| 347 | } | 328 | } |
| 348 | 329 | ||
| 349 | static int __init init_set_sci_en_on_resume(const struct dmi_system_id *d) | ||
| 350 | { | ||
| 351 | set_sci_en_on_resume = true; | ||
| 352 | return 0; | ||
| 353 | } | ||
| 354 | |||
| 355 | static struct dmi_system_id __initdata acpisleep_dmi_table[] = { | 330 | static struct dmi_system_id __initdata acpisleep_dmi_table[] = { |
| 356 | { | 331 | { |
| 357 | .callback = init_old_suspend_ordering, | 332 | .callback = init_old_suspend_ordering, |
| @@ -370,22 +345,6 @@ static struct dmi_system_id __initdata acpisleep_dmi_table[] = { | |||
| 370 | }, | 345 | }, |
| 371 | }, | 346 | }, |
| 372 | { | 347 | { |
| 373 | .callback = init_set_sci_en_on_resume, | ||
| 374 | .ident = "Apple MacBook 1,1", | ||
| 375 | .matches = { | ||
| 376 | DMI_MATCH(DMI_SYS_VENDOR, "Apple Computer, Inc."), | ||
| 377 | DMI_MATCH(DMI_PRODUCT_NAME, "MacBook1,1"), | ||
| 378 | }, | ||
| 379 | }, | ||
| 380 | { | ||
| 381 | .callback = init_set_sci_en_on_resume, | ||
| 382 | .ident = "Apple MacMini 1,1", | ||
| 383 | .matches = { | ||
| 384 | DMI_MATCH(DMI_SYS_VENDOR, "Apple Computer, Inc."), | ||
| 385 | DMI_MATCH(DMI_PRODUCT_NAME, "Macmini1,1"), | ||
| 386 | }, | ||
| 387 | }, | ||
| 388 | { | ||
| 389 | .callback = init_old_suspend_ordering, | 348 | .callback = init_old_suspend_ordering, |
| 390 | .ident = "Asus Pundit P1-AH2 (M2N8L motherboard)", | 349 | .ident = "Asus Pundit P1-AH2 (M2N8L motherboard)", |
| 391 | .matches = { | 350 | .matches = { |
| @@ -394,94 +353,6 @@ static struct dmi_system_id __initdata acpisleep_dmi_table[] = { | |||
| 394 | }, | 353 | }, |
| 395 | }, | 354 | }, |
| 396 | { | 355 | { |
| 397 | .callback = init_set_sci_en_on_resume, | ||
| 398 | .ident = "Toshiba Satellite L300", | ||
| 399 | .matches = { | ||
| 400 | DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), | ||
| 401 | DMI_MATCH(DMI_PRODUCT_NAME, "Satellite L300"), | ||
| 402 | }, | ||
| 403 | }, | ||
| 404 | { | ||
| 405 | .callback = init_set_sci_en_on_resume, | ||
| 406 | .ident = "Hewlett-Packard HP G7000 Notebook PC", | ||
| 407 | .matches = { | ||
| 408 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 409 | DMI_MATCH(DMI_PRODUCT_NAME, "HP G7000 Notebook PC"), | ||
| 410 | }, | ||
| 411 | }, | ||
| 412 | { | ||
| 413 | .callback = init_set_sci_en_on_resume, | ||
| 414 | .ident = "Hewlett-Packard HP Pavilion dv3 Notebook PC", | ||
| 415 | .matches = { | ||
| 416 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 417 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv3 Notebook PC"), | ||
| 418 | }, | ||
| 419 | }, | ||
| 420 | { | ||
| 421 | .callback = init_set_sci_en_on_resume, | ||
| 422 | .ident = "Hewlett-Packard Pavilion dv4", | ||
| 423 | .matches = { | ||
| 424 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 425 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv4"), | ||
| 426 | }, | ||
| 427 | }, | ||
| 428 | { | ||
| 429 | .callback = init_set_sci_en_on_resume, | ||
| 430 | .ident = "Hewlett-Packard Pavilion dv7", | ||
| 431 | .matches = { | ||
| 432 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 433 | DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv7"), | ||
| 434 | }, | ||
| 435 | }, | ||
| 436 | { | ||
| 437 | .callback = init_set_sci_en_on_resume, | ||
| 438 | .ident = "Hewlett-Packard Compaq Presario C700 Notebook PC", | ||
| 439 | .matches = { | ||
| 440 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 441 | DMI_MATCH(DMI_PRODUCT_NAME, "Compaq Presario C700 Notebook PC"), | ||
| 442 | }, | ||
| 443 | }, | ||
| 444 | { | ||
| 445 | .callback = init_set_sci_en_on_resume, | ||
| 446 | .ident = "Hewlett-Packard Compaq Presario CQ40 Notebook PC", | ||
| 447 | .matches = { | ||
| 448 | DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), | ||
| 449 | DMI_MATCH(DMI_PRODUCT_NAME, "Compaq Presario CQ40 Notebook PC"), | ||
| 450 | }, | ||
| 451 | }, | ||
| 452 | { | ||
| 453 | .callback = init_set_sci_en_on_resume, | ||
| 454 | .ident = "Lenovo ThinkPad T410", | ||
| 455 | .matches = { | ||
| 456 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
| 457 | DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T410"), | ||
| 458 | }, | ||
| 459 | }, | ||
| 460 | { | ||
| 461 | .callback = init_set_sci_en_on_resume, | ||
| 462 | .ident = "Lenovo ThinkPad T510", | ||
| 463 | .matches = { | ||
| 464 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
| 465 | DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T510"), | ||
| 466 | }, | ||
| 467 | }, | ||
| 468 | { | ||
| 469 | .callback = init_set_sci_en_on_resume, | ||
| 470 | .ident = "Lenovo ThinkPad W510", | ||
| 471 | .matches = { | ||
| 472 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
| 473 | DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad W510"), | ||
| 474 | }, | ||
| 475 | }, | ||
| 476 | { | ||
| 477 | .callback = init_set_sci_en_on_resume, | ||
| 478 | .ident = "Lenovo ThinkPad X201[s]", | ||
| 479 | .matches = { | ||
| 480 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
| 481 | DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X201"), | ||
| 482 | }, | ||
| 483 | }, | ||
| 484 | { | ||
| 485 | .callback = init_old_suspend_ordering, | 356 | .callback = init_old_suspend_ordering, |
| 486 | .ident = "Panasonic CF51-2L", | 357 | .ident = "Panasonic CF51-2L", |
| 487 | .matches = { | 358 | .matches = { |
| @@ -490,30 +361,6 @@ static struct dmi_system_id __initdata acpisleep_dmi_table[] = { | |||
| 490 | DMI_MATCH(DMI_BOARD_NAME, "CF51-2L"), | 361 | DMI_MATCH(DMI_BOARD_NAME, "CF51-2L"), |
| 491 | }, | 362 | }, |
| 492 | }, | 363 | }, |
| 493 | { | ||
| 494 | .callback = init_set_sci_en_on_resume, | ||
| 495 | .ident = "Dell Studio 1558", | ||
| 496 | .matches = { | ||
| 497 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | ||
| 498 | DMI_MATCH(DMI_PRODUCT_NAME, "Studio 1558"), | ||
| 499 | }, | ||
| 500 | }, | ||
| 501 | { | ||
| 502 | .callback = init_set_sci_en_on_resume, | ||
| 503 | .ident = "Dell Studio 1557", | ||
| 504 | .matches = { | ||
| 505 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | ||
| 506 | DMI_MATCH(DMI_PRODUCT_NAME, "Studio 1557"), | ||
| 507 | }, | ||
| 508 | }, | ||
| 509 | { | ||
| 510 | .callback = init_set_sci_en_on_resume, | ||
| 511 | .ident = "Dell Studio 1555", | ||
| 512 | .matches = { | ||
| 513 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | ||
| 514 | DMI_MATCH(DMI_PRODUCT_NAME, "Studio 1555"), | ||
| 515 | }, | ||
| 516 | }, | ||
| 517 | {}, | 364 | {}, |
| 518 | }; | 365 | }; |
| 519 | #endif /* CONFIG_SUSPEND */ | 366 | #endif /* CONFIG_SUSPEND */ |
diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 1a6b305006ef..9865d46f49a8 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c | |||
| @@ -45,6 +45,7 @@ | |||
| 45 | #include <acpi/acpi_bus.h> | 45 | #include <acpi/acpi_bus.h> |
| 46 | #include <acpi/acpi_drivers.h> | 46 | #include <acpi/acpi_drivers.h> |
| 47 | #include <linux/suspend.h> | 47 | #include <linux/suspend.h> |
| 48 | #include <acpi/video.h> | ||
| 48 | 49 | ||
| 49 | #define PREFIX "ACPI: " | 50 | #define PREFIX "ACPI: " |
| 50 | 51 | ||
| @@ -65,11 +66,6 @@ | |||
| 65 | 66 | ||
| 66 | #define MAX_NAME_LEN 20 | 67 | #define MAX_NAME_LEN 20 |
| 67 | 68 | ||
| 68 | #define ACPI_VIDEO_DISPLAY_CRT 1 | ||
| 69 | #define ACPI_VIDEO_DISPLAY_TV 2 | ||
| 70 | #define ACPI_VIDEO_DISPLAY_DVI 3 | ||
| 71 | #define ACPI_VIDEO_DISPLAY_LCD 4 | ||
| 72 | |||
| 73 | #define _COMPONENT ACPI_VIDEO_COMPONENT | 69 | #define _COMPONENT ACPI_VIDEO_COMPONENT |
| 74 | ACPI_MODULE_NAME("video"); | 70 | ACPI_MODULE_NAME("video"); |
| 75 | 71 | ||
| @@ -1748,11 +1744,27 @@ acpi_video_get_device_attr(struct acpi_video_bus *video, unsigned long device_id | |||
| 1748 | } | 1744 | } |
| 1749 | 1745 | ||
| 1750 | static int | 1746 | static int |
| 1747 | acpi_video_get_device_type(struct acpi_video_bus *video, | ||
| 1748 | unsigned long device_id) | ||
| 1749 | { | ||
| 1750 | struct acpi_video_enumerated_device *ids; | ||
| 1751 | int i; | ||
| 1752 | |||
| 1753 | for (i = 0; i < video->attached_count; i++) { | ||
| 1754 | ids = &video->attached_array[i]; | ||
| 1755 | if ((ids->value.int_val & 0xffff) == device_id) | ||
| 1756 | return ids->value.int_val; | ||
| 1757 | } | ||
| 1758 | |||
| 1759 | return 0; | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | static int | ||
| 1751 | acpi_video_bus_get_one_device(struct acpi_device *device, | 1763 | acpi_video_bus_get_one_device(struct acpi_device *device, |
| 1752 | struct acpi_video_bus *video) | 1764 | struct acpi_video_bus *video) |
| 1753 | { | 1765 | { |
| 1754 | unsigned long long device_id; | 1766 | unsigned long long device_id; |
| 1755 | int status; | 1767 | int status, device_type; |
| 1756 | struct acpi_video_device *data; | 1768 | struct acpi_video_device *data; |
| 1757 | struct acpi_video_device_attrib* attribute; | 1769 | struct acpi_video_device_attrib* attribute; |
| 1758 | 1770 | ||
| @@ -1797,8 +1809,25 @@ acpi_video_bus_get_one_device(struct acpi_device *device, | |||
| 1797 | } | 1809 | } |
| 1798 | if(attribute->bios_can_detect) | 1810 | if(attribute->bios_can_detect) |
| 1799 | data->flags.bios = 1; | 1811 | data->flags.bios = 1; |
| 1800 | } else | 1812 | } else { |
| 1801 | data->flags.unknown = 1; | 1813 | /* Check for legacy IDs */ |
| 1814 | device_type = acpi_video_get_device_type(video, | ||
| 1815 | device_id); | ||
| 1816 | /* Ignore bits 16 and 18-20 */ | ||
| 1817 | switch (device_type & 0xffe2ffff) { | ||
| 1818 | case ACPI_VIDEO_DISPLAY_LEGACY_MONITOR: | ||
| 1819 | data->flags.crt = 1; | ||
| 1820 | break; | ||
| 1821 | case ACPI_VIDEO_DISPLAY_LEGACY_PANEL: | ||
| 1822 | data->flags.lcd = 1; | ||
| 1823 | break; | ||
| 1824 | case ACPI_VIDEO_DISPLAY_LEGACY_TV: | ||
| 1825 | data->flags.tvout = 1; | ||
| 1826 | break; | ||
| 1827 | default: | ||
| 1828 | data->flags.unknown = 1; | ||
| 1829 | } | ||
| 1830 | } | ||
| 1802 | 1831 | ||
| 1803 | acpi_video_device_bind(video, data); | 1832 | acpi_video_device_bind(video, data); |
| 1804 | acpi_video_device_find_cap(data); | 1833 | acpi_video_device_find_cap(data); |
| @@ -2032,6 +2061,71 @@ out: | |||
| 2032 | return result; | 2061 | return result; |
| 2033 | } | 2062 | } |
| 2034 | 2063 | ||
| 2064 | int acpi_video_get_edid(struct acpi_device *device, int type, int device_id, | ||
| 2065 | void **edid) | ||
| 2066 | { | ||
| 2067 | struct acpi_video_bus *video; | ||
| 2068 | struct acpi_video_device *video_device; | ||
| 2069 | union acpi_object *buffer = NULL; | ||
| 2070 | acpi_status status; | ||
| 2071 | int i, length; | ||
| 2072 | |||
| 2073 | if (!device || !acpi_driver_data(device)) | ||
| 2074 | return -EINVAL; | ||
| 2075 | |||
| 2076 | video = acpi_driver_data(device); | ||
| 2077 | |||
| 2078 | for (i = 0; i < video->attached_count; i++) { | ||
| 2079 | video_device = video->attached_array[i].bind_info; | ||
| 2080 | length = 256; | ||
| 2081 | |||
| 2082 | if (!video_device) | ||
| 2083 | continue; | ||
| 2084 | |||
| 2085 | if (type) { | ||
| 2086 | switch (type) { | ||
| 2087 | case ACPI_VIDEO_DISPLAY_CRT: | ||
| 2088 | if (!video_device->flags.crt) | ||
| 2089 | continue; | ||
| 2090 | break; | ||
| 2091 | case ACPI_VIDEO_DISPLAY_TV: | ||
| 2092 | if (!video_device->flags.tvout) | ||
| 2093 | continue; | ||
| 2094 | break; | ||
| 2095 | case ACPI_VIDEO_DISPLAY_DVI: | ||
| 2096 | if (!video_device->flags.dvi) | ||
| 2097 | continue; | ||
| 2098 | break; | ||
| 2099 | case ACPI_VIDEO_DISPLAY_LCD: | ||
| 2100 | if (!video_device->flags.lcd) | ||
| 2101 | continue; | ||
| 2102 | break; | ||
| 2103 | } | ||
| 2104 | } else if (video_device->device_id != device_id) { | ||
| 2105 | continue; | ||
| 2106 | } | ||
| 2107 | |||
| 2108 | status = acpi_video_device_EDID(video_device, &buffer, length); | ||
| 2109 | |||
| 2110 | if (ACPI_FAILURE(status) || !buffer || | ||
| 2111 | buffer->type != ACPI_TYPE_BUFFER) { | ||
| 2112 | length = 128; | ||
| 2113 | status = acpi_video_device_EDID(video_device, &buffer, | ||
| 2114 | length); | ||
| 2115 | if (ACPI_FAILURE(status) || !buffer || | ||
| 2116 | buffer->type != ACPI_TYPE_BUFFER) { | ||
| 2117 | continue; | ||
| 2118 | } | ||
| 2119 | } | ||
| 2120 | |||
| 2121 | *edid = buffer->buffer.pointer; | ||
| 2122 | return length; | ||
| 2123 | } | ||
| 2124 | |||
| 2125 | return -ENODEV; | ||
| 2126 | } | ||
| 2127 | EXPORT_SYMBOL(acpi_video_get_edid); | ||
| 2128 | |||
| 2035 | static int | 2129 | static int |
| 2036 | acpi_video_bus_get_devices(struct acpi_video_bus *video, | 2130 | acpi_video_bus_get_devices(struct acpi_video_bus *video, |
| 2037 | struct acpi_device *device) | 2131 | struct acpi_device *device) |
diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index fc2f26b9b407..c5fef01b3c95 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c | |||
| @@ -250,7 +250,7 @@ static int __init acpi_backlight(char *str) | |||
| 250 | ACPI_VIDEO_BACKLIGHT_FORCE_VENDOR; | 250 | ACPI_VIDEO_BACKLIGHT_FORCE_VENDOR; |
| 251 | if (!strcmp("video", str)) | 251 | if (!strcmp("video", str)) |
| 252 | acpi_video_support |= | 252 | acpi_video_support |= |
| 253 | ACPI_VIDEO_OUTPUT_SWITCHING_FORCE_VIDEO; | 253 | ACPI_VIDEO_BACKLIGHT_FORCE_VIDEO; |
| 254 | } | 254 | } |
| 255 | return 1; | 255 | return 1; |
| 256 | } | 256 | } |
diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h index bd833ea3ba49..7182c337aef1 100644 --- a/drivers/pci/pcie/aer/aerdrv.h +++ b/drivers/pci/pcie/aer/aerdrv.h | |||
| @@ -134,4 +134,21 @@ static inline int aer_osc_setup(struct pcie_device *pciedev) | |||
| 134 | } | 134 | } |
| 135 | #endif | 135 | #endif |
| 136 | 136 | ||
| 137 | #ifdef CONFIG_ACPI_APEI | ||
| 138 | extern int pcie_aer_get_firmware_first(struct pci_dev *pci_dev); | ||
| 139 | #else | ||
| 140 | static inline int pcie_aer_get_firmware_first(struct pci_dev *pci_dev) | ||
| 141 | { | ||
| 142 | if (pci_dev->__aer_firmware_first_valid) | ||
| 143 | return pci_dev->__aer_firmware_first; | ||
| 144 | return 0; | ||
| 145 | } | ||
| 146 | #endif | ||
| 147 | |||
| 148 | static inline void pcie_aer_force_firmware_first(struct pci_dev *pci_dev, | ||
| 149 | int enable) | ||
| 150 | { | ||
| 151 | pci_dev->__aer_firmware_first = !!enable; | ||
| 152 | pci_dev->__aer_firmware_first_valid = 1; | ||
| 153 | } | ||
| 137 | #endif /* _AERDRV_H_ */ | 154 | #endif /* _AERDRV_H_ */ |
diff --git a/drivers/pci/pcie/aer/aerdrv_acpi.c b/drivers/pci/pcie/aer/aerdrv_acpi.c index 04814087658d..f278d7b0d95d 100644 --- a/drivers/pci/pcie/aer/aerdrv_acpi.c +++ b/drivers/pci/pcie/aer/aerdrv_acpi.c | |||
| @@ -16,6 +16,7 @@ | |||
| 16 | #include <linux/acpi.h> | 16 | #include <linux/acpi.h> |
| 17 | #include <linux/pci-acpi.h> | 17 | #include <linux/pci-acpi.h> |
| 18 | #include <linux/delay.h> | 18 | #include <linux/delay.h> |
| 19 | #include <acpi/apei.h> | ||
| 19 | #include "aerdrv.h" | 20 | #include "aerdrv.h" |
| 20 | 21 | ||
| 21 | /** | 22 | /** |
| @@ -53,3 +54,79 @@ int aer_osc_setup(struct pcie_device *pciedev) | |||
| 53 | 54 | ||
| 54 | return 0; | 55 | return 0; |
| 55 | } | 56 | } |
| 57 | |||
| 58 | #ifdef CONFIG_ACPI_APEI | ||
| 59 | static inline int hest_match_pci(struct acpi_hest_aer_common *p, | ||
| 60 | struct pci_dev *pci) | ||
| 61 | { | ||
| 62 | return (0 == pci_domain_nr(pci->bus) && | ||
| 63 | p->bus == pci->bus->number && | ||
| 64 | p->device == PCI_SLOT(pci->devfn) && | ||
| 65 | p->function == PCI_FUNC(pci->devfn)); | ||
| 66 | } | ||
| 67 | |||
| 68 | struct aer_hest_parse_info { | ||
| 69 | struct pci_dev *pci_dev; | ||
| 70 | int firmware_first; | ||
| 71 | }; | ||
| 72 | |||
| 73 | static int aer_hest_parse(struct acpi_hest_header *hest_hdr, void *data) | ||
| 74 | { | ||
| 75 | struct aer_hest_parse_info *info = data; | ||
| 76 | struct acpi_hest_aer_common *p; | ||
| 77 | u8 pcie_type = 0; | ||
| 78 | u8 bridge = 0; | ||
| 79 | int ff = 0; | ||
| 80 | |||
| 81 | switch (hest_hdr->type) { | ||
| 82 | case ACPI_HEST_TYPE_AER_ROOT_PORT: | ||
| 83 | pcie_type = PCI_EXP_TYPE_ROOT_PORT; | ||
| 84 | break; | ||
| 85 | case ACPI_HEST_TYPE_AER_ENDPOINT: | ||
| 86 | pcie_type = PCI_EXP_TYPE_ENDPOINT; | ||
| 87 | break; | ||
| 88 | case ACPI_HEST_TYPE_AER_BRIDGE: | ||
| 89 | if ((info->pci_dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) | ||
| 90 | bridge = 1; | ||
| 91 | break; | ||
| 92 | default: | ||
| 93 | return 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | p = (struct acpi_hest_aer_common *)(hest_hdr + 1); | ||
| 97 | if (p->flags & ACPI_HEST_GLOBAL) { | ||
| 98 | if ((info->pci_dev->is_pcie && | ||
| 99 | info->pci_dev->pcie_type == pcie_type) || bridge) | ||
| 100 | ff = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); | ||
| 101 | } else | ||
| 102 | if (hest_match_pci(p, info->pci_dev)) | ||
| 103 | ff = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); | ||
| 104 | info->firmware_first = ff; | ||
| 105 | |||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | static void aer_set_firmware_first(struct pci_dev *pci_dev) | ||
| 110 | { | ||
| 111 | int rc; | ||
| 112 | struct aer_hest_parse_info info = { | ||
| 113 | .pci_dev = pci_dev, | ||
| 114 | .firmware_first = 0, | ||
| 115 | }; | ||
| 116 | |||
| 117 | rc = apei_hest_parse(aer_hest_parse, &info); | ||
| 118 | |||
| 119 | if (rc) | ||
| 120 | pci_dev->__aer_firmware_first = 0; | ||
| 121 | else | ||
| 122 | pci_dev->__aer_firmware_first = info.firmware_first; | ||
| 123 | pci_dev->__aer_firmware_first_valid = 1; | ||
| 124 | } | ||
| 125 | |||
| 126 | int pcie_aer_get_firmware_first(struct pci_dev *dev) | ||
| 127 | { | ||
| 128 | if (!dev->__aer_firmware_first_valid) | ||
| 129 | aer_set_firmware_first(dev); | ||
| 130 | return dev->__aer_firmware_first; | ||
| 131 | } | ||
| 132 | #endif | ||
diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index aceb04b67b60..586b6713e417 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c | |||
| @@ -36,7 +36,7 @@ int pci_enable_pcie_error_reporting(struct pci_dev *dev) | |||
| 36 | u16 reg16 = 0; | 36 | u16 reg16 = 0; |
| 37 | int pos; | 37 | int pos; |
| 38 | 38 | ||
| 39 | if (dev->aer_firmware_first) | 39 | if (pcie_aer_get_firmware_first(dev)) |
| 40 | return -EIO; | 40 | return -EIO; |
| 41 | 41 | ||
| 42 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); | 42 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
| @@ -64,7 +64,7 @@ int pci_disable_pcie_error_reporting(struct pci_dev *dev) | |||
| 64 | u16 reg16 = 0; | 64 | u16 reg16 = 0; |
| 65 | int pos; | 65 | int pos; |
| 66 | 66 | ||
| 67 | if (dev->aer_firmware_first) | 67 | if (pcie_aer_get_firmware_first(dev)) |
| 68 | return -EIO; | 68 | return -EIO; |
| 69 | 69 | ||
| 70 | pos = pci_pcie_cap(dev); | 70 | pos = pci_pcie_cap(dev); |
| @@ -859,7 +859,7 @@ void aer_delete_rootport(struct aer_rpc *rpc) | |||
| 859 | */ | 859 | */ |
| 860 | int aer_init(struct pcie_device *dev) | 860 | int aer_init(struct pcie_device *dev) |
| 861 | { | 861 | { |
| 862 | if (dev->port->aer_firmware_first) { | 862 | if (pcie_aer_get_firmware_first(dev->port)) { |
| 863 | dev_printk(KERN_DEBUG, &dev->device, | 863 | dev_printk(KERN_DEBUG, &dev->device, |
| 864 | "PCIe errors handled by platform firmware.\n"); | 864 | "PCIe errors handled by platform firmware.\n"); |
| 865 | goto out; | 865 | goto out; |
| @@ -873,7 +873,7 @@ out: | |||
| 873 | if (forceload) { | 873 | if (forceload) { |
| 874 | dev_printk(KERN_DEBUG, &dev->device, | 874 | dev_printk(KERN_DEBUG, &dev->device, |
| 875 | "aerdrv forceload requested.\n"); | 875 | "aerdrv forceload requested.\n"); |
| 876 | dev->port->aer_firmware_first = 0; | 876 | pcie_aer_force_firmware_first(dev->port, 0); |
| 877 | return 0; | 877 | return 0; |
| 878 | } | 878 | } |
| 879 | return -ENXIO; | 879 | return -ENXIO; |
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c82548afcd5c..f4adba2d1dd3 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c | |||
| @@ -10,7 +10,6 @@ | |||
| 10 | #include <linux/module.h> | 10 | #include <linux/module.h> |
| 11 | #include <linux/cpumask.h> | 11 | #include <linux/cpumask.h> |
| 12 | #include <linux/pci-aspm.h> | 12 | #include <linux/pci-aspm.h> |
| 13 | #include <acpi/acpi_hest.h> | ||
| 14 | #include "pci.h" | 13 | #include "pci.h" |
| 15 | 14 | ||
| 16 | #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ | 15 | #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ |
| @@ -904,12 +903,6 @@ void set_pcie_hotplug_bridge(struct pci_dev *pdev) | |||
| 904 | pdev->is_hotplug_bridge = 1; | 903 | pdev->is_hotplug_bridge = 1; |
| 905 | } | 904 | } |
| 906 | 905 | ||
| 907 | static void set_pci_aer_firmware_first(struct pci_dev *pdev) | ||
| 908 | { | ||
| 909 | if (acpi_hest_firmware_first_pci(pdev)) | ||
| 910 | pdev->aer_firmware_first = 1; | ||
| 911 | } | ||
| 912 | |||
| 913 | #define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED) | 906 | #define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED) |
| 914 | 907 | ||
| 915 | /** | 908 | /** |
| @@ -939,7 +932,6 @@ int pci_setup_device(struct pci_dev *dev) | |||
| 939 | dev->multifunction = !!(hdr_type & 0x80); | 932 | dev->multifunction = !!(hdr_type & 0x80); |
| 940 | dev->error_state = pci_channel_io_normal; | 933 | dev->error_state = pci_channel_io_normal; |
| 941 | set_pcie_port_type(dev); | 934 | set_pcie_port_type(dev); |
| 942 | set_pci_aer_firmware_first(dev); | ||
| 943 | 935 | ||
| 944 | list_for_each_entry(slot, &dev->bus->slots, list) | 936 | list_for_each_entry(slot, &dev->bus->slots, list) |
| 945 | if (PCI_SLOT(dev->devfn) == slot->number) | 937 | if (PCI_SLOT(dev->devfn) == slot->number) |
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index 4d74fc72c195..0210898458b2 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c | |||
| @@ -277,8 +277,10 @@ DEFINE_SIMPLE_ATTRIBUTE(fops_x32, debugfs_u32_get, debugfs_u32_set, "0x%08llx\n" | |||
| 277 | DEFINE_SIMPLE_ATTRIBUTE(fops_x32_ro, debugfs_u32_get, NULL, "0x%08llx\n"); | 277 | DEFINE_SIMPLE_ATTRIBUTE(fops_x32_ro, debugfs_u32_get, NULL, "0x%08llx\n"); |
| 278 | DEFINE_SIMPLE_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n"); | 278 | DEFINE_SIMPLE_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n"); |
| 279 | 279 | ||
| 280 | DEFINE_SIMPLE_ATTRIBUTE(fops_x64, debugfs_u64_get, debugfs_u64_set, "0x%016llx\n"); | ||
| 281 | |||
| 280 | /* | 282 | /* |
| 281 | * debugfs_create_x{8,16,32} - create a debugfs file that is used to read and write an unsigned {8,16,32}-bit value | 283 | * debugfs_create_x{8,16,32,64} - create a debugfs file that is used to read and write an unsigned {8,16,32,64}-bit value |
| 282 | * | 284 | * |
| 283 | * These functions are exactly the same as the above functions (but use a hex | 285 | * These functions are exactly the same as the above functions (but use a hex |
| 284 | * output for the decimal challenged). For details look at the above unsigned | 286 | * output for the decimal challenged). For details look at the above unsigned |
| @@ -357,6 +359,23 @@ struct dentry *debugfs_create_x32(const char *name, mode_t mode, | |||
| 357 | } | 359 | } |
| 358 | EXPORT_SYMBOL_GPL(debugfs_create_x32); | 360 | EXPORT_SYMBOL_GPL(debugfs_create_x32); |
| 359 | 361 | ||
| 362 | /** | ||
| 363 | * debugfs_create_x64 - create a debugfs file that is used to read and write an unsigned 64-bit value | ||
| 364 | * @name: a pointer to a string containing the name of the file to create. | ||
| 365 | * @mode: the permission that the file should have | ||
| 366 | * @parent: a pointer to the parent dentry for this file. This should be a | ||
| 367 | * directory dentry if set. If this parameter is %NULL, then the | ||
| 368 | * file will be created in the root of the debugfs filesystem. | ||
| 369 | * @value: a pointer to the variable that the file should read to and write | ||
| 370 | * from. | ||
| 371 | */ | ||
| 372 | struct dentry *debugfs_create_x64(const char *name, mode_t mode, | ||
| 373 | struct dentry *parent, u64 *value) | ||
| 374 | { | ||
| 375 | return debugfs_create_file(name, mode, parent, value, &fops_x64); | ||
| 376 | } | ||
| 377 | EXPORT_SYMBOL_GPL(debugfs_create_x64); | ||
| 378 | |||
| 360 | 379 | ||
| 361 | static int debugfs_size_t_set(void *data, u64 val) | 380 | static int debugfs_size_t_set(void *data, u64 val) |
| 362 | { | 381 | { |
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 7bf83ddf82e0..baacd98e7cc6 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h | |||
| @@ -373,7 +373,7 @@ struct acpi_pci_root { | |||
| 373 | struct acpi_pci_id id; | 373 | struct acpi_pci_id id; |
| 374 | struct pci_bus *bus; | 374 | struct pci_bus *bus; |
| 375 | u16 segment; | 375 | u16 segment; |
| 376 | u8 bus_nr; | 376 | struct resource secondary; /* downstream bus range */ |
| 377 | 377 | ||
| 378 | u32 osc_support_set; /* _OSC state of support bits */ | 378 | u32 osc_support_set; /* _OSC state of support bits */ |
| 379 | u32 osc_control_set; /* _OSC state of control bits */ | 379 | u32 osc_control_set; /* _OSC state of control bits */ |
diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 4f7b44866b76..23d78b4d088b 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h | |||
| @@ -104,8 +104,7 @@ int acpi_pci_bind_root(struct acpi_device *device); | |||
| 104 | 104 | ||
| 105 | /* Arch-defined function to add a bus to the system */ | 105 | /* Arch-defined function to add a bus to the system */ |
| 106 | 106 | ||
| 107 | struct pci_bus *pci_acpi_scan_root(struct acpi_device *device, int domain, | 107 | struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root); |
| 108 | int bus); | ||
| 109 | void pci_acpi_crs_quirks(void); | 108 | void pci_acpi_crs_quirks(void); |
| 110 | 109 | ||
| 111 | /* -------------------------------------------------------------------------- | 110 | /* -------------------------------------------------------------------------- |
diff --git a/include/acpi/acpi_hest.h b/include/acpi/acpi_hest.h deleted file mode 100644 index 63194d03cb2d..000000000000 --- a/include/acpi/acpi_hest.h +++ /dev/null | |||
| @@ -1,12 +0,0 @@ | |||
| 1 | #ifndef __ACPI_HEST_H | ||
| 2 | #define __ACPI_HEST_H | ||
| 3 | |||
| 4 | #include <linux/pci.h> | ||
| 5 | |||
| 6 | #ifdef CONFIG_ACPI | ||
| 7 | extern int acpi_hest_firmware_first_pci(struct pci_dev *pci); | ||
| 8 | #else | ||
| 9 | static inline int acpi_hest_firmware_first_pci(struct pci_dev *pci) { return 0; } | ||
| 10 | #endif | ||
| 11 | |||
| 12 | #endif | ||
diff --git a/include/acpi/apei.h b/include/acpi/apei.h new file mode 100644 index 000000000000..b3365025ff8d --- /dev/null +++ b/include/acpi/apei.h | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | /* | ||
| 2 | * apei.h - ACPI Platform Error Interface | ||
| 3 | */ | ||
| 4 | |||
| 5 | #ifndef ACPI_APEI_H | ||
| 6 | #define ACPI_APEI_H | ||
| 7 | |||
| 8 | #include <linux/acpi.h> | ||
| 9 | #include <linux/cper.h> | ||
| 10 | #include <asm/ioctls.h> | ||
| 11 | |||
| 12 | #define APEI_ERST_INVALID_RECORD_ID 0xffffffffffffffffULL | ||
| 13 | |||
| 14 | #define APEI_ERST_CLEAR_RECORD _IOW('E', 1, u64) | ||
| 15 | #define APEI_ERST_GET_RECORD_COUNT _IOR('E', 2, u32) | ||
| 16 | |||
| 17 | #ifdef __KERNEL__ | ||
| 18 | |||
| 19 | extern int hest_disable; | ||
| 20 | extern int erst_disable; | ||
| 21 | |||
| 22 | typedef int (*apei_hest_func_t)(struct acpi_hest_header *hest_hdr, void *data); | ||
| 23 | int apei_hest_parse(apei_hest_func_t func, void *data); | ||
| 24 | |||
| 25 | int erst_write(const struct cper_record_header *record); | ||
| 26 | ssize_t erst_get_record_count(void); | ||
| 27 | int erst_get_next_record_id(u64 *record_id); | ||
| 28 | ssize_t erst_read(u64 record_id, struct cper_record_header *record, | ||
| 29 | size_t buflen); | ||
| 30 | ssize_t erst_read_next(struct cper_record_header *record, size_t buflen); | ||
| 31 | int erst_clear(u64 record_id); | ||
| 32 | |||
| 33 | #endif | ||
| 34 | #endif | ||
diff --git a/include/acpi/atomicio.h b/include/acpi/atomicio.h new file mode 100644 index 000000000000..8b9fb4b0b9ce --- /dev/null +++ b/include/acpi/atomicio.h | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | #ifndef ACPI_ATOMIC_IO_H | ||
| 2 | #define ACPI_ATOMIC_IO_H | ||
| 3 | |||
| 4 | int acpi_pre_map_gar(struct acpi_generic_address *reg); | ||
| 5 | int acpi_post_unmap_gar(struct acpi_generic_address *reg); | ||
| 6 | |||
| 7 | int acpi_atomic_read(u64 *val, struct acpi_generic_address *reg); | ||
| 8 | int acpi_atomic_write(u64 val, struct acpi_generic_address *reg); | ||
| 9 | |||
| 10 | #endif | ||
diff --git a/include/acpi/hed.h b/include/acpi/hed.h new file mode 100644 index 000000000000..46e1249b70cc --- /dev/null +++ b/include/acpi/hed.h | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | /* | ||
| 2 | * hed.h - ACPI Hardware Error Device | ||
| 3 | * | ||
| 4 | * Copyright (C) 2009, Intel Corp. | ||
| 5 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * This file is released under the GPLv2. | ||
| 8 | */ | ||
| 9 | |||
| 10 | #ifndef ACPI_HED_H | ||
| 11 | #define ACPI_HED_H | ||
| 12 | |||
| 13 | #include <linux/notifier.h> | ||
| 14 | |||
| 15 | int register_acpi_hed_notifier(struct notifier_block *nb); | ||
| 16 | void unregister_acpi_hed_notifier(struct notifier_block *nb); | ||
| 17 | |||
| 18 | #endif | ||
diff --git a/include/acpi/video.h b/include/acpi/video.h index cf7be3dd157b..551793c9b6e8 100644 --- a/include/acpi/video.h +++ b/include/acpi/video.h | |||
| @@ -1,12 +1,28 @@ | |||
| 1 | #ifndef __ACPI_VIDEO_H | 1 | #ifndef __ACPI_VIDEO_H |
| 2 | #define __ACPI_VIDEO_H | 2 | #define __ACPI_VIDEO_H |
| 3 | 3 | ||
| 4 | #define ACPI_VIDEO_DISPLAY_CRT 1 | ||
| 5 | #define ACPI_VIDEO_DISPLAY_TV 2 | ||
| 6 | #define ACPI_VIDEO_DISPLAY_DVI 3 | ||
| 7 | #define ACPI_VIDEO_DISPLAY_LCD 4 | ||
| 8 | |||
| 9 | #define ACPI_VIDEO_DISPLAY_LEGACY_MONITOR 0x0100 | ||
| 10 | #define ACPI_VIDEO_DISPLAY_LEGACY_PANEL 0x0110 | ||
| 11 | #define ACPI_VIDEO_DISPLAY_LEGACY_TV 0x0200 | ||
| 12 | |||
| 4 | #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) | 13 | #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) |
| 5 | extern int acpi_video_register(void); | 14 | extern int acpi_video_register(void); |
| 6 | extern void acpi_video_unregister(void); | 15 | extern void acpi_video_unregister(void); |
| 16 | extern int acpi_video_get_edid(struct acpi_device *device, int type, | ||
| 17 | int device_id, void **edid); | ||
| 7 | #else | 18 | #else |
| 8 | static inline int acpi_video_register(void) { return 0; } | 19 | static inline int acpi_video_register(void) { return 0; } |
| 9 | static inline void acpi_video_unregister(void) { return; } | 20 | static inline void acpi_video_unregister(void) { return; } |
| 21 | static inline int acpi_video_get_edid(struct acpi_device *device, int type, | ||
| 22 | int device_id, void **edid) | ||
| 23 | { | ||
| 24 | return -ENODEV; | ||
| 25 | } | ||
| 10 | #endif | 26 | #endif |
| 11 | 27 | ||
| 12 | #endif | 28 | #endif |
diff --git a/include/linux/acpi.h b/include/linux/acpi.h index b926afe8c03e..87ca4913294c 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h | |||
| @@ -251,7 +251,6 @@ int acpi_check_mem_region(resource_size_t start, resource_size_t n, | |||
| 251 | void __init acpi_no_s4_hw_signature(void); | 251 | void __init acpi_no_s4_hw_signature(void); |
| 252 | void __init acpi_old_suspend_ordering(void); | 252 | void __init acpi_old_suspend_ordering(void); |
| 253 | void __init acpi_s4_no_nvs(void); | 253 | void __init acpi_s4_no_nvs(void); |
| 254 | void __init acpi_set_sci_en_on_resume(void); | ||
| 255 | #endif /* CONFIG_PM_SLEEP */ | 254 | #endif /* CONFIG_PM_SLEEP */ |
| 256 | 255 | ||
| 257 | struct acpi_osc_context { | 256 | struct acpi_osc_context { |
diff --git a/include/linux/cper.h b/include/linux/cper.h new file mode 100644 index 000000000000..4b38f905b705 --- /dev/null +++ b/include/linux/cper.h | |||
| @@ -0,0 +1,314 @@ | |||
| 1 | /* | ||
| 2 | * UEFI Common Platform Error Record | ||
| 3 | * | ||
| 4 | * Copyright (C) 2010, Intel Corp. | ||
| 5 | * Author: Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or | ||
| 8 | * modify it under the terms of the GNU General Public License version | ||
| 9 | * 2 as published by the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | #ifndef LINUX_CPER_H | ||
| 22 | #define LINUX_CPER_H | ||
| 23 | |||
| 24 | #include <linux/uuid.h> | ||
| 25 | |||
| 26 | /* CPER record signature and the size */ | ||
| 27 | #define CPER_SIG_RECORD "CPER" | ||
| 28 | #define CPER_SIG_SIZE 4 | ||
| 29 | /* Used in signature_end field in struct cper_record_header */ | ||
| 30 | #define CPER_SIG_END 0xffffffff | ||
| 31 | |||
| 32 | /* | ||
| 33 | * CPER record header revision, used in revision field in struct | ||
| 34 | * cper_record_header | ||
| 35 | */ | ||
| 36 | #define CPER_RECORD_REV 0x0100 | ||
| 37 | |||
| 38 | /* | ||
| 39 | * Severity difinition for error_severity in struct cper_record_header | ||
| 40 | * and section_severity in struct cper_section_descriptor | ||
| 41 | */ | ||
| 42 | #define CPER_SER_RECOVERABLE 0x0 | ||
| 43 | #define CPER_SER_FATAL 0x1 | ||
| 44 | #define CPER_SER_CORRECTED 0x2 | ||
| 45 | #define CPER_SER_INFORMATIONAL 0x3 | ||
| 46 | |||
| 47 | /* | ||
| 48 | * Validation bits difinition for validation_bits in struct | ||
| 49 | * cper_record_header. If set, corresponding fields in struct | ||
| 50 | * cper_record_header contain valid information. | ||
| 51 | * | ||
| 52 | * corresponds platform_id | ||
| 53 | */ | ||
| 54 | #define CPER_VALID_PLATFORM_ID 0x0001 | ||
| 55 | /* corresponds timestamp */ | ||
| 56 | #define CPER_VALID_TIMESTAMP 0x0002 | ||
| 57 | /* corresponds partition_id */ | ||
| 58 | #define CPER_VALID_PARTITION_ID 0x0004 | ||
| 59 | |||
| 60 | /* | ||
| 61 | * Notification type used to generate error record, used in | ||
| 62 | * notification_type in struct cper_record_header | ||
| 63 | * | ||
| 64 | * Corrected Machine Check | ||
| 65 | */ | ||
| 66 | #define CPER_NOTIFY_CMC \ | ||
| 67 | UUID_LE(0x2DCE8BB1, 0xBDD7, 0x450e, 0xB9, 0xAD, 0x9C, 0xF4, \ | ||
| 68 | 0xEB, 0xD4, 0xF8, 0x90) | ||
| 69 | /* Corrected Platform Error */ | ||
| 70 | #define CPER_NOTIFY_CPE \ | ||
| 71 | UUID_LE(0x4E292F96, 0xD843, 0x4a55, 0xA8, 0xC2, 0xD4, 0x81, \ | ||
| 72 | 0xF2, 0x7E, 0xBE, 0xEE) | ||
| 73 | /* Machine Check Exception */ | ||
| 74 | #define CPER_NOTIFY_MCE \ | ||
| 75 | UUID_LE(0xE8F56FFE, 0x919C, 0x4cc5, 0xBA, 0x88, 0x65, 0xAB, \ | ||
| 76 | 0xE1, 0x49, 0x13, 0xBB) | ||
| 77 | /* PCI Express Error */ | ||
| 78 | #define CPER_NOTIFY_PCIE \ | ||
| 79 | UUID_LE(0xCF93C01F, 0x1A16, 0x4dfc, 0xB8, 0xBC, 0x9C, 0x4D, \ | ||
| 80 | 0xAF, 0x67, 0xC1, 0x04) | ||
| 81 | /* INIT Record (for IPF) */ | ||
| 82 | #define CPER_NOTIFY_INIT \ | ||
| 83 | UUID_LE(0xCC5263E8, 0x9308, 0x454a, 0x89, 0xD0, 0x34, 0x0B, \ | ||
| 84 | 0xD3, 0x9B, 0xC9, 0x8E) | ||
| 85 | /* Non-Maskable Interrupt */ | ||
| 86 | #define CPER_NOTIFY_NMI \ | ||
| 87 | UUID_LE(0x5BAD89FF, 0xB7E6, 0x42c9, 0x81, 0x4A, 0xCF, 0x24, \ | ||
| 88 | 0x85, 0xD6, 0xE9, 0x8A) | ||
| 89 | /* BOOT Error Record */ | ||
| 90 | #define CPER_NOTIFY_BOOT \ | ||
| 91 | UUID_LE(0x3D61A466, 0xAB40, 0x409a, 0xA6, 0x98, 0xF3, 0x62, \ | ||
| 92 | 0xD4, 0x64, 0xB3, 0x8F) | ||
| 93 | /* DMA Remapping Error */ | ||
| 94 | #define CPER_NOTIFY_DMAR \ | ||
| 95 | UUID_LE(0x667DD791, 0xC6B3, 0x4c27, 0x8A, 0x6B, 0x0F, 0x8E, \ | ||
| 96 | 0x72, 0x2D, 0xEB, 0x41) | ||
| 97 | |||
| 98 | /* | ||
| 99 | * Flags bits definitions for flags in struct cper_record_header | ||
| 100 | * If set, the error has been recovered | ||
| 101 | */ | ||
| 102 | #define CPER_HW_ERROR_FLAGS_RECOVERED 0x1 | ||
| 103 | /* If set, the error is for previous boot */ | ||
| 104 | #define CPER_HW_ERROR_FLAGS_PREVERR 0x2 | ||
| 105 | /* If set, the error is injected for testing */ | ||
| 106 | #define CPER_HW_ERROR_FLAGS_SIMULATED 0x4 | ||
| 107 | |||
| 108 | /* | ||
| 109 | * CPER section header revision, used in revision field in struct | ||
| 110 | * cper_section_descriptor | ||
| 111 | */ | ||
| 112 | #define CPER_SEC_REV 0x0100 | ||
| 113 | |||
| 114 | /* | ||
| 115 | * Validation bits difinition for validation_bits in struct | ||
| 116 | * cper_section_descriptor. If set, corresponding fields in struct | ||
| 117 | * cper_section_descriptor contain valid information. | ||
| 118 | * | ||
| 119 | * corresponds fru_id | ||
| 120 | */ | ||
| 121 | #define CPER_SEC_VALID_FRU_ID 0x1 | ||
| 122 | /* corresponds fru_text */ | ||
| 123 | #define CPER_SEC_VALID_FRU_TEXT 0x2 | ||
| 124 | |||
| 125 | /* | ||
| 126 | * Flags bits definitions for flags in struct cper_section_descriptor | ||
| 127 | * | ||
| 128 | * If set, the section is associated with the error condition | ||
| 129 | * directly, and should be focused on | ||
| 130 | */ | ||
| 131 | #define CPER_SEC_PRIMARY 0x0001 | ||
| 132 | /* | ||
| 133 | * If set, the error was not contained within the processor or memory | ||
| 134 | * hierarchy and the error may have propagated to persistent storage | ||
| 135 | * or network | ||
| 136 | */ | ||
| 137 | #define CPER_SEC_CONTAINMENT_WARNING 0x0002 | ||
| 138 | /* If set, the component must be re-initialized or re-enabled prior to use */ | ||
| 139 | #define CPER_SEC_RESET 0x0004 | ||
| 140 | /* If set, Linux may choose to discontinue use of the resource */ | ||
| 141 | #define CPER_SEC_ERROR_THRESHOLD_EXCEEDED 0x0008 | ||
| 142 | /* | ||
| 143 | * If set, resource could not be queried for error information due to | ||
| 144 | * conflicts with other system software or resources. Some fields of | ||
| 145 | * the section will be invalid | ||
| 146 | */ | ||
| 147 | #define CPER_SEC_RESOURCE_NOT_ACCESSIBLE 0x0010 | ||
| 148 | /* | ||
| 149 | * If set, action has been taken to ensure error containment (such as | ||
| 150 | * poisoning data), but the error has not been fully corrected and the | ||
| 151 | * data has not been consumed. Linux may choose to take further | ||
| 152 | * corrective action before the data is consumed | ||
| 153 | */ | ||
| 154 | #define CPER_SEC_LATENT_ERROR 0x0020 | ||
| 155 | |||
| 156 | /* | ||
| 157 | * Section type definitions, used in section_type field in struct | ||
| 158 | * cper_section_descriptor | ||
| 159 | * | ||
| 160 | * Processor Generic | ||
| 161 | */ | ||
| 162 | #define CPER_SEC_PROC_GENERIC \ | ||
| 163 | UUID_LE(0x9876CCAD, 0x47B4, 0x4bdb, 0xB6, 0x5E, 0x16, 0xF1, \ | ||
| 164 | 0x93, 0xC4, 0xF3, 0xDB) | ||
| 165 | /* Processor Specific: X86/X86_64 */ | ||
| 166 | #define CPER_SEC_PROC_IA \ | ||
| 167 | UUID_LE(0xDC3EA0B0, 0xA144, 0x4797, 0xB9, 0x5B, 0x53, 0xFA, \ | ||
| 168 | 0x24, 0x2B, 0x6E, 0x1D) | ||
| 169 | /* Processor Specific: IA64 */ | ||
| 170 | #define CPER_SEC_PROC_IPF \ | ||
| 171 | UUID_LE(0xE429FAF1, 0x3CB7, 0x11D4, 0x0B, 0xCA, 0x07, 0x00, \ | ||
| 172 | 0x80, 0xC7, 0x3C, 0x88, 0x81) | ||
| 173 | /* Platform Memory */ | ||
| 174 | #define CPER_SEC_PLATFORM_MEM \ | ||
| 175 | UUID_LE(0xA5BC1114, 0x6F64, 0x4EDE, 0xB8, 0x63, 0x3E, 0x83, \ | ||
| 176 | 0xED, 0x7C, 0x83, 0xB1) | ||
| 177 | #define CPER_SEC_PCIE \ | ||
| 178 | UUID_LE(0xD995E954, 0xBBC1, 0x430F, 0xAD, 0x91, 0xB4, 0x4D, \ | ||
| 179 | 0xCB, 0x3C, 0x6F, 0x35) | ||
| 180 | /* Firmware Error Record Reference */ | ||
| 181 | #define CPER_SEC_FW_ERR_REC_REF \ | ||
| 182 | UUID_LE(0x81212A96, 0x09ED, 0x4996, 0x94, 0x71, 0x8D, 0x72, \ | ||
| 183 | 0x9C, 0x8E, 0x69, 0xED) | ||
| 184 | /* PCI/PCI-X Bus */ | ||
| 185 | #define CPER_SEC_PCI_X_BUS \ | ||
| 186 | UUID_LE(0xC5753963, 0x3B84, 0x4095, 0xBF, 0x78, 0xED, 0xDA, \ | ||
| 187 | 0xD3, 0xF9, 0xC9, 0xDD) | ||
| 188 | /* PCI Component/Device */ | ||
| 189 | #define CPER_SEC_PCI_DEV \ | ||
| 190 | UUID_LE(0xEB5E4685, 0xCA66, 0x4769, 0xB6, 0xA2, 0x26, 0x06, \ | ||
| 191 | 0x8B, 0x00, 0x13, 0x26) | ||
| 192 | #define CPER_SEC_DMAR_GENERIC \ | ||
| 193 | UUID_LE(0x5B51FEF7, 0xC79D, 0x4434, 0x8F, 0x1B, 0xAA, 0x62, \ | ||
| 194 | 0xDE, 0x3E, 0x2C, 0x64) | ||
| 195 | /* Intel VT for Directed I/O specific DMAr */ | ||
| 196 | #define CPER_SEC_DMAR_VT \ | ||
| 197 | UUID_LE(0x71761D37, 0x32B2, 0x45cd, 0xA7, 0xD0, 0xB0, 0xFE, \ | ||
| 198 | 0xDD, 0x93, 0xE8, 0xCF) | ||
| 199 | /* IOMMU specific DMAr */ | ||
| 200 | #define CPER_SEC_DMAR_IOMMU \ | ||
| 201 | UUID_LE(0x036F84E1, 0x7F37, 0x428c, 0xA7, 0x9E, 0x57, 0x5F, \ | ||
| 202 | 0xDF, 0xAA, 0x84, 0xEC) | ||
| 203 | |||
| 204 | /* | ||
| 205 | * All tables and structs must be byte-packed to match CPER | ||
| 206 | * specification, since the tables are provided by the system BIOS | ||
| 207 | */ | ||
| 208 | #pragma pack(1) | ||
| 209 | |||
| 210 | struct cper_record_header { | ||
| 211 | char signature[CPER_SIG_SIZE]; /* must be CPER_SIG_RECORD */ | ||
| 212 | __u16 revision; /* must be CPER_RECORD_REV */ | ||
| 213 | __u32 signature_end; /* must be CPER_SIG_END */ | ||
| 214 | __u16 section_count; | ||
| 215 | __u32 error_severity; | ||
| 216 | __u32 validation_bits; | ||
| 217 | __u32 record_length; | ||
| 218 | __u64 timestamp; | ||
| 219 | uuid_le platform_id; | ||
| 220 | uuid_le partition_id; | ||
| 221 | uuid_le creator_id; | ||
| 222 | uuid_le notification_type; | ||
| 223 | __u64 record_id; | ||
| 224 | __u32 flags; | ||
| 225 | __u64 persistence_information; | ||
| 226 | __u8 reserved[12]; /* must be zero */ | ||
| 227 | }; | ||
| 228 | |||
| 229 | struct cper_section_descriptor { | ||
| 230 | __u32 section_offset; /* Offset in bytes of the | ||
| 231 | * section body from the base | ||
| 232 | * of the record header */ | ||
| 233 | __u32 section_length; | ||
| 234 | __u16 revision; /* must be CPER_RECORD_REV */ | ||
| 235 | __u8 validation_bits; | ||
| 236 | __u8 reserved; /* must be zero */ | ||
| 237 | __u32 flags; | ||
| 238 | uuid_le section_type; | ||
| 239 | uuid_le fru_id; | ||
| 240 | __u32 section_severity; | ||
| 241 | __u8 fru_text[20]; | ||
| 242 | }; | ||
| 243 | |||
| 244 | /* Generic Processor Error Section */ | ||
| 245 | struct cper_sec_proc_generic { | ||
| 246 | __u64 validation_bits; | ||
| 247 | __u8 proc_type; | ||
| 248 | __u8 proc_isa; | ||
| 249 | __u8 proc_error_type; | ||
| 250 | __u8 operation; | ||
| 251 | __u8 flags; | ||
| 252 | __u8 level; | ||
| 253 | __u16 reserved; | ||
| 254 | __u64 cpu_version; | ||
| 255 | char cpu_brand[128]; | ||
| 256 | __u64 proc_id; | ||
| 257 | __u64 target_addr; | ||
| 258 | __u64 requestor_id; | ||
| 259 | __u64 responder_id; | ||
| 260 | __u64 ip; | ||
| 261 | }; | ||
| 262 | |||
| 263 | /* IA32/X64 Processor Error Section */ | ||
| 264 | struct cper_sec_proc_ia { | ||
| 265 | __u64 validation_bits; | ||
| 266 | __u8 lapic_id; | ||
| 267 | __u8 cpuid[48]; | ||
| 268 | }; | ||
| 269 | |||
| 270 | /* IA32/X64 Processor Error Infomation Structure */ | ||
| 271 | struct cper_ia_err_info { | ||
| 272 | uuid_le err_type; | ||
| 273 | __u64 validation_bits; | ||
| 274 | __u64 check_info; | ||
| 275 | __u64 target_id; | ||
| 276 | __u64 requestor_id; | ||
| 277 | __u64 responder_id; | ||
| 278 | __u64 ip; | ||
| 279 | }; | ||
| 280 | |||
| 281 | /* IA32/X64 Processor Context Information Structure */ | ||
| 282 | struct cper_ia_proc_ctx { | ||
| 283 | __u16 reg_ctx_type; | ||
| 284 | __u16 reg_arr_size; | ||
| 285 | __u32 msr_addr; | ||
| 286 | __u64 mm_reg_addr; | ||
| 287 | }; | ||
| 288 | |||
| 289 | /* Memory Error Section */ | ||
| 290 | struct cper_sec_mem_err { | ||
| 291 | __u64 validation_bits; | ||
| 292 | __u64 error_status; | ||
| 293 | __u64 physical_addr; | ||
| 294 | __u64 physical_addr_mask; | ||
| 295 | __u16 node; | ||
| 296 | __u16 card; | ||
| 297 | __u16 module; | ||
| 298 | __u16 bank; | ||
| 299 | __u16 device; | ||
| 300 | __u16 row; | ||
| 301 | __u16 column; | ||
| 302 | __u16 bit_pos; | ||
| 303 | __u64 requestor_id; | ||
| 304 | __u64 responder_id; | ||
| 305 | __u64 target_id; | ||
| 306 | __u8 error_type; | ||
| 307 | }; | ||
| 308 | |||
| 309 | /* Reset to default packing */ | ||
| 310 | #pragma pack() | ||
| 311 | |||
| 312 | u64 cper_next_record_id(void); | ||
| 313 | |||
| 314 | #endif | ||
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index fc1b930f246c..e7d9b20ddc5b 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h | |||
| @@ -63,6 +63,8 @@ struct dentry *debugfs_create_x16(const char *name, mode_t mode, | |||
| 63 | struct dentry *parent, u16 *value); | 63 | struct dentry *parent, u16 *value); |
| 64 | struct dentry *debugfs_create_x32(const char *name, mode_t mode, | 64 | struct dentry *debugfs_create_x32(const char *name, mode_t mode, |
| 65 | struct dentry *parent, u32 *value); | 65 | struct dentry *parent, u32 *value); |
| 66 | struct dentry *debugfs_create_x64(const char *name, mode_t mode, | ||
| 67 | struct dentry *parent, u64 *value); | ||
| 66 | struct dentry *debugfs_create_size_t(const char *name, mode_t mode, | 68 | struct dentry *debugfs_create_size_t(const char *name, mode_t mode, |
| 67 | struct dentry *parent, size_t *value); | 69 | struct dentry *parent, size_t *value); |
| 68 | struct dentry *debugfs_create_bool(const char *name, mode_t mode, | 70 | struct dentry *debugfs_create_bool(const char *name, mode_t mode, |
diff --git a/include/linux/pci.h b/include/linux/pci.h index a788fa12ff31..63967e877f20 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h | |||
| @@ -311,7 +311,8 @@ struct pci_dev { | |||
| 311 | unsigned int is_virtfn:1; | 311 | unsigned int is_virtfn:1; |
| 312 | unsigned int reset_fn:1; | 312 | unsigned int reset_fn:1; |
| 313 | unsigned int is_hotplug_bridge:1; | 313 | unsigned int is_hotplug_bridge:1; |
| 314 | unsigned int aer_firmware_first:1; | 314 | unsigned int __aer_firmware_first_valid:1; |
| 315 | unsigned int __aer_firmware_first:1; | ||
| 315 | pci_dev_flags_t dev_flags; | 316 | pci_dev_flags_t dev_flags; |
| 316 | atomic_t enable_cnt; /* pci_enable_device has been called */ | 317 | atomic_t enable_cnt; /* pci_enable_device has been called */ |
| 317 | 318 | ||
diff --git a/include/linux/uuid.h b/include/linux/uuid.h new file mode 100644 index 000000000000..5b7efbfcee4e --- /dev/null +++ b/include/linux/uuid.h | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | /* | ||
| 2 | * UUID/GUID definition | ||
| 3 | * | ||
| 4 | * Copyright (C) 2010, Intel Corp. | ||
| 5 | * Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or | ||
| 8 | * modify it under the terms of the GNU General Public License version | ||
| 9 | * 2 as published by the Free Software Foundation; | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | #ifndef _LINUX_UUID_H_ | ||
| 22 | #define _LINUX_UUID_H_ | ||
| 23 | |||
| 24 | #include <linux/types.h> | ||
| 25 | #include <linux/string.h> | ||
| 26 | |||
| 27 | typedef struct { | ||
| 28 | __u8 b[16]; | ||
| 29 | } uuid_le; | ||
| 30 | |||
| 31 | typedef struct { | ||
| 32 | __u8 b[16]; | ||
| 33 | } uuid_be; | ||
| 34 | |||
| 35 | #define UUID_LE(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \ | ||
| 36 | ((uuid_le) \ | ||
| 37 | {{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \ | ||
| 38 | (b) & 0xff, ((b) >> 8) & 0xff, \ | ||
| 39 | (c) & 0xff, ((c) >> 8) & 0xff, \ | ||
| 40 | (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }}) | ||
| 41 | |||
| 42 | #define UUID_BE(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \ | ||
| 43 | ((uuid_be) \ | ||
| 44 | {{ ((a) >> 24) & 0xff, ((a) >> 16) & 0xff, ((a) >> 8) & 0xff, (a) & 0xff, \ | ||
| 45 | ((b) >> 8) & 0xff, (b) & 0xff, \ | ||
| 46 | ((c) >> 8) & 0xff, (c) & 0xff, \ | ||
| 47 | (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }}) | ||
| 48 | |||
| 49 | #define NULL_UUID_LE \ | ||
| 50 | UUID_LE(0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, \ | ||
| 51 | 0x00, 0x00, 0x00, 0x00) | ||
| 52 | |||
| 53 | #define NULL_UUID_BE \ | ||
| 54 | UUID_BE(0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, \ | ||
| 55 | 0x00, 0x00, 0x00, 0x00) | ||
| 56 | |||
| 57 | static inline int uuid_le_cmp(const uuid_le u1, const uuid_le u2) | ||
| 58 | { | ||
| 59 | return memcmp(&u1, &u2, sizeof(uuid_le)); | ||
| 60 | } | ||
| 61 | |||
| 62 | static inline int uuid_be_cmp(const uuid_be u1, const uuid_be u2) | ||
| 63 | { | ||
| 64 | return memcmp(&u1, &u2, sizeof(uuid_be)); | ||
| 65 | } | ||
| 66 | |||
| 67 | extern void uuid_le_gen(uuid_le *u); | ||
| 68 | extern void uuid_be_gen(uuid_be *u); | ||
| 69 | |||
| 70 | #endif | ||
diff --git a/lib/Makefile b/lib/Makefile index 0d4015205c64..f3eb6e8766be 100644 --- a/lib/Makefile +++ b/lib/Makefile | |||
| @@ -21,7 +21,7 @@ lib-y += kobject.o kref.o klist.o | |||
| 21 | 21 | ||
| 22 | obj-y += bcd.o div64.o sort.o parser.o halfmd4.o debug_locks.o random32.o \ | 22 | obj-y += bcd.o div64.o sort.o parser.o halfmd4.o debug_locks.o random32.o \ |
| 23 | bust_spinlocks.o hexdump.o kasprintf.o bitmap.o scatterlist.o \ | 23 | bust_spinlocks.o hexdump.o kasprintf.o bitmap.o scatterlist.o \ |
| 24 | string_helpers.o gcd.o lcm.o list_sort.o | 24 | string_helpers.o gcd.o lcm.o list_sort.o uuid.o |
| 25 | 25 | ||
| 26 | ifeq ($(CONFIG_DEBUG_KOBJECT),y) | 26 | ifeq ($(CONFIG_DEBUG_KOBJECT),y) |
| 27 | CFLAGS_kobject.o += -DDEBUG | 27 | CFLAGS_kobject.o += -DDEBUG |
diff --git a/lib/uuid.c b/lib/uuid.c new file mode 100644 index 000000000000..8fadd7cef46c --- /dev/null +++ b/lib/uuid.c | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /* | ||
| 2 | * Unified UUID/GUID definition | ||
| 3 | * | ||
| 4 | * Copyright (C) 2009, Intel Corp. | ||
| 5 | * Huang Ying <ying.huang@intel.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or | ||
| 8 | * modify it under the terms of the GNU General Public License version | ||
| 9 | * 2 as published by the Free Software Foundation; | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include <linux/kernel.h> | ||
| 22 | #include <linux/module.h> | ||
| 23 | #include <linux/uuid.h> | ||
| 24 | #include <linux/random.h> | ||
| 25 | |||
| 26 | static void __uuid_gen_common(__u8 b[16]) | ||
| 27 | { | ||
| 28 | int i; | ||
| 29 | u32 r; | ||
| 30 | |||
| 31 | for (i = 0; i < 4; i++) { | ||
| 32 | r = random32(); | ||
| 33 | memcpy(b + i * 4, &r, 4); | ||
| 34 | } | ||
| 35 | /* reversion 0b10 */ | ||
| 36 | b[8] = (b[8] & 0x3F) | 0x80; | ||
| 37 | } | ||
| 38 | |||
| 39 | void uuid_le_gen(uuid_le *lu) | ||
| 40 | { | ||
| 41 | __uuid_gen_common(lu->b); | ||
| 42 | /* version 4 : random generation */ | ||
| 43 | lu->b[7] = (lu->b[7] & 0x0F) | 0x40; | ||
| 44 | } | ||
| 45 | EXPORT_SYMBOL_GPL(uuid_le_gen); | ||
| 46 | |||
| 47 | void uuid_be_gen(uuid_be *bu) | ||
| 48 | { | ||
| 49 | __uuid_gen_common(bu->b); | ||
| 50 | /* version 4 : random generation */ | ||
| 51 | bu->b[6] = (bu->b[6] & 0x0F) | 0x40; | ||
| 52 | } | ||
| 53 | EXPORT_SYMBOL_GPL(uuid_be_gen); | ||
