xref: /linux/drivers/edac/a72_edac.c (revision 03f76ddff5b04a808ae16c06418460151e2fdd4b)
1*fb13ae06SSascha Hauer // SPDX-License-Identifier: GPL-2.0
2*fb13ae06SSascha Hauer /*
3*fb13ae06SSascha Hauer  * Cortex A72 EDAC L1 and L2 cache error detection
4*fb13ae06SSascha Hauer  *
5*fb13ae06SSascha Hauer  * Copyright (c) 2020 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
6*fb13ae06SSascha Hauer  * Copyright (c) 2025 Microsoft Corporation, <vijayb@linux.microsoft.com>
7*fb13ae06SSascha Hauer  *
8*fb13ae06SSascha Hauer  * Based on Code from:
9*fb13ae06SSascha Hauer  * Copyright (c) 2018, NXP Semiconductor
10*fb13ae06SSascha Hauer  * Author: York Sun <york.sun@nxp.com>
11*fb13ae06SSascha Hauer  */
12*fb13ae06SSascha Hauer 
13*fb13ae06SSascha Hauer #include <linux/module.h>
14*fb13ae06SSascha Hauer #include <linux/of.h>
15*fb13ae06SSascha Hauer #include <linux/bitfield.h>
16*fb13ae06SSascha Hauer #include <asm/smp_plat.h>
17*fb13ae06SSascha Hauer 
18*fb13ae06SSascha Hauer #include "edac_module.h"
19*fb13ae06SSascha Hauer 
20*fb13ae06SSascha Hauer #define DRVNAME			"a72-edac"
21*fb13ae06SSascha Hauer 
22*fb13ae06SSascha Hauer #define SYS_CPUMERRSR_EL1	sys_reg(3, 1, 15, 2, 2)
23*fb13ae06SSascha Hauer #define SYS_L2MERRSR_EL1	sys_reg(3, 1, 15, 2, 3)
24*fb13ae06SSascha Hauer 
25*fb13ae06SSascha Hauer #define CPUMERRSR_EL1_RAMID	GENMASK(30, 24)
26*fb13ae06SSascha Hauer #define L2MERRSR_EL1_CPUID_WAY	GENMASK(21, 18)
27*fb13ae06SSascha Hauer 
28*fb13ae06SSascha Hauer #define CPUMERRSR_EL1_VALID	BIT(31)
29*fb13ae06SSascha Hauer #define CPUMERRSR_EL1_FATAL	BIT(63)
30*fb13ae06SSascha Hauer #define L2MERRSR_EL1_VALID	BIT(31)
31*fb13ae06SSascha Hauer #define L2MERRSR_EL1_FATAL	BIT(63)
32*fb13ae06SSascha Hauer 
33*fb13ae06SSascha Hauer #define L1_I_TAG_RAM		0x00
34*fb13ae06SSascha Hauer #define L1_I_DATA_RAM		0x01
35*fb13ae06SSascha Hauer #define L1_D_TAG_RAM		0x08
36*fb13ae06SSascha Hauer #define L1_D_DATA_RAM		0x09
37*fb13ae06SSascha Hauer #define TLB_RAM			0x18
38*fb13ae06SSascha Hauer 
39*fb13ae06SSascha Hauer #define MESSAGE_SIZE		64
40*fb13ae06SSascha Hauer 
41*fb13ae06SSascha Hauer struct mem_err_synd_reg {
42*fb13ae06SSascha Hauer 	u64 cpu_mesr;
43*fb13ae06SSascha Hauer 	u64 l2_mesr;
44*fb13ae06SSascha Hauer };
45*fb13ae06SSascha Hauer 
46*fb13ae06SSascha Hauer static struct cpumask compat_mask;
47*fb13ae06SSascha Hauer 
report_errors(struct edac_device_ctl_info * edac_ctl,int cpu,struct mem_err_synd_reg * mesr)48*fb13ae06SSascha Hauer static void report_errors(struct edac_device_ctl_info *edac_ctl, int cpu,
49*fb13ae06SSascha Hauer 			  struct mem_err_synd_reg *mesr)
50*fb13ae06SSascha Hauer {
51*fb13ae06SSascha Hauer 	u64 cpu_mesr = mesr->cpu_mesr;
52*fb13ae06SSascha Hauer 	u64 l2_mesr = mesr->l2_mesr;
53*fb13ae06SSascha Hauer 	char msg[MESSAGE_SIZE];
54*fb13ae06SSascha Hauer 
55*fb13ae06SSascha Hauer 	if (cpu_mesr & CPUMERRSR_EL1_VALID) {
56*fb13ae06SSascha Hauer 		const char *str;
57*fb13ae06SSascha Hauer 		bool fatal = cpu_mesr & CPUMERRSR_EL1_FATAL;
58*fb13ae06SSascha Hauer 
59*fb13ae06SSascha Hauer 		switch (FIELD_GET(CPUMERRSR_EL1_RAMID, cpu_mesr)) {
60*fb13ae06SSascha Hauer 		case L1_I_TAG_RAM:
61*fb13ae06SSascha Hauer 			str = "L1-I Tag RAM";
62*fb13ae06SSascha Hauer 			break;
63*fb13ae06SSascha Hauer 		case L1_I_DATA_RAM:
64*fb13ae06SSascha Hauer 			str = "L1-I Data RAM";
65*fb13ae06SSascha Hauer 			break;
66*fb13ae06SSascha Hauer 		case L1_D_TAG_RAM:
67*fb13ae06SSascha Hauer 			str = "L1-D Tag RAM";
68*fb13ae06SSascha Hauer 			break;
69*fb13ae06SSascha Hauer 		case L1_D_DATA_RAM:
70*fb13ae06SSascha Hauer 			str = "L1-D Data RAM";
71*fb13ae06SSascha Hauer 			break;
72*fb13ae06SSascha Hauer 		case TLB_RAM:
73*fb13ae06SSascha Hauer 			str = "TLB RAM";
74*fb13ae06SSascha Hauer 			break;
75*fb13ae06SSascha Hauer 		default:
76*fb13ae06SSascha Hauer 			str = "Unspecified";
77*fb13ae06SSascha Hauer 			break;
78*fb13ae06SSascha Hauer 		}
79*fb13ae06SSascha Hauer 
80*fb13ae06SSascha Hauer 		snprintf(msg, MESSAGE_SIZE, "%s %s error(s) on CPU %d",
81*fb13ae06SSascha Hauer 			 str, fatal ? "fatal" : "correctable", cpu);
82*fb13ae06SSascha Hauer 
83*fb13ae06SSascha Hauer 		if (fatal)
84*fb13ae06SSascha Hauer 			edac_device_handle_ue(edac_ctl, cpu, 0, msg);
85*fb13ae06SSascha Hauer 		else
86*fb13ae06SSascha Hauer 			edac_device_handle_ce(edac_ctl, cpu, 0, msg);
87*fb13ae06SSascha Hauer 	}
88*fb13ae06SSascha Hauer 
89*fb13ae06SSascha Hauer 	if (l2_mesr & L2MERRSR_EL1_VALID) {
90*fb13ae06SSascha Hauer 		bool fatal = l2_mesr & L2MERRSR_EL1_FATAL;
91*fb13ae06SSascha Hauer 
92*fb13ae06SSascha Hauer 		snprintf(msg, MESSAGE_SIZE, "L2 %s error(s) on CPU %d CPUID/WAY 0x%lx",
93*fb13ae06SSascha Hauer 			 fatal ? "fatal" : "correctable", cpu,
94*fb13ae06SSascha Hauer 			 FIELD_GET(L2MERRSR_EL1_CPUID_WAY, l2_mesr));
95*fb13ae06SSascha Hauer 		if (fatal)
96*fb13ae06SSascha Hauer 			edac_device_handle_ue(edac_ctl, cpu, 1, msg);
97*fb13ae06SSascha Hauer 		else
98*fb13ae06SSascha Hauer 			edac_device_handle_ce(edac_ctl, cpu, 1, msg);
99*fb13ae06SSascha Hauer 	}
100*fb13ae06SSascha Hauer }
101*fb13ae06SSascha Hauer 
read_errors(void * data)102*fb13ae06SSascha Hauer static void read_errors(void *data)
103*fb13ae06SSascha Hauer {
104*fb13ae06SSascha Hauer 	struct mem_err_synd_reg *mesr = data;
105*fb13ae06SSascha Hauer 
106*fb13ae06SSascha Hauer 	mesr->cpu_mesr = read_sysreg_s(SYS_CPUMERRSR_EL1);
107*fb13ae06SSascha Hauer 	if (mesr->cpu_mesr & CPUMERRSR_EL1_VALID) {
108*fb13ae06SSascha Hauer 		write_sysreg_s(0, SYS_CPUMERRSR_EL1);
109*fb13ae06SSascha Hauer 		isb();
110*fb13ae06SSascha Hauer 	}
111*fb13ae06SSascha Hauer 	mesr->l2_mesr = read_sysreg_s(SYS_L2MERRSR_EL1);
112*fb13ae06SSascha Hauer 	if (mesr->l2_mesr & L2MERRSR_EL1_VALID) {
113*fb13ae06SSascha Hauer 		write_sysreg_s(0, SYS_L2MERRSR_EL1);
114*fb13ae06SSascha Hauer 		isb();
115*fb13ae06SSascha Hauer 	}
116*fb13ae06SSascha Hauer }
117*fb13ae06SSascha Hauer 
a72_edac_check(struct edac_device_ctl_info * edac_ctl)118*fb13ae06SSascha Hauer static void a72_edac_check(struct edac_device_ctl_info *edac_ctl)
119*fb13ae06SSascha Hauer {
120*fb13ae06SSascha Hauer 	struct mem_err_synd_reg mesr;
121*fb13ae06SSascha Hauer 	int cpu;
122*fb13ae06SSascha Hauer 
123*fb13ae06SSascha Hauer 	cpus_read_lock();
124*fb13ae06SSascha Hauer 	for_each_cpu_and(cpu, cpu_online_mask, &compat_mask) {
125*fb13ae06SSascha Hauer 		smp_call_function_single(cpu, read_errors, &mesr, true);
126*fb13ae06SSascha Hauer 		report_errors(edac_ctl, cpu, &mesr);
127*fb13ae06SSascha Hauer 	}
128*fb13ae06SSascha Hauer 	cpus_read_unlock();
129*fb13ae06SSascha Hauer }
130*fb13ae06SSascha Hauer 
a72_edac_probe(struct platform_device * pdev)131*fb13ae06SSascha Hauer static int a72_edac_probe(struct platform_device *pdev)
132*fb13ae06SSascha Hauer {
133*fb13ae06SSascha Hauer 	struct edac_device_ctl_info *edac_ctl;
134*fb13ae06SSascha Hauer 	struct device *dev = &pdev->dev;
135*fb13ae06SSascha Hauer 	int rc;
136*fb13ae06SSascha Hauer 
137*fb13ae06SSascha Hauer 	edac_ctl = edac_device_alloc_ctl_info(0, "cpu",
138*fb13ae06SSascha Hauer 					      num_possible_cpus(), "L", 2, 1,
139*fb13ae06SSascha Hauer 					      edac_device_alloc_index());
140*fb13ae06SSascha Hauer 	if (!edac_ctl)
141*fb13ae06SSascha Hauer 		return -ENOMEM;
142*fb13ae06SSascha Hauer 
143*fb13ae06SSascha Hauer 	edac_ctl->edac_check = a72_edac_check;
144*fb13ae06SSascha Hauer 	edac_ctl->dev = dev;
145*fb13ae06SSascha Hauer 	edac_ctl->mod_name = dev_name(dev);
146*fb13ae06SSascha Hauer 	edac_ctl->dev_name = dev_name(dev);
147*fb13ae06SSascha Hauer 	edac_ctl->ctl_name = DRVNAME;
148*fb13ae06SSascha Hauer 	dev_set_drvdata(dev, edac_ctl);
149*fb13ae06SSascha Hauer 
150*fb13ae06SSascha Hauer 	rc = edac_device_add_device(edac_ctl);
151*fb13ae06SSascha Hauer 	if (rc)
152*fb13ae06SSascha Hauer 		goto out_dev;
153*fb13ae06SSascha Hauer 
154*fb13ae06SSascha Hauer 	return 0;
155*fb13ae06SSascha Hauer 
156*fb13ae06SSascha Hauer out_dev:
157*fb13ae06SSascha Hauer 	edac_device_free_ctl_info(edac_ctl);
158*fb13ae06SSascha Hauer 
159*fb13ae06SSascha Hauer 	return rc;
160*fb13ae06SSascha Hauer }
161*fb13ae06SSascha Hauer 
a72_edac_remove(struct platform_device * pdev)162*fb13ae06SSascha Hauer static void a72_edac_remove(struct platform_device *pdev)
163*fb13ae06SSascha Hauer {
164*fb13ae06SSascha Hauer 	struct edac_device_ctl_info *edac_ctl = dev_get_drvdata(&pdev->dev);
165*fb13ae06SSascha Hauer 
166*fb13ae06SSascha Hauer 	edac_device_del_device(edac_ctl->dev);
167*fb13ae06SSascha Hauer 	edac_device_free_ctl_info(edac_ctl);
168*fb13ae06SSascha Hauer }
169*fb13ae06SSascha Hauer 
170*fb13ae06SSascha Hauer static const struct of_device_id cortex_arm64_edac_of_match[] = {
171*fb13ae06SSascha Hauer 	{ .compatible = "arm,cortex-a72" },
172*fb13ae06SSascha Hauer 	{}
173*fb13ae06SSascha Hauer };
174*fb13ae06SSascha Hauer MODULE_DEVICE_TABLE(of, cortex_arm64_edac_of_match);
175*fb13ae06SSascha Hauer 
176*fb13ae06SSascha Hauer static struct platform_driver a72_edac_driver = {
177*fb13ae06SSascha Hauer 	.probe = a72_edac_probe,
178*fb13ae06SSascha Hauer 	.remove = a72_edac_remove,
179*fb13ae06SSascha Hauer 	.driver = {
180*fb13ae06SSascha Hauer 		.name = DRVNAME,
181*fb13ae06SSascha Hauer 	},
182*fb13ae06SSascha Hauer };
183*fb13ae06SSascha Hauer 
184*fb13ae06SSascha Hauer static struct platform_device *a72_pdev;
185*fb13ae06SSascha Hauer 
a72_edac_driver_init(void)186*fb13ae06SSascha Hauer static int __init a72_edac_driver_init(void)
187*fb13ae06SSascha Hauer {
188*fb13ae06SSascha Hauer 	int cpu;
189*fb13ae06SSascha Hauer 
190*fb13ae06SSascha Hauer 	for_each_possible_cpu(cpu) {
191*fb13ae06SSascha Hauer 		struct device_node *np __free(device_node) = of_cpu_device_node_get(cpu);
192*fb13ae06SSascha Hauer 		if (np) {
193*fb13ae06SSascha Hauer 			if (of_match_node(cortex_arm64_edac_of_match, np) &&
194*fb13ae06SSascha Hauer 			    of_property_read_bool(np, "edac-enabled")) {
195*fb13ae06SSascha Hauer 				cpumask_set_cpu(cpu, &compat_mask);
196*fb13ae06SSascha Hauer 			}
197*fb13ae06SSascha Hauer 		} else {
198*fb13ae06SSascha Hauer 			pr_warn("failed to find device node for CPU %d\n", cpu);
199*fb13ae06SSascha Hauer 		}
200*fb13ae06SSascha Hauer 	}
201*fb13ae06SSascha Hauer 
202*fb13ae06SSascha Hauer 	if (cpumask_empty(&compat_mask))
203*fb13ae06SSascha Hauer 		return 0;
204*fb13ae06SSascha Hauer 
205*fb13ae06SSascha Hauer 	a72_pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
206*fb13ae06SSascha Hauer 	if (IS_ERR(a72_pdev)) {
207*fb13ae06SSascha Hauer 		pr_err("failed to register A72 EDAC device\n");
208*fb13ae06SSascha Hauer 		return PTR_ERR(a72_pdev);
209*fb13ae06SSascha Hauer 	}
210*fb13ae06SSascha Hauer 
211*fb13ae06SSascha Hauer 	return platform_driver_register(&a72_edac_driver);
212*fb13ae06SSascha Hauer }
213*fb13ae06SSascha Hauer 
a72_edac_driver_exit(void)214*fb13ae06SSascha Hauer static void __exit a72_edac_driver_exit(void)
215*fb13ae06SSascha Hauer {
216*fb13ae06SSascha Hauer 	platform_device_unregister(a72_pdev);
217*fb13ae06SSascha Hauer 	platform_driver_unregister(&a72_edac_driver);
218*fb13ae06SSascha Hauer }
219*fb13ae06SSascha Hauer 
220*fb13ae06SSascha Hauer module_init(a72_edac_driver_init);
221*fb13ae06SSascha Hauer module_exit(a72_edac_driver_exit);
222*fb13ae06SSascha Hauer 
223*fb13ae06SSascha Hauer MODULE_LICENSE("GPL");
224*fb13ae06SSascha Hauer MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
225*fb13ae06SSascha Hauer MODULE_DESCRIPTION("Cortex A72 L1 and L2 cache EDAC driver");
226