1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Dummy EDAC driver
4 *
5 * A minimal EDAC device driver that registers its own platform device
6 * unconditionally, so it probes successfully on any system -- including
7 * VMs and guests that have no real ECC-capable cache/memory hardware
8 * exposed to them. Useful for exercising the EDAC core, sysfs interface,
9 * and userspace tooling (edac-utils, mcelog, etc.) without needing the
10 * physical hardware the "real" drivers depend on.
11 *
12 * Modeled after drivers/edac/a72_edac.c (Cortex A72 EDAC L1/L2 cache
13 * error detection), but with all of the hardware-specific bits (system
14 * register reads, SMP cross-calls, devicetree compatible matching)
15 * replaced by a software-only error source that can be poked from
16 * debugfs or left to generate nothing at all.
17 */
18
19 #include <linux/cpu.h>
20 #include <linux/debugfs.h>
21 #include <linux/module.h>
22 #include <linux/platform_device.h>
23 #include <linux/slab.h>
24
25 #include "edac_module.h"
26
27 #define DRVNAME "dummy-edac"
28 #define MESSAGE_SIZE 64
29
30 /*
31 * Simulated "error syndrome". In real hardware drivers this would be
32 * read out of a per-CPU system/MMIO register. Here userspace (via
33 * debugfs) or nothing at all drives it, which is exactly the point:
34 * the driver has no dependency on any physical error source.
35 */
36 struct dummy_edac_priv {
37 struct dentry *debugfs_dir;
38 atomic_t inject_ce;
39 atomic_t inject_ue;
40 };
41
dummy_edac_check(struct edac_device_ctl_info * edac_ctl)42 static void dummy_edac_check(struct edac_device_ctl_info *edac_ctl)
43 {
44 struct dummy_edac_priv *priv = edac_ctl->pvt_info;
45 int cpu = raw_smp_processor_id();
46 char msg[MESSAGE_SIZE];
47
48 if (atomic_xchg(&priv->inject_ce, 0)) {
49 snprintf(msg, MESSAGE_SIZE,
50 "simulated correctable error on CPU %d", cpu);
51 edac_device_handle_ce(edac_ctl, cpu, 0, msg);
52 }
53
54 if (atomic_xchg(&priv->inject_ue, 0)) {
55 snprintf(msg, MESSAGE_SIZE,
56 "simulated uncorrectable error on CPU %d", cpu);
57 edac_device_handle_ue(edac_ctl, cpu, 0, msg);
58 }
59 }
60
61 /*
62 * debugfs knobs so you can drive the driver from userspace inside the
63 * guest, e.g.:
64 * echo 1 > /sys/kernel/debug/dummy-edac/inject_ce
65 * echo 1 > /sys/kernel/debug/dummy-edac/inject_ue
66 */
inject_ce_set(void * data,u64 val)67 static int inject_ce_set(void *data, u64 val)
68 {
69 struct dummy_edac_priv *priv = data;
70
71 if (val)
72 atomic_set(&priv->inject_ce, 1);
73 return 0;
74 }
75 DEFINE_DEBUGFS_ATTRIBUTE(inject_ce_fops, NULL, inject_ce_set, "%llu\n");
76
inject_ue_set(void * data,u64 val)77 static int inject_ue_set(void *data, u64 val)
78 {
79 struct dummy_edac_priv *priv = data;
80
81 if (val)
82 atomic_set(&priv->inject_ue, 1);
83 return 0;
84 }
85 DEFINE_DEBUGFS_ATTRIBUTE(inject_ue_fops, NULL, inject_ue_set, "%llu\n");
86
dummy_edac_probe(struct platform_device * pdev)87 static int dummy_edac_probe(struct platform_device *pdev)
88 {
89 struct edac_device_ctl_info *edac_ctl;
90 struct dummy_edac_priv *priv;
91 struct device *dev = &pdev->dev;
92 int rc;
93
94 edac_ctl = edac_device_alloc_ctl_info(sizeof(*priv), "cpu",
95 num_possible_cpus(), "L", 1, 1,
96 edac_device_alloc_index());
97 if (!edac_ctl)
98 return -ENOMEM;
99
100 priv = edac_ctl->pvt_info;
101 atomic_set(&priv->inject_ce, 0);
102 atomic_set(&priv->inject_ue, 0);
103
104 edac_ctl->edac_check = dummy_edac_check;
105 edac_ctl->dev = dev;
106 edac_ctl->mod_name = dev_name(dev);
107 edac_ctl->dev_name = dev_name(dev);
108 edac_ctl->ctl_name = DRVNAME;
109 /* Poll fairly slowly; this is a software source, not real hardware. */
110 edac_ctl->poll_msec = 1000;
111 dev_set_drvdata(dev, edac_ctl);
112
113 rc = edac_device_add_device(edac_ctl);
114 if (rc)
115 goto out_dev;
116
117 priv->debugfs_dir = debugfs_create_dir(DRVNAME, NULL);
118 debugfs_create_file_unsafe("inject_ce", 0200, priv->debugfs_dir,
119 priv, &inject_ce_fops);
120 debugfs_create_file_unsafe("inject_ue", 0200, priv->debugfs_dir,
121 priv, &inject_ue_fops);
122
123 return 0;
124
125 out_dev:
126 edac_device_free_ctl_info(edac_ctl);
127
128 return rc;
129 }
130
dummy_edac_remove(struct platform_device * pdev)131 static void dummy_edac_remove(struct platform_device *pdev)
132 {
133 struct edac_device_ctl_info *edac_ctl = dev_get_drvdata(&pdev->dev);
134 struct dummy_edac_priv *priv = edac_ctl->pvt_info;
135
136 debugfs_remove_recursive(priv->debugfs_dir);
137 edac_device_del_device(edac_ctl->dev);
138 edac_device_free_ctl_info(edac_ctl);
139 }
140
141 static struct platform_driver dummy_edac_driver = {
142 .probe = dummy_edac_probe,
143 .remove = dummy_edac_remove,
144 .driver = {
145 .name = DRVNAME,
146 },
147 };
148
149 /*
150 * No devicetree/ACPI match table, and no scan of CPU nodes for a "compatible"
151 * + enable property: simply register a platform device unconditionally so
152 * probe() always runs. There is nothing here that depends on the underlying
153 * platform actually exposing the corresponding hardware, so it loads fine
154 * under QEMU/KVM, containers-with-a-kernel, or any other guest environment.
155 */
156 static struct platform_device *dummy_pdev;
157
dummy_edac_driver_init(void)158 static int __init dummy_edac_driver_init(void)
159 {
160 int rc;
161
162 dummy_pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
163 if (IS_ERR(dummy_pdev)) {
164 pr_err(DRVNAME ": failed to register dummy platform device\n");
165 return PTR_ERR(dummy_pdev);
166 }
167
168 rc = platform_driver_register(&dummy_edac_driver);
169 if (rc) {
170 platform_device_unregister(dummy_pdev);
171 return rc;
172 }
173
174 pr_info("Loading %s\n", DRVNAME);
175
176 return 0;
177 }
178
dummy_edac_driver_exit(void)179 static void __exit dummy_edac_driver_exit(void)
180 {
181 platform_driver_unregister(&dummy_edac_driver);
182 platform_device_unregister(dummy_pdev);
183
184 pr_info("Removing %s\n", DRVNAME);
185 }
186
187 module_init(dummy_edac_driver_init);
188 module_exit(dummy_edac_driver_exit);
189
190 MODULE_LICENSE("GPL");
191 MODULE_DESCRIPTION("Dummy EDAC driver for testing without real ECC hardware");
192