Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/MCE/AMD: Add support for new MCA_SYND{1,2} registers

Starting with Zen4, AMD's Scalable MCA systems incorporate two new registers:
MCA_SYND1 and MCA_SYND2.

These registers will include supplemental error information in addition to the
existing MCA_SYND register. The data within these registers is considered
valid if MCA_STATUS[SyndV] is set.

Userspace error decoding tools like rasdaemon gather related hardware error
information through the tracepoints.

Therefore, export these two registers through the mce_record tracepoint so
that tools like rasdaemon can parse them and output the supplemental error
information like FRU text contained in them.

[ bp: Massage. ]

Signed-off-by: Yazen Ghannam <yazen.ghannam@amd.com>
Signed-off-by: Avadhut Naik <avadhut.naik@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Qiuxu Zhuo <qiuxu.zhuo@intel.com>
Link: https://lore.kernel.org/r/20241022194158.110073-4-avadhut.naik@amd.com

authored by

Avadhut Naik and committed by
Borislav Petkov (AMD)
d4fca135 e52750fb

+46 -7
+21
arch/x86/include/asm/mce.h
··· 122 122 #define MSR_AMD64_SMCA_MC0_DESTAT 0xc0002008 123 123 #define MSR_AMD64_SMCA_MC0_DEADDR 0xc0002009 124 124 #define MSR_AMD64_SMCA_MC0_MISC1 0xc000200a 125 + /* Registers MISC2 to MISC4 are at offsets B to D. */ 126 + #define MSR_AMD64_SMCA_MC0_SYND1 0xc000200e 127 + #define MSR_AMD64_SMCA_MC0_SYND2 0xc000200f 125 128 #define MSR_AMD64_SMCA_MCx_CTL(x) (MSR_AMD64_SMCA_MC0_CTL + 0x10*(x)) 126 129 #define MSR_AMD64_SMCA_MCx_STATUS(x) (MSR_AMD64_SMCA_MC0_STATUS + 0x10*(x)) 127 130 #define MSR_AMD64_SMCA_MCx_ADDR(x) (MSR_AMD64_SMCA_MC0_ADDR + 0x10*(x)) ··· 135 132 #define MSR_AMD64_SMCA_MCx_DESTAT(x) (MSR_AMD64_SMCA_MC0_DESTAT + 0x10*(x)) 136 133 #define MSR_AMD64_SMCA_MCx_DEADDR(x) (MSR_AMD64_SMCA_MC0_DEADDR + 0x10*(x)) 137 134 #define MSR_AMD64_SMCA_MCx_MISCy(x, y) ((MSR_AMD64_SMCA_MC0_MISC1 + y) + (0x10*(x))) 135 + #define MSR_AMD64_SMCA_MCx_SYND1(x) (MSR_AMD64_SMCA_MC0_SYND1 + 0x10*(x)) 136 + #define MSR_AMD64_SMCA_MCx_SYND2(x) (MSR_AMD64_SMCA_MC0_SYND2 + 0x10*(x)) 138 137 139 138 #define XEC(x, mask) (((x) >> 16) & mask) 140 139 ··· 195 190 /** 196 191 * struct mce_hw_err - Hardware Error Record. 197 192 * @m: Machine Check record. 193 + * @vendor: Vendor-specific error information. 194 + * 195 + * Vendor-specific fields should not be added to struct mce. Instead, vendors 196 + * should export their vendor-specific data through their structure in the 197 + * vendor union below. 198 + * 199 + * AMD's vendor data is parsed by error decoding tools for supplemental error 200 + * information. Thus, current offsets of existing fields must be maintained. 201 + * Only add new fields at the end of AMD's vendor structure. 198 202 */ 199 203 struct mce_hw_err { 200 204 struct mce m; 205 + 206 + union vendor_info { 207 + struct { 208 + u64 synd1; /* MCA_SYND1 MSR */ 209 + u64 synd2; /* MCA_SYND2 MSR */ 210 + } amd; 211 + } vendor; 201 212 }; 202 213 203 214 #define to_mce_hw_err(mce) container_of(mce, struct mce_hw_err, m)
+2 -1
arch/x86/include/uapi/asm/mce.h
··· 8 8 /* 9 9 * Fields are zero when not available. Also, this struct is shared with 10 10 * userspace mcelog and thus must keep existing fields at current offsets. 11 - * Only add new fields to the end of the structure 11 + * Only add new, shared fields to the end of the structure. 12 + * Do not add vendor-specific fields. 12 13 */ 13 14 struct mce { 14 15 __u64 status; /* Bank's MCi_STATUS MSR */
+4 -1
arch/x86/kernel/cpu/mce/amd.c
··· 797 797 if (mce_flags.smca) { 798 798 rdmsrl(MSR_AMD64_SMCA_MCx_IPID(bank), m->ipid); 799 799 800 - if (m->status & MCI_STATUS_SYNDV) 800 + if (m->status & MCI_STATUS_SYNDV) { 801 801 rdmsrl(MSR_AMD64_SMCA_MCx_SYND(bank), m->synd); 802 + rdmsrl(MSR_AMD64_SMCA_MCx_SYND1(bank), err.vendor.amd.synd1); 803 + rdmsrl(MSR_AMD64_SMCA_MCx_SYND2(bank), err.vendor.amd.synd2); 804 + } 802 805 } 803 806 804 807 mce_log(&err);
+8 -1
arch/x86/kernel/cpu/mce/core.c
··· 202 202 if (mce_flags.smca) { 203 203 if (m->synd) 204 204 pr_cont("SYND %llx ", m->synd); 205 + if (err->vendor.amd.synd1) 206 + pr_cont("SYND1 %llx ", err->vendor.amd.synd1); 207 + if (err->vendor.amd.synd2) 208 + pr_cont("SYND2 %llx ", err->vendor.amd.synd2); 205 209 if (m->ipid) 206 210 pr_cont("IPID %llx ", m->ipid); 207 211 } ··· 682 678 if (mce_flags.smca) { 683 679 m->ipid = mce_rdmsrl(MSR_AMD64_SMCA_MCx_IPID(i)); 684 680 685 - if (m->status & MCI_STATUS_SYNDV) 681 + if (m->status & MCI_STATUS_SYNDV) { 686 682 m->synd = mce_rdmsrl(MSR_AMD64_SMCA_MCx_SYND(i)); 683 + err->vendor.amd.synd1 = mce_rdmsrl(MSR_AMD64_SMCA_MCx_SYND1(i)); 684 + err->vendor.amd.synd2 = mce_rdmsrl(MSR_AMD64_SMCA_MCx_SYND2(i)); 685 + } 687 686 } 688 687 } 689 688
+6 -2
drivers/edac/mce_amd.c
··· 793 793 amd_decode_mce(struct notifier_block *nb, unsigned long val, void *data) 794 794 { 795 795 struct mce *m = (struct mce *)data; 796 + struct mce_hw_err *err = to_mce_hw_err(m); 796 797 unsigned int fam = x86_family(m->cpuid); 797 798 int ecc; 798 799 ··· 851 850 if (boot_cpu_has(X86_FEATURE_SMCA)) { 852 851 pr_emerg(HW_ERR "IPID: 0x%016llx", m->ipid); 853 852 854 - if (m->status & MCI_STATUS_SYNDV) 855 - pr_cont(", Syndrome: 0x%016llx", m->synd); 853 + if (m->status & MCI_STATUS_SYNDV) { 854 + pr_cont(", Syndrome: 0x%016llx\n", m->synd); 855 + pr_emerg(HW_ERR "Syndrome1: 0x%016llx, Syndrome2: 0x%016llx", 856 + err->vendor.amd.synd1, err->vendor.amd.synd2); 857 + } 856 858 857 859 pr_cont("\n"); 858 860
+5 -2
include/trace/events/mce.h
··· 43 43 __field( u8, bank ) 44 44 __field( u8, cpuvendor ) 45 45 __field( u32, microcode ) 46 + __dynamic_array(u8, v_data, sizeof(err->vendor)) 46 47 ), 47 48 48 49 TP_fast_assign( ··· 66 65 __entry->bank = err->m.bank; 67 66 __entry->cpuvendor = err->m.cpuvendor; 68 67 __entry->microcode = err->m.microcode; 68 + memcpy(__get_dynamic_array(v_data), &err->vendor, sizeof(err->vendor)); 69 69 ), 70 70 71 - TP_printk("CPU: %d, MCGc/s: %llx/%llx, MC%d: %016Lx, IPID: %016Lx, ADDR: %016Lx, MISC: %016Lx, SYND: %016Lx, RIP: %02x:<%016Lx>, TSC: %llx, PPIN: %llx, vendor: %u, CPUID: %x, time: %llu, socket: %u, APIC: %x, microcode: %x", 71 + TP_printk("CPU: %d, MCGc/s: %llx/%llx, MC%d: %016llx, IPID: %016llx, ADDR: %016llx, MISC: %016llx, SYND: %016llx, RIP: %02x:<%016llx>, TSC: %llx, PPIN: %llx, vendor: %u, CPUID: %x, time: %llu, socket: %u, APIC: %x, microcode: %x, vendor data: %s", 72 72 __entry->cpu, 73 73 __entry->mcgcap, __entry->mcgstatus, 74 74 __entry->bank, __entry->status, ··· 85 83 __entry->walltime, 86 84 __entry->socketid, 87 85 __entry->apicid, 88 - __entry->microcode) 86 + __entry->microcode, 87 + __print_dynamic_array(v_data, sizeof(u8))) 89 88 ); 90 89 91 90 #endif /* _TRACE_MCE_H */