]>
Commit | Line | Data |
---|---|---|
d334a491 HY |
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 | * | |
482908b4 HY |
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 | * | |
d334a491 HY |
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); | |
482908b4 HY |
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 | } |