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