mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
EDAC/dummy: Add a dummy EDAC driver
A dummy EDAC driver is useful for testing purposes in a VM when one doesn't have all the hardware needed to test aspects of the EDAC subsystem code. Fix edac/Makefile alignment while at it. Assisted-by: LLM Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de> Link: https://patch.msgid.link/20260707215307.396571-1-bp@kernel.org
This commit is contained in:
parent
68a3e57814
commit
558c19e274
|
|
@ -594,4 +594,13 @@ config EDAC_VERSALNET
|
|||
and other system errors from various IP subsystems like RPU, NOCs,
|
||||
HNICX, PL on the AMD Versal NET DDR memory controller.
|
||||
|
||||
config EDAC_DUMMY
|
||||
tristate "Dummy EDAC driver"
|
||||
default n
|
||||
help
|
||||
A dummy EDAC driver is useful for testing purposes in a VM when one
|
||||
doesn't have all the hardware needed to test aspects of the EDAC
|
||||
subsystem code.
|
||||
|
||||
You definitely wanna say N here.
|
||||
endif # EDAC
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ obj-$(CONFIG_EDAC_I7300) += i7300_edac.o
|
|||
obj-$(CONFIG_EDAC_I7CORE) += i7core_edac.o
|
||||
obj-$(CONFIG_EDAC_SBRIDGE) += sb_edac.o
|
||||
obj-$(CONFIG_EDAC_PND2) += pnd2_edac.o
|
||||
obj-$(CONFIG_EDAC_IGEN6) += igen6_edac.o
|
||||
obj-$(CONFIG_EDAC_IGEN6) += igen6_edac.o
|
||||
obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o
|
||||
obj-$(CONFIG_EDAC_E752X) += e752x_edac.o
|
||||
obj-$(CONFIG_EDAC_I82875P) += i82875p_edac.o
|
||||
|
|
@ -93,3 +93,4 @@ obj-$(CONFIG_EDAC_VERSAL) += versal_edac.o
|
|||
obj-$(CONFIG_EDAC_LOONGSON) += loongson_edac.o
|
||||
obj-$(CONFIG_EDAC_VERSALNET) += versalnet_edac.o
|
||||
obj-$(CONFIG_EDAC_CORTEX_A72) += a72_edac.o
|
||||
obj-$(CONFIG_EDAC_DUMMY) += dummy_edac.o
|
||||
|
|
|
|||
191
drivers/edac/dummy_edac.c
Normal file
191
drivers/edac/dummy_edac.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Dummy EDAC driver
|
||||
*
|
||||
* A minimal EDAC device driver that registers its own platform device
|
||||
* unconditionally, so it probes successfully on any system -- including
|
||||
* VMs and guests that have no real ECC-capable cache/memory hardware
|
||||
* exposed to them. Useful for exercising the EDAC core, sysfs interface,
|
||||
* and userspace tooling (edac-utils, mcelog, etc.) without needing the
|
||||
* physical hardware the "real" drivers depend on.
|
||||
*
|
||||
* Modeled after drivers/edac/a72_edac.c (Cortex A72 EDAC L1/L2 cache
|
||||
* error detection), but with all of the hardware-specific bits (system
|
||||
* register reads, SMP cross-calls, devicetree compatible matching)
|
||||
* replaced by a software-only error source that can be poked from
|
||||
* debugfs or left to generate nothing at all.
|
||||
*/
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "edac_module.h"
|
||||
|
||||
#define DRVNAME "dummy-edac"
|
||||
#define MESSAGE_SIZE 64
|
||||
|
||||
/*
|
||||
* Simulated "error syndrome". In real hardware drivers this would be
|
||||
* read out of a per-CPU system/MMIO register. Here userspace (via
|
||||
* debugfs) or nothing at all drives it, which is exactly the point:
|
||||
* the driver has no dependency on any physical error source.
|
||||
*/
|
||||
struct dummy_edac_priv {
|
||||
struct dentry *debugfs_dir;
|
||||
atomic_t inject_ce;
|
||||
atomic_t inject_ue;
|
||||
};
|
||||
|
||||
static void dummy_edac_check(struct edac_device_ctl_info *edac_ctl)
|
||||
{
|
||||
struct dummy_edac_priv *priv = edac_ctl->pvt_info;
|
||||
int cpu = raw_smp_processor_id();
|
||||
char msg[MESSAGE_SIZE];
|
||||
|
||||
if (atomic_xchg(&priv->inject_ce, 0)) {
|
||||
snprintf(msg, MESSAGE_SIZE,
|
||||
"simulated correctable error on CPU %d", cpu);
|
||||
edac_device_handle_ce(edac_ctl, cpu, 0, msg);
|
||||
}
|
||||
|
||||
if (atomic_xchg(&priv->inject_ue, 0)) {
|
||||
snprintf(msg, MESSAGE_SIZE,
|
||||
"simulated uncorrectable error on CPU %d", cpu);
|
||||
edac_device_handle_ue(edac_ctl, cpu, 0, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* debugfs knobs so you can drive the driver from userspace inside the
|
||||
* guest, e.g.:
|
||||
* echo 1 > /sys/kernel/debug/dummy-edac/inject_ce
|
||||
* echo 1 > /sys/kernel/debug/dummy-edac/inject_ue
|
||||
*/
|
||||
static int inject_ce_set(void *data, u64 val)
|
||||
{
|
||||
struct dummy_edac_priv *priv = data;
|
||||
|
||||
if (val)
|
||||
atomic_set(&priv->inject_ce, 1);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(inject_ce_fops, NULL, inject_ce_set, "%llu\n");
|
||||
|
||||
static int inject_ue_set(void *data, u64 val)
|
||||
{
|
||||
struct dummy_edac_priv *priv = data;
|
||||
|
||||
if (val)
|
||||
atomic_set(&priv->inject_ue, 1);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(inject_ue_fops, NULL, inject_ue_set, "%llu\n");
|
||||
|
||||
static int dummy_edac_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct edac_device_ctl_info *edac_ctl;
|
||||
struct dummy_edac_priv *priv;
|
||||
struct device *dev = &pdev->dev;
|
||||
int rc;
|
||||
|
||||
edac_ctl = edac_device_alloc_ctl_info(sizeof(*priv), "cpu",
|
||||
num_possible_cpus(), "L", 1, 1,
|
||||
edac_device_alloc_index());
|
||||
if (!edac_ctl)
|
||||
return -ENOMEM;
|
||||
|
||||
priv = edac_ctl->pvt_info;
|
||||
atomic_set(&priv->inject_ce, 0);
|
||||
atomic_set(&priv->inject_ue, 0);
|
||||
|
||||
edac_ctl->edac_check = dummy_edac_check;
|
||||
edac_ctl->dev = dev;
|
||||
edac_ctl->mod_name = dev_name(dev);
|
||||
edac_ctl->dev_name = dev_name(dev);
|
||||
edac_ctl->ctl_name = DRVNAME;
|
||||
/* Poll fairly slowly; this is a software source, not real hardware. */
|
||||
edac_ctl->poll_msec = 1000;
|
||||
dev_set_drvdata(dev, edac_ctl);
|
||||
|
||||
rc = edac_device_add_device(edac_ctl);
|
||||
if (rc)
|
||||
goto out_dev;
|
||||
|
||||
priv->debugfs_dir = debugfs_create_dir(DRVNAME, NULL);
|
||||
debugfs_create_file_unsafe("inject_ce", 0200, priv->debugfs_dir,
|
||||
priv, &inject_ce_fops);
|
||||
debugfs_create_file_unsafe("inject_ue", 0200, priv->debugfs_dir,
|
||||
priv, &inject_ue_fops);
|
||||
|
||||
return 0;
|
||||
|
||||
out_dev:
|
||||
edac_device_free_ctl_info(edac_ctl);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dummy_edac_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct edac_device_ctl_info *edac_ctl = dev_get_drvdata(&pdev->dev);
|
||||
struct dummy_edac_priv *priv = edac_ctl->pvt_info;
|
||||
|
||||
debugfs_remove_recursive(priv->debugfs_dir);
|
||||
edac_device_del_device(edac_ctl->dev);
|
||||
edac_device_free_ctl_info(edac_ctl);
|
||||
}
|
||||
|
||||
static struct platform_driver dummy_edac_driver = {
|
||||
.probe = dummy_edac_probe,
|
||||
.remove = dummy_edac_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* No devicetree/ACPI match table, and no scan of CPU nodes for a "compatible"
|
||||
* + enable property: simply register a platform device unconditionally so
|
||||
* probe() always runs. There is nothing here that depends on the underlying
|
||||
* platform actually exposing the corresponding hardware, so it loads fine
|
||||
* under QEMU/KVM, containers-with-a-kernel, or any other guest environment.
|
||||
*/
|
||||
static struct platform_device *dummy_pdev;
|
||||
|
||||
static int __init dummy_edac_driver_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
dummy_pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||||
if (IS_ERR(dummy_pdev)) {
|
||||
pr_err(DRVNAME ": failed to register dummy platform device\n");
|
||||
return PTR_ERR(dummy_pdev);
|
||||
}
|
||||
|
||||
rc = platform_driver_register(&dummy_edac_driver);
|
||||
if (rc) {
|
||||
platform_device_unregister(dummy_pdev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pr_info("Loading %s\n", DRVNAME);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit dummy_edac_driver_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&dummy_edac_driver);
|
||||
platform_device_unregister(dummy_pdev);
|
||||
|
||||
pr_info("Removing %s\n", DRVNAME);
|
||||
}
|
||||
|
||||
module_init(dummy_edac_driver_init);
|
||||
module_exit(dummy_edac_driver_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Dummy EDAC driver for testing without real ECC hardware");
|
||||
Loading…
Reference in New Issue
Block a user