| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/06 00:36 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces a new driver for ACPI RAS2 hardware-based memory scrubbing. The driver initialization (acpi_ras2_init) is called exactly once during boot (from acpi_init) and checks for the presence of the ACPI RAS2 table. Since QEMU/GCE do not provide this specific ACPI table by default, and the driver does not support dynamic table loading (e.g., via configfs), the code is completely unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/06 00:36 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d31ce05d21ead0ffd27b3c8dcbfd513deb0ac94f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Jul 6 00:36:22 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/ABI/testing/sysfs-edac-scrub b/Documentation/ABI/testing/sysfs-edac-scrub\nindex ab6014743da568..2b874e03362334 100644\n--- a/Documentation/ABI/testing/sysfs-edac-scrub\n+++ b/Documentation/ABI/testing/sysfs-edac-scrub\n@@ -20,11 +20,7 @@ KernelVersion:\t6.15\n Contact:\tlinux-edac@vger.kernel.org\n Description:\n \t\t(RW) The base address of the memory region to be scrubbed\n-\t\tfor on-demand scrubbing. Setting address starts scrubbing.\n-\t\tThe size must be set before that.\n-\n-\t\tThe readback addr value is non-zero if the requested\n-\t\ton-demand scrubbing is in progress, zero otherwise.\n+\t\tfor demand scrubbing.\n \n What:\t\t/sys/bus/edac/devices/\u003cdev-name\u003e/scrubX/size\n Date:\t\tMarch 2025\n@@ -34,6 +30,14 @@ Description:\n \t\t(RW) The size of the memory region to be scrubbed\n \t\t(on-demand scrubbing).\n \n+What:\t\t/sys/bus/edac/devices/\u003cdev-name\u003e/scrubX/enable_demand\n+Date:\t\tJan 2026\n+KernelVersion:\t6.19\n+Contact:\tlinux-edac@vger.kernel.org\n+Description:\n+\t\t(RW) Start/Stop demand scrubbing. The stop operation is\n+\t\tsupported only if the driver and hardware support it.\n+\n What:\t\t/sys/bus/edac/devices/\u003cdev-name\u003e/scrubX/enable_background\n Date:\t\tMarch 2025\n KernelVersion:\t6.15\ndiff --git a/Documentation/edac/scrub.rst b/Documentation/edac/scrub.rst\nindex 2cfa74fa1ffd2f..562bfd6ff63051 100644\n--- a/Documentation/edac/scrub.rst\n+++ b/Documentation/edac/scrub.rst\n@@ -340,3 +340,73 @@ controller or platform when unexpectedly high error rates are detected.\n \n Sysfs files for scrubbing are documented in\n `Documentation/ABI/testing/sysfs-edac-ecs`\n+\n+3. ACPI RAS2 Hardware-based Memory Scrubbing\n+\n+3.1. Demand scrubbing for a specific memory region.\n+\n+3.1.1. Query the status of demand scrubbing\n+\n+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_demand\n+\n+0\n+\n+3.1.2. Query what is device default/current scrub cycle setting.\n+\n+Applicable to both demand and background scrubbing. The unit of the\n+scrub cycle is seconds.\n+\n+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration\n+\n+36000\n+\n+3.1.3. Query the range of device supported scrub cycle for a memory region.\n+The unit of the scrub cycle range is seconds.\n+\n+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/min_cycle_duration\n+\n+3600\n+\n+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/max_cycle_duration\n+\n+86400\n+\n+3.1.4. Program scrubbing for the memory region in RAS2 device to repeat every\n+43200 seconds (half a day).\n+\n+# echo 43200 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration\n+\n+3.1.5. Set address range.\n+\n+Set 'addr' of the memory region to scrub.\n+\n+# echo 0x80000000 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/addr\n+\n+Set 'size' of the memory region to scrub.\n+\n+# echo 0x200000 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/size\n+\n+3.1.6. Start 'demand scrubbing'.\n+\n+When a demand scrub is started, any background scrub currently in progress\n+will be stopped and then automatically restarted at the beginning when the\n+demand scrub has completed.\n+\n+# echo 1 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_demand\n+\n+3.2. Background scrubbing the entire memory\n+\n+3.2.1. Query the status of background scrubbing.\n+\n+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_background\n+\n+0\n+\n+3.2.2. Program background scrubbing for RAS2 device to repeat in every 21600\n+seconds (quarter of a day).\n+\n+# echo 21600 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration\n+\n+3.2.3. Start 'background scrubbing'.\n+\n+# echo 1 \u003e /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_background\ndiff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig\nindex f165d14cf61a3a..539ff143ccc522 100644\n--- a/drivers/acpi/Kconfig\n+++ b/drivers/acpi/Kconfig\n@@ -294,6 +294,17 @@ config ACPI_CPPC_LIB\n \t If your platform does not support CPPC in firmware,\n \t leave this option disabled.\n \n+config ACPI_RAS2\n+\tbool \"ACPI RAS2 driver\"\n+\tselect AUXILIARY_BUS\n+\tdepends on MAILBOX\n+\tdepends on PCC\n+\thelp\n+\t Add support for the RAS2 feature table and provide interfaces for\n+\t platform RAS features, such as hardware-based memory scrubbing.\n+\n+\t If unsure, select N.\n+\n config ACPI_PROCESSOR\n \ttristate \"Processor\"\n \tdepends on X86 || ARM64 || LOONGARCH || RISCV\ndiff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile\nindex d1b0affb844f05..abfec6745724e3 100644\n--- a/drivers/acpi/Makefile\n+++ b/drivers/acpi/Makefile\n@@ -105,6 +105,7 @@ obj-$(CONFIG_ACPI_EC_DEBUGFS)\t+= ec_sys.o\n obj-$(CONFIG_ACPI_BGRT)\t\t+= bgrt.o\n obj-$(CONFIG_ACPI_CPPC_LIB)\t+= cppc_acpi.o\n obj-$(CONFIG_ACPI_SPCR_TABLE)\t+= spcr.o\n+obj-$(CONFIG_ACPI_RAS2)\t\t+= ras2.o\n obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o\n obj-$(CONFIG_ACPI_PPTT) \t+= pptt.o\n obj-$(CONFIG_ACPI_PFRUT)\t+= pfr_update.o pfr_telemetry.o\ndiff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c\nindex a30a904f6535f4..8d6a0b773bc57a 100644\n--- a/drivers/acpi/bus.c\n+++ b/drivers/acpi/bus.c\n@@ -31,6 +31,7 @@\n #include \u003cacpi/apei.h\u003e\n #include \u003clinux/suspend.h\u003e\n #include \u003clinux/prmt.h\u003e\n+#include \u003cacpi/ras2.h\u003e\n \n #include \"internal.h\"\n \n@@ -1610,6 +1611,8 @@ static int __init acpi_init(void)\n \tacpi_debugger_init();\n \tacpi_setup_sb_notify_handler();\n \tacpi_viot_init();\n+\tacpi_ras2_init();\n+\n \treturn 0;\n }\n \ndiff --git a/drivers/acpi/ras2.c b/drivers/acpi/ras2.c\nnew file mode 100644\nindex 00000000000000..19ecbe7c8199bb\n--- /dev/null\n+++ b/drivers/acpi/ras2.c\n@@ -0,0 +1,503 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * ACPI RAS2 feature table driver.\n+ *\n+ * Copyright (c) 2024-2026 HiSilicon Limited.\n+ *\n+ * Support for RAS2 table - ACPI 6.5 Specification, section 5.2.21, which\n+ * provides interfaces for platform RAS features, e.g., for HW-based memory\n+ * scrubbing, and logical to physical address translation service. RAS2 uses\n+ * PCC channel subspace for communicating with the ACPI compliant HW platform.\n+ */\n+\n+#undef pr_fmt\n+#define pr_fmt(fmt) \"ACPI RAS2: \" fmt\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/export.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/ktime.h\u003e\n+#include \u003cacpi/pcc.h\u003e\n+#include \u003cacpi/ras2.h\u003e\n+\n+/**\n+ * struct ras2_sspcc - Data structure for PCC communication\n+ * @mbox_client:\tstruct mbox_client object\n+ * @pcc_chan:\t\tPointer to struct pcc_mbox_chan\n+ * @comm_addr:\t\tPointer to RAS2 PCC shared memory region\n+ * @elem:\t\tList for registered RAS2 PCC channel subspaces\n+ * @pcc_lock:\t\tPCC lock to provide mutually exclusive access\n+ *\t\t\tto PCC channel subspace\n+ * @deadline_us:\tPoll PCC status register timeout in microsecs\n+ *\t\t\tfor PCC command completion\n+ * @pcc_mpar:\t\tMaximum Periodic Access Rate (MPAR) for PCC channel\n+ * @pcc_mrtt:\t\tMinimum Request Turnaround Time (MRTT) in microsecs\n+ *\t\t\tOS must wait after completion of a PCC command before\n+ *\t\t\tissuing next command\n+ * @last_cmd_cmpl_time: Completion time of last PCC command\n+ * @last_mpar_reset:\tTime of last MPAR count reset\n+ * @mpar_count:\t\tMPAR count\n+ * @pcc_id:\t\tIdentifier of the RAS2 platform communication channel\n+ * @last_cmd:\t\tLast PCC command\n+ * @kref:\t\tkref object\n+ */\n+struct ras2_sspcc {\n+\tstruct mbox_client\t\tmbox_client;\n+\tstruct pcc_mbox_chan\t\t*pcc_chan;\n+\tstruct acpi_ras2_shmem __iomem\t*comm_addr;\n+\tstruct list_head\t\telem;\n+\tstruct mutex\t\t\tpcc_lock;\n+\tu64\t\t\t\tdeadline_us;\n+\tunsigned int\t\t\tpcc_mpar;\n+\tunsigned int\t\t\tpcc_mrtt;\n+\tktime_t\t\t\t\tlast_cmd_cmpl_time;\n+\tktime_t\t\t\t\tlast_mpar_reset;\n+\tint\t\t\t\tmpar_count;\n+\tint\t\t\t\tpcc_id;\n+\tu16\t\t\t\tlast_cmd;\n+\tstruct kref\t\t\tkref;\n+};\n+\n+/*\n+ * Arbitrary retries for PCC commands because the remote processor could be\n+ * much slower to reply. Keep it high enough to cover emulators where the\n+ * processors run painfully slow.\n+ */\n+#define PCC_NUM_RETRIES 600ULL\n+#define PCC_CHNL_DEFAULT_LATENCY 1000\n+#define PCC_MIN_POLL_USECS 3\n+\n+#define RAS2_MAX_NUM_PCC_DESCS 100\n+#define RAS2_FEAT_TYPE_MEMORY 0x00\n+\n+/* Static variables for the RAS2 PCC subspaces */\n+static DEFINE_MUTEX(ras2_pcc_list_lock);\n+static LIST_HEAD(ras2_sspcc);\n+\n+static int check_pcc_chan(struct ras2_sspcc *sspcc)\n+{\n+\tstruct acpi_ras2_shmem __iomem *gen_comm_base = sspcc-\u003ecomm_addr;\n+\tu32 cap_status;\n+\tu16 status;\n+\tint rc;\n+\n+\t/*\n+\t * As per ACPI spec, the PCC space will be initialized by the\n+\t * platform and should have set the command completion bit when\n+\t * PCC can be used by OSPM.\n+\t *\n+\t * Poll PCC status register every PCC_MIN_POLL_USECS for maximum of\n+\t * PCC_NUM_RETRIES * PCC channel latency until PCC command complete\n+\t * bit is set.\n+\t */\n+\trc = readw_relaxed_poll_timeout(\u0026gen_comm_base-\u003estatus, status,\n+\t\t\t\t\tstatus \u0026 PCC_STATUS_CMD_COMPLETE,\n+\t\t\t\t\tPCC_MIN_POLL_USECS, sspcc-\u003edeadline_us);\n+\tif (rc) {\n+\t\tpr_warn(\"PCC ID: 0x%x: PCC check channel timeout for last command: 0x%x rc=%d\\n\",\n+\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd, rc);\n+\n+\t\treturn rc;\n+\t}\n+\n+\tif (status \u0026 PCC_STATUS_ERROR) {\n+\t\tpr_warn(\"PCC ID: 0x%x: Error in executing last command: 0x%x\\n\",\n+\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd);\n+\t\tstatus \u0026= ~PCC_STATUS_ERROR;\n+\t\tiowrite16(status, \u0026gen_comm_base-\u003estatus);\n+\t\tiowrite32(0x0, \u0026gen_comm_base-\u003eset_caps_status);\n+\t\treturn -EIO;\n+\t}\n+\n+\t/* Ensure get updated PCC status */\n+\trmb();\n+\tcap_status = ioread32(\u0026gen_comm_base-\u003eset_caps_status);\n+\tswitch (cap_status) {\n+\tcase ACPI_RAS2_NOT_VALID:\n+\tcase ACPI_RAS2_NOT_SUPPORTED:\n+\t\trc = -EPERM;\n+\t\tbreak;\n+\tcase ACPI_RAS2_BUSY:\n+\t\trc = -EBUSY;\n+\t\tbreak;\n+\tcase ACPI_RAS2_FAILED:\n+\tcase ACPI_RAS2_ABORTED:\n+\tcase ACPI_RAS2_INVALID_DATA:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\tdefault:\n+\t\trc = 0;\n+\t}\n+\n+\tiowrite32(0x0, \u0026gen_comm_base-\u003eset_caps_status);\n+\n+\treturn rc;\n+}\n+\n+/**\n+ * ras2_send_pcc_cmd() - Send RAS2 command via PCC channel\n+ * @ras2_ctx:\tpointer to the RAS2 context structure\n+ * @cmd:\tRAS2 command to send\n+ *\n+ * Returns: 0 on success, an error otherwise\n+ */\n+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)\n+{\n+\tstruct acpi_ras2_shmem __iomem *gen_comm_base;\n+\tstruct mbox_chan *pcc_channel;\n+\tstruct ras2_sspcc *sspcc;\n+\ts64 time_delta;\n+\tu16 val;\n+\tint rc;\n+\n+\tif (!ras2_ctx)\n+\t\treturn -EINVAL;\n+\n+\tlockdep_assert_held(ras2_ctx-\u003epcc_lock);\n+\tsspcc = ras2_ctx-\u003esspcc;\n+\tgen_comm_base = sspcc-\u003ecomm_addr;\n+\n+\trc = check_pcc_chan(sspcc);\n+\tif (rc \u003c 0)\n+\t\treturn rc;\n+\n+\tpcc_channel = sspcc-\u003epcc_chan-\u003emchan;\n+\n+\t/*\n+\t * Handle the Minimum Request Turnaround Time (MRTT): the minimum\n+\t * amount of time that OSPM must wait after the completion of\n+\t * a command before issuing the next command, in microseconds.\n+\t */\n+\tif (sspcc-\u003epcc_mrtt) {\n+\t\ttime_delta = ktime_us_delta(ktime_get(), sspcc-\u003elast_cmd_cmpl_time);\n+\t\tif (sspcc-\u003epcc_mrtt \u003e time_delta)\n+\t\t\tfsleep(sspcc-\u003epcc_mrtt - time_delta);\n+\t}\n+\n+\t/*\n+\t * Handle the non-zero Maximum Periodic Access Rate (MPAR): the\n+\t * maximum number of periodic requests that the subspace channel can\n+\t * support, reported in commands per minute. 0 indicates no\n+\t * limitation.\n+\t *\n+\t * This parameter should be ideally zero or large enough so that it\n+\t * can handle maximum number of requests that all the cores in the\n+\t * system can collectively generate. If it is not, follow the spec and\n+\t * just not send the request to the platform after hitting the MPAR\n+\t * limit in any 60s window.\n+\t */\n+\tif (sspcc-\u003epcc_mpar) {\n+\t\tif (!sspcc-\u003empar_count) {\n+\t\t\ttime_delta = ktime_ms_delta(ktime_get(), sspcc-\u003elast_mpar_reset);\n+\t\t\tif ((time_delta \u003c 60 * MSEC_PER_SEC) \u0026\u0026 sspcc-\u003elast_mpar_reset) {\n+\t\t\t\tdev_dbg(ras2_ctx-\u003edev,\n+\t\t\t\t\t\"PCC command 0x%x not sent due to MPAR limit\", cmd);\n+\t\t\t\treturn -EIO;\n+\t\t\t}\n+\t\t\tsspcc-\u003elast_mpar_reset = ktime_get();\n+\t\t\tsspcc-\u003empar_count = sspcc-\u003epcc_mpar;\n+\t\t}\n+\t\tsspcc-\u003empar_count--;\n+\t}\n+\n+\t/* Write to the shared comm region */\n+\tiowrite16(cmd, \u0026gen_comm_base-\u003ecommand);\n+\n+\t/* Flip CMD COMPLETE bit */\n+\tiowrite16(0, \u0026gen_comm_base-\u003estatus);\n+\n+\t/* Ring doorbell */\n+\trc = mbox_send_message(pcc_channel, \u0026cmd);\n+\t/*\n+\t * mbox_send_message() returns a non-negative integer for successful submission\n+\t * and a negative value on failure.\n+\t */\n+\tif (rc \u003c 0) {\n+\t\tdev_warn(ras2_ctx-\u003edev,\n+\t\t\t \"Error sending PCC mbox message command: 0x%x, rc:%d\\n\", cmd, rc);\n+\t\t/* Restore CMD COMPLETE bit on error */\n+\t\tval = ioread16(\u0026gen_comm_base-\u003estatus);\n+\t\tval |= PCC_STATUS_CMD_COMPLETE;\n+\t\tiowrite16(val, \u0026gen_comm_base-\u003estatus);\n+\t\treturn rc;\n+\t} else {\n+\t\trc = 0;\n+\t}\n+\n+\tsspcc-\u003elast_cmd = cmd;\n+\n+\t/*\n+\t * If Minimum Request Turnaround Time is non-zero, need to record the\n+\t * completion time of both READ and WRITE commands for proper handling\n+\t * of MRTT, so need to check for pcc_mrtt in addition to PCC_CMD_EXEC_RAS2.\n+\t */\n+\tif (cmd == PCC_CMD_EXEC_RAS2 || sspcc-\u003epcc_mrtt) {\n+\t\trc = check_pcc_chan(sspcc);\n+\t\tif (sspcc-\u003epcc_mrtt)\n+\t\t\tsspcc-\u003elast_cmd_cmpl_time = ktime_get();\n+\t}\n+\n+\tif (!pcc_channel-\u003embox-\u003etxdone_irq)\n+\t\tmbox_client_txdone(pcc_channel, rc);\n+\n+\treturn rc;\n+}\n+EXPORT_SYMBOL_FOR_MODULES(ras2_send_pcc_cmd, \"acpi_ras2\");\n+\n+static void ras2_list_pcc_release(struct kref *kref)\n+{\n+\tstruct ras2_sspcc *sspcc =\n+\t\tcontainer_of(kref, struct ras2_sspcc, kref);\n+\n+\tguard(mutex)(\u0026ras2_pcc_list_lock);\n+\tlist_del(\u0026sspcc-\u003eelem);\n+\tpcc_mbox_free_channel(sspcc-\u003epcc_chan);\n+\tkfree(sspcc);\n+}\n+\n+static void ras2_sspcc_put(struct ras2_sspcc *sspcc)\n+{\n+\tkref_put(\u0026sspcc-\u003ekref, \u0026ras2_list_pcc_release);\n+}\n+\n+static struct ras2_sspcc *ras2_sspcc_get(int pcc_id)\n+{\n+\tstruct ras2_sspcc *sspcc;\n+\n+\tguard(mutex)(\u0026ras2_pcc_list_lock);\n+\tlist_for_each_entry(sspcc, \u0026ras2_sspcc, elem) {\n+\t\tif (sspcc-\u003epcc_id != pcc_id)\n+\t\t\tcontinue;\n+\n+\t\tif (!kref_get_unless_zero(\u0026sspcc-\u003ekref))\n+\t\t\tbreak;\n+\n+\t\treturn sspcc;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)\n+{\n+\tstruct pcc_mbox_chan *pcc_chan;\n+\tstruct ras2_sspcc *sspcc;\n+\n+\tif (pcc_id \u003c 0)\n+\t\treturn -EINVAL;\n+\n+\tsspcc = ras2_sspcc_get(pcc_id);\n+\tif (sspcc) {\n+\t\tras2_ctx-\u003esspcc\t\t= sspcc;\n+\t\tras2_ctx-\u003ecomm_addr\t= sspcc-\u003ecomm_addr;\n+\t\tras2_ctx-\u003edev\t\t=\n+\t\t\tsspcc-\u003epcc_chan-\u003emchan-\u003embox-\u003edev;\n+\t\tras2_ctx-\u003epcc_lock\t= \u0026sspcc-\u003epcc_lock;\n+\t\treturn 0;\n+\t}\n+\n+\tsspcc = kzalloc(sizeof(*sspcc), GFP_KERNEL);\n+\tif (!sspcc)\n+\t\treturn -ENOMEM;\n+\n+\tpcc_chan = pcc_mbox_request_channel(\u0026sspcc-\u003embox_client, pcc_id);\n+\tif (IS_ERR(pcc_chan)) {\n+\t\tkfree(sspcc);\n+\t\treturn PTR_ERR(pcc_chan);\n+\t}\n+\n+\tif (!pcc_chan-\u003eshmem) {\n+\t\tpcc_mbox_free_channel(pcc_chan);\n+\t\tkfree(sspcc);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tsspcc-\u003epcc_id\t\t= pcc_id;\n+\tsspcc-\u003epcc_chan\t\t= pcc_chan;\n+\tsspcc-\u003ecomm_addr\t= pcc_chan-\u003eshmem;\n+\tif (pcc_chan-\u003elatency)\n+\t\tsspcc-\u003edeadline_us = PCC_NUM_RETRIES * pcc_chan-\u003elatency;\n+\telse\n+\t\tsspcc-\u003edeadline_us = PCC_NUM_RETRIES * PCC_CHNL_DEFAULT_LATENCY;\n+\tsspcc-\u003epcc_mrtt\t\t= pcc_chan-\u003emin_turnaround_time;\n+\tsspcc-\u003epcc_mpar\t\t= pcc_chan-\u003emax_access_rate;\n+\tsspcc-\u003embox_client.knows_txdone\t= true;\n+\n+\tkref_init(\u0026sspcc-\u003ekref);\n+\n+\tmutex_lock(\u0026ras2_pcc_list_lock);\n+\tlist_add(\u0026sspcc-\u003eelem, \u0026ras2_sspcc);\n+\tmutex_unlock(\u0026ras2_pcc_list_lock);\n+\n+\tras2_ctx-\u003esspcc\t\t= sspcc;\n+\tras2_ctx-\u003ecomm_addr\t= sspcc-\u003ecomm_addr;\n+\tras2_ctx-\u003edev\t\t= pcc_chan-\u003emchan-\u003embox-\u003edev;\n+\n+\tmutex_init(\u0026sspcc-\u003epcc_lock);\n+\tras2_ctx-\u003epcc_lock\t= \u0026sspcc-\u003epcc_lock;\n+\n+\treturn 0;\n+}\n+\n+static DEFINE_IDA(ras2_ida);\n+static void ras2_release(struct device *device)\n+{\n+\tstruct auxiliary_device *auxdev = to_auxiliary_dev(device);\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\n+\tida_free(\u0026ras2_ida, auxdev-\u003eid);\n+\tras2_sspcc_put(ras2_ctx-\u003esspcc);\n+\tkfree(ras2_ctx);\n+}\n+\n+static struct ras2_mem_ctx *add_aux_device(char *name, int channel, u32 pxm_inst)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx;\n+\tu32 comp_nid;\n+\tint id, rc;\n+\n+\tcomp_nid = pxm_to_node(pxm_inst);\n+\tif (comp_nid == NUMA_NO_NODE) {\n+\t\tpr_debug(\"Invalid NUMA node, channel=%d pxm_inst=%d\\n\", channel, pxm_inst);\n+\t\treturn ERR_PTR(-ENXIO);\n+\t}\n+\n+\tras2_ctx = kzalloc(sizeof(*ras2_ctx), GFP_KERNEL);\n+\tif (!ras2_ctx)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tras2_ctx-\u003esys_comp_nid = comp_nid;\n+\n+\trc = register_pcc_channel(ras2_ctx, channel);\n+\tif (rc \u003c 0) {\n+\t\tpr_debug(\"Failed to register PCC channel=%d pxm_inst=%d rc=%d\\n\", channel,\n+\t\t\t pxm_inst, rc);\n+\t\tgoto ctx_free;\n+\t}\n+\n+\tid = ida_alloc(\u0026ras2_ida, GFP_KERNEL);\n+\tif (id \u003c 0) {\n+\t\trc = id;\n+\t\tgoto pcc_free;\n+\t}\n+\n+\tras2_ctx-\u003eadev.id\t\t= id;\n+\tras2_ctx-\u003eadev.name\t\t= name;\n+\tras2_ctx-\u003eadev.dev.release\t= ras2_release;\n+\tras2_ctx-\u003eadev.dev.parent\t= ras2_ctx-\u003edev;\n+\n+\trc = auxiliary_device_init(\u0026ras2_ctx-\u003eadev);\n+\tif (rc)\n+\t\tgoto ida_free;\n+\n+\trc = auxiliary_device_add(\u0026ras2_ctx-\u003eadev);\n+\tif (rc) {\n+\t\tauxiliary_device_uninit(\u0026ras2_ctx-\u003eadev);\n+\t\treturn ERR_PTR(rc);\n+\t}\n+\n+\treturn ras2_ctx;\n+\n+ida_free:\n+\tida_free(\u0026ras2_ida, id);\n+pcc_free:\n+\tras2_sspcc_put(ras2_ctx-\u003esspcc);\n+ctx_free:\n+\tkfree(ras2_ctx);\n+\n+\treturn ERR_PTR(rc);\n+}\n+\n+static void remove_aux_device(struct ras2_mem_ctx *ras2_ctx)\n+{\n+\tif (!ras2_ctx)\n+\t\treturn;\n+\n+\tauxiliary_device_delete(\u0026ras2_ctx-\u003eadev);\n+\tauxiliary_device_uninit(\u0026ras2_ctx-\u003eadev);\n+}\n+\n+static int parse_ras2_table(struct acpi_table_ras2 *ras2_tab)\n+{\n+\tstruct acpi_ras2_pcc_desc *pcc_desc_list;\n+\tstruct ras2_mem_ctx **pctx_list;\n+\tstruct ras2_mem_ctx *ras2_ctx;\n+\tu16 tot_tbl_len;\n+\tu16 i;\n+\n+\tif (ras2_tab-\u003eheader.length \u003c sizeof(*ras2_tab)) {\n+\t\tpr_warn(FW_WARN \"ACPI RAS2 table present but broken (too short, size=%u)\\n\",\n+\t\t\tras2_tab-\u003eheader.length);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!ras2_tab-\u003enum_pcc_descs || ras2_tab-\u003enum_pcc_descs \u003e RAS2_MAX_NUM_PCC_DESCS) {\n+\t\tpr_warn(FW_WARN \"No/Invalid number of PCC descs(%d) in ACPI RAS2 table\\n\",\n+\t\t\tras2_tab-\u003enum_pcc_descs);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\ttot_tbl_len = sizeof(*ras2_tab) + ras2_tab-\u003enum_pcc_descs * sizeof(*pcc_desc_list);\n+\tif (ras2_tab-\u003eheader.length \u003c tot_tbl_len) {\n+\t\tpr_warn(FW_WARN \"RAS2 table is not large enough to contain PCC descs=%d size=%u)\\n\",\n+\t\t\tras2_tab-\u003enum_pcc_descs, ras2_tab-\u003eheader.length);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpctx_list = kcalloc(ras2_tab-\u003enum_pcc_descs, sizeof(*pctx_list), GFP_KERNEL);\n+\tif (!pctx_list)\n+\t\treturn -ENOMEM;\n+\n+\tpcc_desc_list = (struct acpi_ras2_pcc_desc *)(ras2_tab + 1);\n+\tfor (i = 0; i \u003c ras2_tab-\u003enum_pcc_descs; i++, pcc_desc_list++) {\n+\t\tif (pcc_desc_list-\u003efeature_type != RAS2_FEAT_TYPE_MEMORY)\n+\t\t\tcontinue;\n+\n+\t\tras2_ctx = add_aux_device(RAS2_MEM_DEV_ID_NAME, pcc_desc_list-\u003echannel_id,\n+\t\t\t\t\t pcc_desc_list-\u003einstance);\n+\t\t/* Invalid NUMA node, continue parsing next node */\n+\t\tif (PTR_ERR(ras2_ctx) == -ENXIO)\n+\t\t\tcontinue;\n+\n+\t\tif (IS_ERR(ras2_ctx)) {\n+\t\t\tpr_warn(\"Failed to add RAS2 auxiliary device rc=%ld\\n\", PTR_ERR(ras2_ctx));\n+\t\t\tfor (; i \u003e 0; i--) {\n+\t\t\t\tif (pctx_list[i - 1])\n+\t\t\t\t\tremove_aux_device(pctx_list[i - 1]);\n+\t\t\t}\n+\t\t\tkfree(pctx_list);\n+\t\t\treturn PTR_ERR(ras2_ctx);\n+\t\t}\n+\t\tpctx_list[i] = ras2_ctx;\n+\t}\n+\tkfree(pctx_list);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * acpi_ras2_init - RAS2 driver initialization function.\n+ *\n+ * Extracts the ACPI RAS2 table and retrieves ID for the PCC channel subspace\n+ * for communicating with the ACPI compliant HW platform. Driver adds an\n+ * auxiliary device, which binds to the memory ACPI RAS2 driver, for each RAS2\n+ * memory feature.\n+ *\n+ * Returns: none.\n+ */\n+void __init acpi_ras2_init(void)\n+{\n+\tstruct acpi_table_ras2 *ras2_tab;\n+\tacpi_status status;\n+\n+\tstatus = acpi_get_table(ACPI_SIG_RAS2, 0, (struct acpi_table_header **)\u0026ras2_tab);\n+\tif (ACPI_FAILURE(status)) {\n+\t\tpr_debug(\"Failed to get table, %s\\n\", acpi_format_exception(status));\n+\t\treturn;\n+\t}\n+\n+\tif (parse_ras2_table(ras2_tab))\n+\t\tpr_debug(\"Failed to parse RAS2 table\\n\");\n+\n+\tacpi_put_table((struct acpi_table_header *)ras2_tab);\n+}\ndiff --git a/drivers/edac/scrub.c b/drivers/edac/scrub.c\nindex f9d02af2fc3a20..f3b9a2f049500e 100644\n--- a/drivers/edac/scrub.c\n+++ b/drivers/edac/scrub.c\n@@ -14,6 +14,7 @@ enum edac_scrub_attributes {\n \tSCRUB_ADDRESS,\n \tSCRUB_SIZE,\n \tSCRUB_ENABLE_BACKGROUND,\n+\tSCRUB_ENABLE_DEMAND,\n \tSCRUB_MIN_CYCLE_DURATION,\n \tSCRUB_MAX_CYCLE_DURATION,\n \tSCRUB_CUR_CYCLE_DURATION,\n@@ -55,6 +56,7 @@ static ssize_t attrib##_show(struct device *ras_feat_dev,\t\t\t\\\n EDAC_SCRUB_ATTR_SHOW(addr, read_addr, u64, \"0x%llx\\n\")\n EDAC_SCRUB_ATTR_SHOW(size, read_size, u64, \"0x%llx\\n\")\n EDAC_SCRUB_ATTR_SHOW(enable_background, get_enabled_bg, bool, \"%u\\n\")\n+EDAC_SCRUB_ATTR_SHOW(enable_demand, get_enabled_od, bool, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(min_cycle_duration, get_min_cycle, u32, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(max_cycle_duration, get_max_cycle, u32, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(current_cycle_duration, get_cycle_duration, u32, \"%u\\n\")\n@@ -84,6 +86,7 @@ static ssize_t attrib##_store(struct device *ras_feat_dev,\t\t\t\\\n EDAC_SCRUB_ATTR_STORE(addr, write_addr, u64, kstrtou64)\n EDAC_SCRUB_ATTR_STORE(size, write_size, u64, kstrtou64)\n EDAC_SCRUB_ATTR_STORE(enable_background, set_enabled_bg, unsigned long, kstrtoul)\n+EDAC_SCRUB_ATTR_STORE(enable_demand, set_enabled_od, unsigned long, kstrtoul)\n EDAC_SCRUB_ATTR_STORE(current_cycle_duration, set_cycle_duration, unsigned long, kstrtoul)\n \n static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)\n@@ -119,6 +122,14 @@ static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int\n \t\t\t\treturn 0444;\n \t\t}\n \t\tbreak;\n+\tcase SCRUB_ENABLE_DEMAND:\n+\t\tif (ops-\u003eget_enabled_od) {\n+\t\t\tif (ops-\u003eset_enabled_od)\n+\t\t\t\treturn a-\u003emode;\n+\t\t\telse\n+\t\t\t\treturn 0444;\n+\t\t}\n+\t\tbreak;\n \tcase SCRUB_MIN_CYCLE_DURATION:\n \t\tif (ops-\u003eget_min_cycle)\n \t\t\treturn a-\u003emode;\n@@ -164,6 +175,7 @@ static int scrub_create_desc(struct device *scrub_dev,\n \t\t[SCRUB_ADDRESS] = EDAC_SCRUB_ATTR_RW(addr, instance),\n \t\t[SCRUB_SIZE] = EDAC_SCRUB_ATTR_RW(size, instance),\n \t\t[SCRUB_ENABLE_BACKGROUND] = EDAC_SCRUB_ATTR_RW(enable_background, instance),\n+\t\t[SCRUB_ENABLE_DEMAND] = EDAC_SCRUB_ATTR_RW(enable_demand, instance),\n \t\t[SCRUB_MIN_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(min_cycle_duration, instance),\n \t\t[SCRUB_MAX_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(max_cycle_duration, instance),\n \t\t[SCRUB_CUR_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RW(current_cycle_duration, instance)\ndiff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig\nindex fc4f4bb94a4c65..a1e6aed8bcc859 100644\n--- a/drivers/ras/Kconfig\n+++ b/drivers/ras/Kconfig\n@@ -46,4 +46,17 @@ config RAS_FMPM\n \t Memory will be retired during boot time and run time depending on\n \t platform-specific policies.\n \n+config MEM_ACPI_RAS2\n+\ttristate \"Memory ACPI RAS2 driver\"\n+\tdepends on ACPI_RAS2\n+\tdepends on EDAC\n+\tdepends on EDAC_SCRUB\n+\tselect NUMA_KEEP_MEMINFO\n+\thelp\n+\t The driver binds to the auxiliary device added by the ACPI RAS2\n+\t feature table parser. The driver uses a PCC channel subspace to\n+\t communicate with the ACPI-compliant platform and provides\n+\t control of the HW-based memory scrubber parameters to the user\n+\t through the EDAC scrub interface.\n+\n endif\ndiff --git a/drivers/ras/Makefile b/drivers/ras/Makefile\nindex 11f95d59d3972d..a0e6e903d6b09b 100644\n--- a/drivers/ras/Makefile\n+++ b/drivers/ras/Makefile\n@@ -2,6 +2,7 @@\n obj-$(CONFIG_RAS)\t+= ras.o\n obj-$(CONFIG_DEBUG_FS)\t+= debugfs.o\n obj-$(CONFIG_RAS_CEC)\t+= cec.o\n+obj-$(CONFIG_MEM_ACPI_RAS2)\t+= acpi_ras2.o\n \n obj-$(CONFIG_RAS_FMPM)\t+= amd/fmpm.o\n obj-y\t\t\t+= amd/atl/\ndiff --git a/drivers/ras/acpi_ras2.c b/drivers/ras/acpi_ras2.c\nnew file mode 100644\nindex 00000000000000..2a7de226ec8592\n--- /dev/null\n+++ b/drivers/ras/acpi_ras2.c\n@@ -0,0 +1,589 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * ACPI RAS2 memory driver\n+ *\n+ * Copyright (c) 2024-2026 HiSilicon Limited.\n+ *\n+ */\n+\n+#undef pr_fmt\n+#define pr_fmt(fmt)\t\"ACPI RAS2 MEMORY: \" fmt\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/edac.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003cacpi/ras2.h\u003e\n+\n+#define RAS2_SUPPORT_HW_PARTOL_SCRUB BIT(0)\n+#define RAS2_TYPE_PATROL_SCRUB 0x0000\n+\n+#define RAS2_GET_PATROL_PARAMETERS 0x01\n+#define RAS2_START_PATROL_SCRUBBER 0x02\n+#define RAS2_STOP_PATROL_SCRUBBER 0x03\n+\n+/*\n+ * RAS2 patrol scrub\n+ */\n+#define RAS2_PS_SC_HRS_IN_MASK GENMASK(15, 8)\n+#define RAS2_PS_EN_BACKGROUND BIT(0)\n+#define RAS2_PS_SC_HRS_OUT_MASK GENMASK(7, 0)\n+#define RAS2_PS_MIN_SC_HRS_OUT_MASK GENMASK(15, 8)\n+#define RAS2_PS_MAX_SC_HRS_OUT_MASK GENMASK(23, 16)\n+#define RAS2_PS_FLAG_SCRUB_RUNNING BIT(0)\n+\n+#define RAS2_SCRUB_NAME_LEN 128\n+#define RAS2_HOUR_IN_SECS 3600\n+\n+struct acpi_ras2_ps_shared_mem {\n+\tstruct acpi_ras2_shmem common;\n+\tstruct acpi_ras2_patrol_scrub_param params;\n+};\n+\n+#define TO_ACPI_RAS2_PS_SHMEM(_addr) \\\n+\tcontainer_of(_addr, struct acpi_ras2_ps_shared_mem, common)\n+\n+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable);\n+\n+static int ras2_is_patrol_scrub_support(struct ras2_mem_ctx *ras2_ctx)\n+{\n+\tstruct acpi_ras2_shmem __iomem *common = (void *)ras2_ctx-\u003ecomm_addr;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tiowrite8(0, \u0026common-\u003eset_caps[0]);\n+\n+\treturn ioread8(\u0026common-\u003efeatures[0]) \u0026 RAS2_SUPPORT_HW_PARTOL_SCRUB;\n+}\n+\n+static int ras2_update_patrol_scrub_params_cache(struct ras2_mem_ctx *ras2_ctx)\n+{\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm =\n+\t\tTO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tu32 scrub_params_out;\n+\tint ret;\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tiowrite16(RAS2_GET_PATROL_PARAMETERS, \u0026ps_sm-\u003eparams.command);\n+\tiowrite64(ras2_ctx-\u003emem_base, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\tiowrite64(ras2_ctx-\u003emem_size, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(ras2_ctx-\u003edev, \"Failed to read patrol scrub parameters\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tscrub_params_out = ioread32(\u0026ps_sm-\u003eparams.scrub_params_out);\n+\tras2_ctx-\u003emin_scrub_cycle = FIELD_GET(RAS2_PS_MIN_SC_HRS_OUT_MASK,\n+\t\t\t\t\t scrub_params_out);\n+\tras2_ctx-\u003emax_scrub_cycle = FIELD_GET(RAS2_PS_MAX_SC_HRS_OUT_MASK,\n+\t\t\t\t\t scrub_params_out);\n+\tras2_ctx-\u003escrub_cycle_hrs = FIELD_GET(RAS2_PS_SC_HRS_OUT_MASK,\n+\t\t\t\t\t scrub_params_out);\n+\tif (ras2_ctx-\u003ebg_scrub) {\n+\t\tras2_ctx-\u003eod_scrub = false;\n+\t\tras2_ctx-\u003ebase = 0;\n+\t\tras2_ctx-\u003esize = 0;\n+\t\treturn 0;\n+\t}\n+\n+\tif (ioread32(\u0026ps_sm-\u003eparams.flags) \u0026 RAS2_PS_FLAG_SCRUB_RUNNING) {\n+\t\tras2_ctx-\u003eod_scrub = true;\n+\t\tras2_ctx-\u003ebase = ioread64(\u0026ps_sm-\u003eparams.actl_addr_range[0]);\n+\t\tras2_ctx-\u003esize = ioread64(\u0026ps_sm-\u003eparams.actl_addr_range[1]);\n+\t} else {\n+\t\tras2_ctx-\u003eod_scrub = false;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Context - PCC lock must be held */\n+static int ras2_get_demand_scrub_running(struct ras2_mem_ctx *ras2_ctx, bool *running)\n+{\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm =\n+\t\tTO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tint ret;\n+\n+\tif (!ras2_ctx-\u003eod_scrub) {\n+\t\t*running = false;\n+\t\treturn 0;\n+\t}\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tiowrite16(RAS2_GET_PATROL_PARAMETERS, \u0026ps_sm-\u003eparams.command);\n+\tiowrite64(ras2_ctx-\u003emem_base, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\tiowrite64(ras2_ctx-\u003emem_size, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(ras2_ctx-\u003edev, \"Failed to read patrol scrub parameters\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\t*running = ioread32(\u0026ps_sm-\u003eparams.flags) \u0026 RAS2_PS_FLAG_SCRUB_RUNNING;\n+\tif (!(*running))\n+\t\tras2_ctx-\u003eod_scrub = false;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_scrub_monitor_thread(void *p)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = (struct ras2_mem_ctx *)p;\n+\tbool running;\n+\tint ret;\n+\n+\twhile (!kthread_should_stop()) {\n+\t\tif (!ras2_ctx-\u003ereenable_bg_scrub)\n+\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * If ras2_get_demand_scrub_running() fails here, re-enabling background\n+\t\t * scrubbing immediately may not be possible or correct. In that case,\n+\t\t * the admin or firmware may need to re-enable background scrubbing\n+\t\t * after demand scrubbing has finished.\n+\t\t */\n+\t\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\t\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\t\tif (ret) {\n+\t\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!running) {\n+\t\t\tret = __ras2_hw_scrub_set_enabled_bg(ras2_ctx-\u003edev, ras2_ctx, true);\n+\t\t\tif (ret)\n+\t\t\t\tdev_err(ras2_ctx-\u003edev,\n+\t\t\t\t\t\"Failed to enable background scrub ret=%d\\n\", ret);\n+\n+\t\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\tmsleep(1000);\n+\t}\n+\n+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\tras2_ctx-\u003ethread = NULL;\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_min_scrub_cycle(struct device *dev, void *drv_data, u32 *min)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*min = ras2_ctx-\u003emin_scrub_cycle * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_max_scrub_cycle(struct device *dev, void *drv_data, u32 *max)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*max = ras2_ctx-\u003emax_scrub_cycle * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_cycle_read(struct device *dev, void *drv_data, u32 *scrub_cycle_secs)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*scrub_cycle_secs = ras2_ctx-\u003escrub_cycle_hrs * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_cycle_write(struct device *dev, void *drv_data, u32 scrub_cycle_secs)\n+{\n+\tu32 scrub_cycle_hrs = scrub_cycle_secs / RAS2_HOUR_IN_SECS;\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tif (scrub_cycle_hrs \u003c ras2_ctx-\u003emin_scrub_cycle ||\n+\t scrub_cycle_hrs \u003e ras2_ctx-\u003emax_scrub_cycle)\n+\t\treturn -EINVAL;\n+\n+\tras2_ctx-\u003eset_scrub_cycle = scrub_cycle_hrs;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_addr(struct device *dev, void *drv_data, u64 *base)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t/*\n+\t * When BG scrubbing is enabled the actual address range is not valid.\n+\t * Return -EBUSY now unless find out a method to retrieve actual full PA range.\n+\t */\n+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*base = ras2_ctx-\u003ebase;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_size(struct device *dev, void *drv_data, u64 *size)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*size = ras2_ctx-\u003esize;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_write_addr(struct device *dev, void *drv_data, u64 base)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tras2_ctx-\u003ebase = base;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_write_size(struct device *dev, void *drv_data, u64 size)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tif (!size)\n+\t\treturn -EINVAL;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tras2_ctx-\u003esize = size;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_get_enabled_bg(struct device *dev, void *drv_data, bool *enabled)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*enabled = ras2_ctx-\u003ebg_scrub;\n+\n+\treturn 0;\n+}\n+\n+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tu32 scrub_params_in;\n+\tbool running;\n+\tint ret;\n+\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tif (enable) {\n+\t\tif (ras2_ctx-\u003ebg_scrub || running)\n+\t\t\treturn -EBUSY;\n+\n+\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\t\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\t\tscrub_params_in \u0026= ~RAS2_PS_SC_HRS_IN_MASK;\n+\t\tscrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx-\u003eset_scrub_cycle);\n+\t\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\t\tiowrite16(RAS2_START_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\t} else {\n+\t\tif (!ras2_ctx-\u003ebg_scrub)\n+\t\t\treturn -EPERM;\n+\n+\t\tiowrite16(RAS2_STOP_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\t}\n+\n+\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\tscrub_params_in \u0026= ~RAS2_PS_EN_BACKGROUND;\n+\tscrub_params_in |= FIELD_PREP(RAS2_PS_EN_BACKGROUND, enable);\n+\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to %s background scrubbing\\n\",\n+\t\t\tstr_enable_disable(enable));\n+\t\treturn ret;\n+\t}\n+\n+\tras2_ctx-\u003ebg_scrub = enable;\n+\tif (enable) {\n+\t\tras2_ctx-\u003ereenable_bg_scrub = false;\n+\t\t/* Update the cache to account for rounding of supplied parameters and similar */\n+\t\treturn ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\n+\treturn __ras2_hw_scrub_set_enabled_bg(dev, drv_data, enable);\n+}\n+\n+static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*enabled = running;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tu32 scrub_params_in;\n+\tbool running;\n+\tint ret;\n+\n+\tif (!enable)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\t/* Stop any background scrub currently in progress */\n+\tif (ras2_ctx-\u003ebg_scrub) {\n+\t\tret = __ras2_hw_scrub_set_enabled_bg(dev, drv_data, false);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tras2_ctx-\u003ereenable_bg_scrub = true;\n+\t}\n+\n+\t/*\n+\t * The validity checks for the address range to scrub need to be updated\n+\t * with (base + size) \u003e (mem_base + mem_size) check in the future once a\n+\t * proper method for determining the NUMA node memory range is available.\n+\t */\n+\tif (!ras2_ctx-\u003esize || ras2_ctx-\u003ebase \u003c ras2_ctx-\u003emem_base) {\n+\t\tdev_err(dev, \"%s: Invalid address range, base=0x%llx size=0x%llx\\n\",\n+\t\t\t__func__, ras2_ctx-\u003ebase, ras2_ctx-\u003esize);\n+\t\tret = -ERANGE;\n+\t\tgoto enable_bg_scrub;\n+\t}\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\tscrub_params_in \u0026= ~RAS2_PS_SC_HRS_IN_MASK;\n+\tscrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx-\u003eset_scrub_cycle);\n+\tscrub_params_in \u0026= ~RAS2_PS_EN_BACKGROUND;\n+\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\tiowrite64(ras2_ctx-\u003ebase, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\tiowrite64(ras2_ctx-\u003esize, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\tiowrite16(RAS2_START_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to start demand scrubbing rc(%d)\\n\", ret);\n+\t\tif (ret != -EBUSY) {\n+\t\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\t\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\t\t\tras2_ctx-\u003eod_scrub = false;\n+\t\t\tras2_ctx-\u003ebase = 0;\n+\t\t\tras2_ctx-\u003esize = 0;\n+\t\t}\n+\t\tgoto enable_bg_scrub;\n+\t}\n+\n+\tras2_ctx-\u003eod_scrub = enable;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\n+\tif (ras2_ctx-\u003ereenable_bg_scrub \u0026\u0026 ras2_ctx-\u003edriver_active \u0026\u0026 !ras2_ctx-\u003ethread) {\n+\t\tras2_ctx-\u003ethread = kthread_run(ras2_scrub_monitor_thread, ras2_ctx,\n+\t\t\t\t\t \"ras2_scrub_nid%d\", ras2_ctx-\u003esys_comp_nid);\n+\t\tif (IS_ERR(ras2_ctx-\u003ethread)) {\n+\t\t\tret = PTR_ERR(ras2_ctx-\u003ethread);\n+\t\t\tras2_ctx-\u003ethread = NULL;\n+\t\t\t/*\n+\t\t\t * If kthread_run() fails and the demand scrubbing has started running,\n+\t\t\t * re-enabling background scrub will fail. Thus admin/firmware may need\n+\t\t\t * to re-enable background scrub after demand scrubbing has finished.\n+\t\t\t */\n+\t\t\tgoto enable_bg_scrub;\n+\t\t}\n+\t}\n+\n+\treturn ret;\n+\n+enable_bg_scrub:\n+\tif (ras2_ctx-\u003ereenable_bg_scrub) {\n+\t\tras2_ctx-\u003ereenable_bg_scrub = false;\n+\t\t__ras2_hw_scrub_set_enabled_bg(dev, drv_data, true);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static const struct edac_scrub_ops ras2_scrub_ops = {\n+\t.read_addr = ras2_hw_scrub_read_addr,\n+\t.read_size = ras2_hw_scrub_read_size,\n+\t.write_addr = ras2_hw_scrub_write_addr,\n+\t.write_size = ras2_hw_scrub_write_size,\n+\t.get_enabled_bg = ras2_hw_scrub_get_enabled_bg,\n+\t.set_enabled_bg = ras2_hw_scrub_set_enabled_bg,\n+\t.get_enabled_od = ras2_hw_scrub_get_enabled_od,\n+\t.set_enabled_od = ras2_hw_scrub_set_enabled_od,\n+\t.get_min_cycle = ras2_hw_scrub_read_min_scrub_cycle,\n+\t.get_max_cycle = ras2_hw_scrub_read_max_scrub_cycle,\n+\t.get_cycle_duration = ras2_hw_scrub_cycle_read,\n+\t.set_cycle_duration = ras2_hw_scrub_cycle_write,\n+};\n+\n+static void ras2_mem_drv_remove(struct auxiliary_device *auxdev)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\n+\tif (!ras2_ctx)\n+\t\treturn;\n+\n+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\tras2_ctx-\u003edriver_active = false;\n+\tif (ras2_ctx-\u003ethread) {\n+\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\tkthread_stop(ras2_ctx-\u003ethread);\n+\t\tras2_ctx-\u003ethread = NULL;\n+\t\treturn;\n+\t}\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+}\n+\n+static int ras2_mem_drv_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\tstruct edac_dev_feature ras_features;\n+\tchar scrub_name[RAS2_SCRUB_NAME_LEN];\n+\tunsigned long start_pfn, num_spanned_pages;\n+\tint ret;\n+\n+\tif (!ras2_is_patrol_scrub_support(ras2_ctx))\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/*\n+\t * Retrieve the PA range of the NUMA domain and use it as the\n+\t * 'Requested Address Range', when send RAS2 command\n+\t * GET_PATROL_PARAMETERS to get parameters that apply to all addresses\n+\t * in the NUMA domain as well as when send command START_PATROL_SCRUBBER\n+\t * to start the demand scrubbing.\n+\t */\n+\tstart_pfn = node_start_pfn(ras2_ctx-\u003esys_comp_nid);\n+\tnum_spanned_pages = node_spanned_pages(ras2_ctx-\u003esys_comp_nid);\n+\tif (!num_spanned_pages) {\n+\t\tpr_debug(\"Failed to find PA range of NUMA node(%u)\\n\", ras2_ctx-\u003esys_comp_nid);\n+\t\treturn -EPERM;\n+\t}\n+\n+\tras2_ctx-\u003emem_base = __pfn_to_phys(start_pfn);\n+\tras2_ctx-\u003emem_size = (u64)num_spanned_pages * PAGE_SIZE;\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Initialize set_scrub_cycle */\n+\tif (ras2_ctx-\u003escrub_cycle_hrs \u003e ras2_ctx-\u003emin_scrub_cycle)\n+\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003escrub_cycle_hrs;\n+\telse\n+\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003emin_scrub_cycle;\n+\n+\tsprintf(scrub_name, \"acpi_ras_mem%d\", auxdev-\u003eid);\n+\n+\tras_features.ft_type\t= RAS_FEAT_SCRUB;\n+\tras_features.instance\t= 0;\n+\tras_features.scrub_ops\t= \u0026ras2_scrub_ops;\n+\tras_features.ctx\t= ras2_ctx;\n+\n+\tras2_ctx-\u003edriver_active = true;\n+\n+\treturn edac_dev_register(\u0026auxdev-\u003edev, scrub_name, NULL, 1, \u0026ras_features);\n+}\n+\n+static const struct auxiliary_device_id ras2_mem_dev_id_table[] = {\n+\t{ .name = RAS2_AUX_DEV_NAME \".\" RAS2_MEM_DEV_ID_NAME, },\n+\t{ }\n+};\n+\n+MODULE_DEVICE_TABLE(auxiliary, ras2_mem_dev_id_table);\n+\n+static struct auxiliary_driver ras2_mem_driver = {\n+\t.name = RAS2_MEM_DEV_ID_NAME,\n+\t.probe = ras2_mem_drv_probe,\n+\t.remove = ras2_mem_drv_remove,\n+\t.id_table = ras2_mem_dev_id_table,\n+};\n+module_auxiliary_driver(ras2_mem_driver);\n+\n+MODULE_IMPORT_NS(\"ACPI_RAS2\");\n+MODULE_DESCRIPTION(\"ACPI RAS2 memory driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/include/acpi/ras2.h b/include/acpi/ras2.h\nnew file mode 100644\nindex 00000000000000..ac75b0307fa9cc\n--- /dev/null\n+++ b/include/acpi/ras2.h\n@@ -0,0 +1,86 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * ACPI RAS2 (RAS Feature Table) methods.\n+ *\n+ * Copyright (c) 2024-2026 HiSilicon Limited\n+ */\n+\n+#ifndef _ACPI_RAS2_H\n+#define _ACPI_RAS2_H\n+\n+#include \u003clinux/acpi.h\u003e\n+#include \u003clinux/auxiliary_bus.h\u003e\n+#include \u003clinux/mailbox_client.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct device;\n+\n+/*\n+ * ACPI spec 6.5 Table 5.82: PCC command codes used by\n+ * RAS2 platform communication channel.\n+ */\n+#define PCC_CMD_EXEC_RAS2 0x01\n+\n+#define RAS2_AUX_DEV_NAME \"ras2\"\n+#define RAS2_MEM_DEV_ID_NAME \"acpi_ras2_mem\"\n+\n+/**\n+ * struct ras2_mem_ctx - Context for RAS2 memory features\n+ * @adev:\t\tAuxiliary device object\n+ * @comm_addr:\t\tPointer to RAS2 PCC shared memory region\n+ * @dev:\t\tPointer to device backing struct mbox_controller for PCC\n+ * @sspcc:\t\tPointer to local data structure for PCC communication\n+ * @pcc_lock:\t\tPointer to PCC lock to provide mutually exclusive access\n+ *\t\t\tto PCC channel subspace\n+ * @sys_comp_nid:\tNode ID of the system component that the RAS feature\n+ *\t\t\tis associated with. See ACPI spec 6.5 Table 5.80: RAS2\n+ *\t\t\tPlatform Communication Channel Descriptor format,\n+ *\t\t\tField: Instance\n+ * @mem_base:\t\tBase of the lowest physical continuous memory range\n+ *\t\t\tof the memory associated with the NUMA domain\n+ * @mem_size\t\tSize of the lowest physical continuous memory range\n+ *\t\t\tof the memory associated with the NUMA domain\n+ * @base:\t\tBase address of the memory region to scrub\n+ * @size:\t\tSize of the memory region to scrub\n+ * @scrub_cycle_hrs:\tCurrent scrub rate in hours\n+ * @set_scrub_cycle:\tScrub rate to set in hours\n+ * @min_scrub_cycle:\tMinimum scrub rate supported\n+ * @max_scrub_cycle:\tMaximum scrub rate supported\n+ * @od_scrub:\t\tStatus of demand scrubbing (memory region)\n+ * @bg_scrub:\t\tStatus of background patrol scrubbing\n+ * @reenable_bg_scrub:\tFlag indicates restart background scrubbing after demand\n+ *\t\t\tscrubbing is finished\n+ * @thread:\t\tDemand scrub monitor kthread\n+ * @driver_active:\tFlag indicates RAS2 memory driver is active/removed\n+ */\n+struct ras2_mem_ctx {\n+\tstruct auxiliary_device\t\tadev;\n+\tstruct acpi_ras2_shmem __iomem\t*comm_addr;\n+\tstruct device\t\t\t*dev;\n+\tvoid\t\t\t\t*sspcc;\n+\tstruct mutex\t\t\t*pcc_lock;\n+\tu32\t\t\t\tsys_comp_nid;\n+\tu64\t\t\t\tmem_base;\n+\tu64\t\t\t\tmem_size;\n+\tu64\t\t\t\tbase;\n+\tu64\t\t\t\tsize;\n+\tu8\t\t\t\tscrub_cycle_hrs;\n+\tu8\t\t\t\tset_scrub_cycle;\n+\tu8\t\t\t\tmin_scrub_cycle;\n+\tu8\t\t\t\tmax_scrub_cycle;\n+\tbool\t\t\t\tod_scrub;\n+\tbool\t\t\t\tbg_scrub;\n+\tbool\t\t\t\treenable_bg_scrub;\n+\tstruct task_struct\t\t*thread;\n+\tbool\t\t\t\tdriver_active;\n+};\n+\n+#ifdef CONFIG_ACPI_RAS2\n+void __init acpi_ras2_init(void);\n+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd);\n+#else\n+static inline void acpi_ras2_init(void) { }\n+#endif\n+\n+#endif /* _ACPI_RAS2_H */\ndiff --git a/include/linux/edac.h b/include/linux/edac.h\nindex e6b4e51130e5ff..be4f6e52ea38a0 100644\n--- a/include/linux/edac.h\n+++ b/include/linux/edac.h\n@@ -684,6 +684,8 @@ enum edac_dev_feat {\n * @write_size: set offset of the scrubbing range.\n * @get_enabled_bg: check if currently performing background scrub.\n * @set_enabled_bg: start or stop a bg-scrub.\n+ * @get_enabled_od: check if currently performing demand scrub.\n+ * @set_enabled_od: start or stop a demand-scrub.\n * @get_min_cycle: get minimum supported scrub cycle duration in seconds.\n * @get_max_cycle: get maximum supported scrub cycle duration in seconds.\n * @get_cycle_duration: get current scrub cycle duration in seconds.\n@@ -696,6 +698,8 @@ struct edac_scrub_ops {\n \tint (*write_size)(struct device *dev, void *drv_data, u64 size);\n \tint (*get_enabled_bg)(struct device *dev, void *drv_data, bool *enable);\n \tint (*set_enabled_bg)(struct device *dev, void *drv_data, bool enable);\n+\tint (*get_enabled_od)(struct device *dev, void *drv_data, bool *enable);\n+\tint (*set_enabled_od)(struct device *dev, void *drv_data, bool enable);\n \tint (*get_min_cycle)(struct device *dev, void *drv_data, u32 *min);\n \tint (*get_max_cycle)(struct device *dev, void *drv_data, u32 *max);\n \tint (*get_cycle_duration)(struct device *dev, void *drv_data, u32 *cycle);\n"
} |
| 2/1 | 2026/07/06 00:36 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces a new driver for ACPI RAS2 hardware-based memory scrubbing. The driver initialization (acpi_ras2_init) is called exactly once during boot (from acpi_init) and checks for the presence of the ACPI RAS2 table. Since QEMU/GCE do not provide this specific ACPI table by default, and the driver does not support dynamic table loading (e.g., via configfs), the code is completely unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d31ce05d21ead0ffd27b3c8dcbfd513deb0ac94f
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Jul 6 00:36:22 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/ABI/testing/sysfs-edac-scrub b/Documentation/ABI/testing/sysfs-edac-scrub
index ab6014743da568..2b874e03362334 100644
--- a/Documentation/ABI/testing/sysfs-edac-scrub
+++ b/Documentation/ABI/testing/sysfs-edac-scrub
@@ -20,11 +20,7 @@ KernelVersion: 6.15
Contact: linux-edac@vger.kernel.org
Description:
(RW) The base address of the memory region to be scrubbed
- for on-demand scrubbing. Setting address starts scrubbing.
- The size must be set before that.
-
- The readback addr value is non-zero if the requested
- on-demand scrubbing is in progress, zero otherwise.
+ for demand scrubbing.
What: /sys/bus/edac/devices/<dev-name>/scrubX/size
Date: March 2025
@@ -34,6 +30,14 @@ Description:
(RW) The size of the memory region to be scrubbed
(on-demand scrubbing).
+What: /sys/bus/edac/devices/<dev-name>/scrubX/enable_demand
+Date: Jan 2026
+KernelVersion: 6.19
+Contact: linux-edac@vger.kernel.org
+Description:
+ (RW) Start/Stop demand scrubbing. The stop operation is
+ supported only if the driver and hardware support it.
+
What: /sys/bus/edac/devices/<dev-name>/scrubX/enable_background
Date: March 2025
KernelVersion: 6.15
diff --git a/Documentation/edac/scrub.rst b/Documentation/edac/scrub.rst
index 2cfa74fa1ffd2f..562bfd6ff63051 100644
--- a/Documentation/edac/scrub.rst
+++ b/Documentation/edac/scrub.rst
@@ -340,3 +340,73 @@ controller or platform when unexpectedly high error rates are detected.
Sysfs files for scrubbing are documented in
`Documentation/ABI/testing/sysfs-edac-ecs`
+
+3. ACPI RAS2 Hardware-based Memory Scrubbing
+
+3.1. Demand scrubbing for a specific memory region.
+
+3.1.1. Query the status of demand scrubbing
+
+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_demand
+
+0
+
+3.1.2. Query what is device default/current scrub cycle setting.
+
+Applicable to both demand and background scrubbing. The unit of the
+scrub cycle is seconds.
+
+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration
+
+36000
+
+3.1.3. Query the range of device supported scrub cycle for a memory region.
+The unit of the scrub cycle range is seconds.
+
+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/min_cycle_duration
+
+3600
+
+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/max_cycle_duration
+
+86400
+
+3.1.4. Program scrubbing for the memory region in RAS2 device to repeat every
+43200 seconds (half a day).
+
+# echo 43200 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration
+
+3.1.5. Set address range.
+
+Set 'addr' of the memory region to scrub.
+
+# echo 0x80000000 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/addr
+
+Set 'size' of the memory region to scrub.
+
+# echo 0x200000 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/size
+
+3.1.6. Start 'demand scrubbing'.
+
+When a demand scrub is started, any background scrub currently in progress
+will be stopped and then automatically restarted at the beginning when the
+demand scrub has completed.
+
+# echo 1 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_demand
+
+3.2. Background scrubbing the entire memory
+
+3.2.1. Query the status of background scrubbing.
+
+# cat /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_background
+
+0
+
+3.2.2. Program background scrubbing for RAS2 device to repeat in every 21600
+seconds (quarter of a day).
+
+# echo 21600 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/current_cycle_duration
+
+3.2.3. Start 'background scrubbing'.
+
+# echo 1 > /sys/bus/edac/devices/acpi_ras_mem0/scrub0/enable_background
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index f165d14cf61a3a..539ff143ccc522 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -294,6 +294,17 @@ config ACPI_CPPC_LIB
If your platform does not support CPPC in firmware,
leave this option disabled.
+config ACPI_RAS2
+ bool "ACPI RAS2 driver"
+ select AUXILIARY_BUS
+ depends on MAILBOX
+ depends on PCC
+ help
+ Add support for the RAS2 feature table and provide interfaces for
+ platform RAS features, such as hardware-based memory scrubbing.
+
+ If unsure, select N.
+
config ACPI_PROCESSOR
tristate "Processor"
depends on X86 || ARM64 || LOONGARCH || RISCV
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index d1b0affb844f05..abfec6745724e3 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_ACPI_EC_DEBUGFS) += ec_sys.o
obj-$(CONFIG_ACPI_BGRT) += bgrt.o
obj-$(CONFIG_ACPI_CPPC_LIB) += cppc_acpi.o
obj-$(CONFIG_ACPI_SPCR_TABLE) += spcr.o
+obj-$(CONFIG_ACPI_RAS2) += ras2.o
obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
obj-$(CONFIG_ACPI_PPTT) += pptt.o
obj-$(CONFIG_ACPI_PFRUT) += pfr_update.o pfr_telemetry.o
diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
index a30a904f6535f4..8d6a0b773bc57a 100644
--- a/drivers/acpi/bus.c
+++ b/drivers/acpi/bus.c
@@ -31,6 +31,7 @@
#include <acpi/apei.h>
#include <linux/suspend.h>
#include <linux/prmt.h>
+#include <acpi/ras2.h>
#include "internal.h"
@@ -1610,6 +1611,8 @@ static int __init acpi_init(void)
acpi_debugger_init();
acpi_setup_sb_notify_handler();
acpi_viot_init();
+ acpi_ras2_init();
+
return 0;
}
diff --git a/drivers/acpi/ras2.c b/drivers/acpi/ras2.c
new file mode 100644
index 00000000000000..19ecbe7c8199bb
--- /dev/null
+++ b/drivers/acpi/ras2.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI RAS2 feature table driver.
+ *
+ * Copyright (c) 2024-2026 HiSilicon Limited.
+ *
+ * Support for RAS2 table - ACPI 6.5 Specification, section 5.2.21, which
+ * provides interfaces for platform RAS features, e.g., for HW-based memory
+ * scrubbing, and logical to physical address translation service. RAS2 uses
+ * PCC channel subspace for communicating with the ACPI compliant HW platform.
+ */
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ACPI RAS2: " fmt
+
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/iopoll.h>
+#include <linux/ktime.h>
+#include <acpi/pcc.h>
+#include <acpi/ras2.h>
+
+/**
+ * struct ras2_sspcc - Data structure for PCC communication
+ * @mbox_client: struct mbox_client object
+ * @pcc_chan: Pointer to struct pcc_mbox_chan
+ * @comm_addr: Pointer to RAS2 PCC shared memory region
+ * @elem: List for registered RAS2 PCC channel subspaces
+ * @pcc_lock: PCC lock to provide mutually exclusive access
+ * to PCC channel subspace
+ * @deadline_us: Poll PCC status register timeout in microsecs
+ * for PCC command completion
+ * @pcc_mpar: Maximum Periodic Access Rate (MPAR) for PCC channel
+ * @pcc_mrtt: Minimum Request Turnaround Time (MRTT) in microsecs
+ * OS must wait after completion of a PCC command before
+ * issuing next command
+ * @last_cmd_cmpl_time: Completion time of last PCC command
+ * @last_mpar_reset: Time of last MPAR count reset
+ * @mpar_count: MPAR count
+ * @pcc_id: Identifier of the RAS2 platform communication channel
+ * @last_cmd: Last PCC command
+ * @kref: kref object
+ */
+struct ras2_sspcc {
+ struct mbox_client mbox_client;
+ struct pcc_mbox_chan *pcc_chan;
+ struct acpi_ras2_shmem __iomem *comm_addr;
+ struct list_head elem;
+ struct mutex pcc_lock;
+ u64 deadline_us;
+ unsigned int pcc_mpar;
+ unsigned int pcc_mrtt;
+ ktime_t last_cmd_cmpl_time;
+ ktime_t last_mpar_reset;
+ int mpar_count;
+ int pcc_id;
+ u16 last_cmd;
+ struct kref kref;
+};
+
+/*
+ * Arbitrary retries for PCC commands because the remote processor could be
+ * much slower to reply. Keep it high enough to cover emulators where the
+ * processors run painfully slow.
+ */
+#define PCC_NUM_RETRIES 600ULL
+#define PCC_CHNL_DEFAULT_LATENCY 1000
+#define PCC_MIN_POLL_USECS 3
+
+#define RAS2_MAX_NUM_PCC_DESCS 100
+#define RAS2_FEAT_TYPE_MEMORY 0x00
+
+/* Static variables for the RAS2 PCC subspaces */
+static DEFINE_MUTEX(ras2_pcc_list_lock);
+static LIST_HEAD(ras2_sspcc);
+
+static int check_pcc_chan(struct ras2_sspcc *sspcc)
+{
+ struct acpi_ras2_shmem __iomem *gen_comm_base = sspcc->comm_addr;
+ u32 cap_status;
+ u16 status;
+ int rc;
+
+ /*
+ * As per ACPI spec, the PCC space will be initialized by the
+ * platform and should have set the command completion bit when
+ * PCC can be used by OSPM.
+ *
+ * Poll PCC status register every PCC_MIN_POLL_USECS for maximum of
+ * PCC_NUM_RETRIES * PCC channel latency until PCC command complete
+ * bit is set.
+ */
+ rc = readw_relaxed_poll_timeout(&gen_comm_base->status, status,
+ status & PCC_STATUS_CMD_COMPLETE,
+ PCC_MIN_POLL_USECS, sspcc->deadline_us);
+ if (rc) {
+ pr_warn("PCC ID: 0x%x: PCC check channel timeout for last command: 0x%x rc=%d\n",
+ sspcc->pcc_id, sspcc->last_cmd, rc);
+
+ return rc;
+ }
+
+ if (status & PCC_STATUS_ERROR) {
+ pr_warn("PCC ID: 0x%x: Error in executing last command: 0x%x\n",
+ sspcc->pcc_id, sspcc->last_cmd);
+ status &= ~PCC_STATUS_ERROR;
+ iowrite16(status, &gen_comm_base->status);
+ iowrite32(0x0, &gen_comm_base->set_caps_status);
+ return -EIO;
+ }
+
+ /* Ensure get updated PCC status */
+ rmb();
+ cap_status = ioread32(&gen_comm_base->set_caps_status);
+ switch (cap_status) {
+ case ACPI_RAS2_NOT_VALID:
+ case ACPI_RAS2_NOT_SUPPORTED:
+ rc = -EPERM;
+ break;
+ case ACPI_RAS2_BUSY:
+ rc = -EBUSY;
+ break;
+ case ACPI_RAS2_FAILED:
+ case ACPI_RAS2_ABORTED:
+ case ACPI_RAS2_INVALID_DATA:
+ rc = -EINVAL;
+ break;
+ default:
+ rc = 0;
+ }
+
+ iowrite32(0x0, &gen_comm_base->set_caps_status);
+
+ return rc;
+}
+
+/**
+ * ras2_send_pcc_cmd() - Send RAS2 command via PCC channel
+ * @ras2_ctx: pointer to the RAS2 context structure
+ * @cmd: RAS2 command to send
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)
+{
+ struct acpi_ras2_shmem __iomem *gen_comm_base;
+ struct mbox_chan *pcc_channel;
+ struct ras2_sspcc *sspcc;
+ s64 time_delta;
+ u16 val;
+ int rc;
+
+ if (!ras2_ctx)
+ return -EINVAL;
+
+ lockdep_assert_held(ras2_ctx->pcc_lock);
+ sspcc = ras2_ctx->sspcc;
+ gen_comm_base = sspcc->comm_addr;
+
+ rc = check_pcc_chan(sspcc);
+ if (rc < 0)
+ return rc;
+
+ pcc_channel = sspcc->pcc_chan->mchan;
+
+ /*
+ * Handle the Minimum Request Turnaround Time (MRTT): the minimum
+ * amount of time that OSPM must wait after the completion of
+ * a command before issuing the next command, in microseconds.
+ */
+ if (sspcc->pcc_mrtt) {
+ time_delta = ktime_us_delta(ktime_get(), sspcc->last_cmd_cmpl_time);
+ if (sspcc->pcc_mrtt > time_delta)
+ fsleep(sspcc->pcc_mrtt - time_delta);
+ }
+
+ /*
+ * Handle the non-zero Maximum Periodic Access Rate (MPAR): the
+ * maximum number of periodic requests that the subspace channel can
+ * support, reported in commands per minute. 0 indicates no
+ * limitation.
+ *
+ * This parameter should be ideally zero or large enough so that it
+ * can handle maximum number of requests that all the cores in the
+ * system can collectively generate. If it is not, follow the spec and
+ * just not send the request to the platform after hitting the MPAR
+ * limit in any 60s window.
+ */
+ if (sspcc->pcc_mpar) {
+ if (!sspcc->mpar_count) {
+ time_delta = ktime_ms_delta(ktime_get(), sspcc->last_mpar_reset);
+ if ((time_delta < 60 * MSEC_PER_SEC) && sspcc->last_mpar_reset) {
+ dev_dbg(ras2_ctx->dev,
+ "PCC command 0x%x not sent due to MPAR limit", cmd);
+ return -EIO;
+ }
+ sspcc->last_mpar_reset = ktime_get();
+ sspcc->mpar_count = sspcc->pcc_mpar;
+ }
+ sspcc->mpar_count--;
+ }
+
+ /* Write to the shared comm region */
+ iowrite16(cmd, &gen_comm_base->command);
+
+ /* Flip CMD COMPLETE bit */
+ iowrite16(0, &gen_comm_base->status);
+
+ /* Ring doorbell */
+ rc = mbox_send_message(pcc_channel, &cmd);
+ /*
+ * mbox_send_message() returns a non-negative integer for successful submission
+ * and a negative value on failure.
+ */
+ if (rc < 0) {
+ dev_warn(ras2_ctx->dev,
+ "Error sending PCC mbox message command: 0x%x, rc:%d\n", cmd, rc);
+ /* Restore CMD COMPLETE bit on error */
+ val = ioread16(&gen_comm_base->status);
+ val |= PCC_STATUS_CMD_COMPLETE;
+ iowrite16(val, &gen_comm_base->status);
+ return rc;
+ } else {
+ rc = 0;
+ }
+
+ sspcc->last_cmd = cmd;
+
+ /*
+ * If Minimum Request Turnaround Time is non-zero, need to record the
+ * completion time of both READ and WRITE commands for proper handling
+ * of MRTT, so need to check for pcc_mrtt in addition to PCC_CMD_EXEC_RAS2.
+ */
+ if (cmd == PCC_CMD_EXEC_RAS2 || sspcc->pcc_mrtt) {
+ rc = check_pcc_chan(sspcc);
+ if (sspcc->pcc_mrtt)
+ sspcc->last_cmd_cmpl_time = ktime_get();
+ }
+
+ if (!pcc_channel->mbox->txdone_irq)
+ mbox_client_txdone(pcc_channel, rc);
+
+ return rc;
+}
+EXPORT_SYMBOL_FOR_MODULES(ras2_send_pcc_cmd, "acpi_ras2");
+
+static void ras2_list_pcc_release(struct kref *kref)
+{
+ struct ras2_sspcc *sspcc =
+ container_of(kref, struct ras2_sspcc, kref);
+
+ guard(mutex)(&ras2_pcc_list_lock);
+ list_del(&sspcc->elem);
+ pcc_mbox_free_channel(sspcc->pcc_chan);
+ kfree(sspcc);
+}
+
+static void ras2_sspcc_put(struct ras2_sspcc *sspcc)
+{
+ kref_put(&sspcc->kref, &ras2_list_pcc_release);
+}
+
+static struct ras2_sspcc *ras2_sspcc_get(int pcc_id)
+{
+ struct ras2_sspcc *sspcc;
+
+ guard(mutex)(&ras2_pcc_list_lock);
+ list_for_each_entry(sspcc, &ras2_sspcc, elem) {
+ if (sspcc->pcc_id != pcc_id)
+ continue;
+
+ if (!kref_get_unless_zero(&sspcc->kref))
+ break;
+
+ return sspcc;
+ }
+
+ return NULL;
+}
+
+static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)
+{
+ struct pcc_mbox_chan *pcc_chan;
+ struct ras2_sspcc *sspcc;
+
+ if (pcc_id < 0)
+ return -EINVAL;
+
+ sspcc = ras2_sspcc_get(pcc_id);
+ if (sspcc) {
+ ras2_ctx->sspcc = sspcc;
+ ras2_ctx->comm_addr = sspcc->comm_addr;
+ ras2_ctx->dev =
+ sspcc->pcc_chan->mchan->mbox->dev;
+ ras2_ctx->pcc_lock = &sspcc->pcc_lock;
+ return 0;
+ }
+
+ sspcc = kzalloc(sizeof(*sspcc), GFP_KERNEL);
+ if (!sspcc)
+ return -ENOMEM;
+
+ pcc_chan = pcc_mbox_request_channel(&sspcc->mbox_client, pcc_id);
+ if (IS_ERR(pcc_chan)) {
+ kfree(sspcc);
+ return PTR_ERR(pcc_chan);
+ }
+
+ if (!pcc_chan->shmem) {
+ pcc_mbox_free_channel(pcc_chan);
+ kfree(sspcc);
+ return -EINVAL;
+ }
+
+ sspcc->pcc_id = pcc_id;
+ sspcc->pcc_chan = pcc_chan;
+ sspcc->comm_addr = pcc_chan->shmem;
+ if (pcc_chan->latency)
+ sspcc->deadline_us = PCC_NUM_RETRIES * pcc_chan->latency;
+ else
+ sspcc->deadline_us = PCC_NUM_RETRIES * PCC_CHNL_DEFAULT_LATENCY;
+ sspcc->pcc_mrtt = pcc_chan->min_turnaround_time;
+ sspcc->pcc_mpar = pcc_chan->max_access_rate;
+ sspcc->mbox_client.knows_txdone = true;
+
+ kref_init(&sspcc->kref);
+
+ mutex_lock(&ras2_pcc_list_lock);
+ list_add(&sspcc->elem, &ras2_sspcc);
+ mutex_unlock(&ras2_pcc_list_lock);
+
+ ras2_ctx->sspcc = sspcc;
+ ras2_ctx->comm_addr = sspcc->comm_addr;
+ ras2_ctx->dev = pcc_chan->mchan->mbox->dev;
+
+ mutex_init(&sspcc->pcc_lock);
+ ras2_ctx->pcc_lock = &sspcc->pcc_lock;
+
+ return 0;
+}
+
+static DEFINE_IDA(ras2_ida);
+static void ras2_release(struct device *device)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(device);
+ struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+
+ ida_free(&ras2_ida, auxdev->id);
+ ras2_sspcc_put(ras2_ctx->sspcc);
+ kfree(ras2_ctx);
+}
+
+static struct ras2_mem_ctx *add_aux_device(char *name, int channel, u32 pxm_inst)
+{
+ struct ras2_mem_ctx *ras2_ctx;
+ u32 comp_nid;
+ int id, rc;
+
+ comp_nid = pxm_to_node(pxm_inst);
+ if (comp_nid == NUMA_NO_NODE) {
+ pr_debug("Invalid NUMA node, channel=%d pxm_inst=%d\n", channel, pxm_inst);
+ return ERR_PTR(-ENXIO);
+ }
+
+ ras2_ctx = kzalloc(sizeof(*ras2_ctx), GFP_KERNEL);
+ if (!ras2_ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ras2_ctx->sys_comp_nid = comp_nid;
+
+ rc = register_pcc_channel(ras2_ctx, channel);
+ if (rc < 0) {
+ pr_debug("Failed to register PCC channel=%d pxm_inst=%d rc=%d\n", channel,
+ pxm_inst, rc);
+ goto ctx_free;
+ }
+
+ id = ida_alloc(&ras2_ida, GFP_KERNEL);
+ if (id < 0) {
+ rc = id;
+ goto pcc_free;
+ }
+
+ ras2_ctx->adev.id = id;
+ ras2_ctx->adev.name = name;
+ ras2_ctx->adev.dev.release = ras2_release;
+ ras2_ctx->adev.dev.parent = ras2_ctx->dev;
+
+ rc = auxiliary_device_init(&ras2_ctx->adev);
+ if (rc)
+ goto ida_free;
+
+ rc = auxiliary_device_add(&ras2_ctx->adev);
+ if (rc) {
+ auxiliary_device_uninit(&ras2_ctx->adev);
+ return ERR_PTR(rc);
+ }
+
+ return ras2_ctx;
+
+ida_free:
+ ida_free(&ras2_ida, id);
+pcc_free:
+ ras2_sspcc_put(ras2_ctx->sspcc);
+ctx_free:
+ kfree(ras2_ctx);
+
+ return ERR_PTR(rc);
+}
+
+static void remove_aux_device(struct ras2_mem_ctx *ras2_ctx)
+{
+ if (!ras2_ctx)
+ return;
+
+ auxiliary_device_delete(&ras2_ctx->adev);
+ auxiliary_device_uninit(&ras2_ctx->adev);
+}
+
+static int parse_ras2_table(struct acpi_table_ras2 *ras2_tab)
+{
+ struct acpi_ras2_pcc_desc *pcc_desc_list;
+ struct ras2_mem_ctx **pctx_list;
+ struct ras2_mem_ctx *ras2_ctx;
+ u16 tot_tbl_len;
+ u16 i;
+
+ if (ras2_tab->header.length < sizeof(*ras2_tab)) {
+ pr_warn(FW_WARN "ACPI RAS2 table present but broken (too short, size=%u)\n",
+ ras2_tab->header.length);
+ return -EINVAL;
+ }
+
+ if (!ras2_tab->num_pcc_descs || ras2_tab->num_pcc_descs > RAS2_MAX_NUM_PCC_DESCS) {
+ pr_warn(FW_WARN "No/Invalid number of PCC descs(%d) in ACPI RAS2 table\n",
+ ras2_tab->num_pcc_descs);
+ return -EINVAL;
+ }
+
+ tot_tbl_len = sizeof(*ras2_tab) + ras2_tab->num_pcc_descs * sizeof(*pcc_desc_list);
+ if (ras2_tab->header.length < tot_tbl_len) {
+ pr_warn(FW_WARN "RAS2 table is not large enough to contain PCC descs=%d size=%u)\n",
+ ras2_tab->num_pcc_descs, ras2_tab->header.length);
+ return -EINVAL;
+ }
+
+ pctx_list = kcalloc(ras2_tab->num_pcc_descs, sizeof(*pctx_list), GFP_KERNEL);
+ if (!pctx_list)
+ return -ENOMEM;
+
+ pcc_desc_list = (struct acpi_ras2_pcc_desc *)(ras2_tab + 1);
+ for (i = 0; i < ras2_tab->num_pcc_descs; i++, pcc_desc_list++) {
+ if (pcc_desc_list->feature_type != RAS2_FEAT_TYPE_MEMORY)
+ continue;
+
+ ras2_ctx = add_aux_device(RAS2_MEM_DEV_ID_NAME, pcc_desc_list->channel_id,
+ pcc_desc_list->instance);
+ /* Invalid NUMA node, continue parsing next node */
+ if (PTR_ERR(ras2_ctx) == -ENXIO)
+ continue;
+
+ if (IS_ERR(ras2_ctx)) {
+ pr_warn("Failed to add RAS2 auxiliary device rc=%ld\n", PTR_ERR(ras2_ctx));
+ for (; i > 0; i--) {
+ if (pctx_list[i - 1])
+ remove_aux_device(pctx_list[i - 1]);
+ }
+ kfree(pctx_list);
+ return PTR_ERR(ras2_ctx);
+ }
+ pctx_list[i] = ras2_ctx;
+ }
+ kfree(pctx_list);
+
+ return 0;
+}
+
+/**
+ * acpi_ras2_init - RAS2 driver initialization function.
+ *
+ * Extracts the ACPI RAS2 table and retrieves ID for the PCC channel subspace
+ * for communicating with the ACPI compliant HW platform. Driver adds an
+ * auxiliary device, which binds to the memory ACPI RAS2 driver, for each RAS2
+ * memory feature.
+ *
+ * Returns: none.
+ */
+void __init acpi_ras2_init(void)
+{
+ struct acpi_table_ras2 *ras2_tab;
+ acpi_status status;
+
+ status = acpi_get_table(ACPI_SIG_RAS2, 0, (struct acpi_table_header **)&ras2_tab);
+ if (ACPI_FAILURE(status)) {
+ pr_debug("Failed to get table, %s\n", acpi_format_exception(status));
+ return;
+ }
+
+ if (parse_ras2_table(ras2_tab))
+ pr_debug("Failed to parse RAS2 table\n");
+
+ acpi_put_table((struct acpi_table_header *)ras2_tab);
+}
diff --git a/drivers/edac/scrub.c b/drivers/edac/scrub.c
index f9d02af2fc3a20..f3b9a2f049500e 100644
--- a/drivers/edac/scrub.c
+++ b/drivers/edac/scrub.c
@@ -14,6 +14,7 @@ enum edac_scrub_attributes {
SCRUB_ADDRESS,
SCRUB_SIZE,
SCRUB_ENABLE_BACKGROUND,
+ SCRUB_ENABLE_DEMAND,
SCRUB_MIN_CYCLE_DURATION,
SCRUB_MAX_CYCLE_DURATION,
SCRUB_CUR_CYCLE_DURATION,
@@ -55,6 +56,7 @@ static ssize_t attrib##_show(struct device *ras_feat_dev, \
EDAC_SCRUB_ATTR_SHOW(addr, read_addr, u64, "0x%llx\n")
EDAC_SCRUB_ATTR_SHOW(size, read_size, u64, "0x%llx\n")
EDAC_SCRUB_ATTR_SHOW(enable_background, get_enabled_bg, bool, "%u\n")
+EDAC_SCRUB_ATTR_SHOW(enable_demand, get_enabled_od, bool, "%u\n")
EDAC_SCRUB_ATTR_SHOW(min_cycle_duration, get_min_cycle, u32, "%u\n")
EDAC_SCRUB_ATTR_SHOW(max_cycle_duration, get_max_cycle, u32, "%u\n")
EDAC_SCRUB_ATTR_SHOW(current_cycle_duration, get_cycle_duration, u32, "%u\n")
@@ -84,6 +86,7 @@ static ssize_t attrib##_store(struct device *ras_feat_dev, \
EDAC_SCRUB_ATTR_STORE(addr, write_addr, u64, kstrtou64)
EDAC_SCRUB_ATTR_STORE(size, write_size, u64, kstrtou64)
EDAC_SCRUB_ATTR_STORE(enable_background, set_enabled_bg, unsigned long, kstrtoul)
+EDAC_SCRUB_ATTR_STORE(enable_demand, set_enabled_od, unsigned long, kstrtoul)
EDAC_SCRUB_ATTR_STORE(current_cycle_duration, set_cycle_duration, unsigned long, kstrtoul)
static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)
@@ -119,6 +122,14 @@ static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int
return 0444;
}
break;
+ case SCRUB_ENABLE_DEMAND:
+ if (ops->get_enabled_od) {
+ if (ops->set_enabled_od)
+ return a->mode;
+ else
+ return 0444;
+ }
+ break;
case SCRUB_MIN_CYCLE_DURATION:
if (ops->get_min_cycle)
return a->mode;
@@ -164,6 +175,7 @@ static int scrub_create_desc(struct device *scrub_dev,
[SCRUB_ADDRESS] = EDAC_SCRUB_ATTR_RW(addr, instance),
[SCRUB_SIZE] = EDAC_SCRUB_ATTR_RW(size, instance),
[SCRUB_ENABLE_BACKGROUND] = EDAC_SCRUB_ATTR_RW(enable_background, instance),
+ [SCRUB_ENABLE_DEMAND] = EDAC_SCRUB_ATTR_RW(enable_demand, instance),
[SCRUB_MIN_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(min_cycle_duration, instance),
[SCRUB_MAX_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(max_cycle_duration, instance),
[SCRUB_CUR_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RW(current_cycle_duration, instance)
diff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig
index fc4f4bb94a4c65..a1e6aed8bcc859 100644
--- a/drivers/ras/Kconfig
+++ b/drivers/ras/Kconfig
@@ -46,4 +46,17 @@ config RAS_FMPM
Memory will be retired during boot time and run time depending on
platform-specific policies.
+config MEM_ACPI_RAS2
+ tristate "Memory ACPI RAS2 driver"
+ depends on ACPI_RAS2
+ depends on EDAC
+ depends on EDAC_SCRUB
+ select NUMA_KEEP_MEMINFO
+ help
+ The driver binds to the auxiliary device added by the ACPI RAS2
+ feature table parser. The driver uses a PCC channel subspace to
+ communicate with the ACPI-compliant platform and provides
+ control of the HW-based memory scrubber parameters to the user
+ through the EDAC scrub interface.
+
endif
diff --git a/drivers/ras/Makefile b/drivers/ras/Makefile
index 11f95d59d3972d..a0e6e903d6b09b 100644
--- a/drivers/ras/Makefile
+++ b/drivers/ras/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_RAS) += ras.o
obj-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_RAS_CEC) += cec.o
+obj-$(CONFIG_MEM_ACPI_RAS2) += acpi_ras2.o
obj-$(CONFIG_RAS_FMPM) += amd/fmpm.o
obj-y += amd/atl/
diff --git a/drivers/ras/acpi_ras2.c b/drivers/ras/acpi_ras2.c
new file mode 100644
index 00000000000000..2a7de226ec8592
--- /dev/null
+++ b/drivers/ras/acpi_ras2.c
@@ -0,0 +1,589 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI RAS2 memory driver
+ *
+ * Copyright (c) 2024-2026 HiSilicon Limited.
+ *
+ */
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ACPI RAS2 MEMORY: " fmt
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/edac.h>
+#include <linux/kthread.h>
+#include <linux/platform_device.h>
+#include <acpi/ras2.h>
+
+#define RAS2_SUPPORT_HW_PARTOL_SCRUB BIT(0)
+#define RAS2_TYPE_PATROL_SCRUB 0x0000
+
+#define RAS2_GET_PATROL_PARAMETERS 0x01
+#define RAS2_START_PATROL_SCRUBBER 0x02
+#define RAS2_STOP_PATROL_SCRUBBER 0x03
+
+/*
+ * RAS2 patrol scrub
+ */
+#define RAS2_PS_SC_HRS_IN_MASK GENMASK(15, 8)
+#define RAS2_PS_EN_BACKGROUND BIT(0)
+#define RAS2_PS_SC_HRS_OUT_MASK GENMASK(7, 0)
+#define RAS2_PS_MIN_SC_HRS_OUT_MASK GENMASK(15, 8)
+#define RAS2_PS_MAX_SC_HRS_OUT_MASK GENMASK(23, 16)
+#define RAS2_PS_FLAG_SCRUB_RUNNING BIT(0)
+
+#define RAS2_SCRUB_NAME_LEN 128
+#define RAS2_HOUR_IN_SECS 3600
+
+struct acpi_ras2_ps_shared_mem {
+ struct acpi_ras2_shmem common;
+ struct acpi_ras2_patrol_scrub_param params;
+};
+
+#define TO_ACPI_RAS2_PS_SHMEM(_addr) \
+ container_of(_addr, struct acpi_ras2_ps_shared_mem, common)
+
+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable);
+
+static int ras2_is_patrol_scrub_support(struct ras2_mem_ctx *ras2_ctx)
+{
+ struct acpi_ras2_shmem __iomem *common = (void *)ras2_ctx->comm_addr;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ iowrite8(0, &common->set_caps[0]);
+
+ return ioread8(&common->features[0]) & RAS2_SUPPORT_HW_PARTOL_SCRUB;
+}
+
+static int ras2_update_patrol_scrub_params_cache(struct ras2_mem_ctx *ras2_ctx)
+{
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm =
+ TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+ u32 scrub_params_out;
+ int ret;
+
+ iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+ iowrite16(RAS2_GET_PATROL_PARAMETERS, &ps_sm->params.command);
+ iowrite64(ras2_ctx->mem_base, &ps_sm->params.req_addr_range[0]);
+ iowrite64(ras2_ctx->mem_size, &ps_sm->params.req_addr_range[1]);
+ ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "Failed to read patrol scrub parameters\n");
+ return ret;
+ }
+
+ scrub_params_out = ioread32(&ps_sm->params.scrub_params_out);
+ ras2_ctx->min_scrub_cycle = FIELD_GET(RAS2_PS_MIN_SC_HRS_OUT_MASK,
+ scrub_params_out);
+ ras2_ctx->max_scrub_cycle = FIELD_GET(RAS2_PS_MAX_SC_HRS_OUT_MASK,
+ scrub_params_out);
+ ras2_ctx->scrub_cycle_hrs = FIELD_GET(RAS2_PS_SC_HRS_OUT_MASK,
+ scrub_params_out);
+ if (ras2_ctx->bg_scrub) {
+ ras2_ctx->od_scrub = false;
+ ras2_ctx->base = 0;
+ ras2_ctx->size = 0;
+ return 0;
+ }
+
+ if (ioread32(&ps_sm->params.flags) & RAS2_PS_FLAG_SCRUB_RUNNING) {
+ ras2_ctx->od_scrub = true;
+ ras2_ctx->base = ioread64(&ps_sm->params.actl_addr_range[0]);
+ ras2_ctx->size = ioread64(&ps_sm->params.actl_addr_range[1]);
+ } else {
+ ras2_ctx->od_scrub = false;
+ }
+
+ return 0;
+}
+
+/* Context - PCC lock must be held */
+static int ras2_get_demand_scrub_running(struct ras2_mem_ctx *ras2_ctx, bool *running)
+{
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm =
+ TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+ int ret;
+
+ if (!ras2_ctx->od_scrub) {
+ *running = false;
+ return 0;
+ }
+
+ iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+ iowrite16(RAS2_GET_PATROL_PARAMETERS, &ps_sm->params.command);
+ iowrite64(ras2_ctx->mem_base, &ps_sm->params.req_addr_range[0]);
+ iowrite64(ras2_ctx->mem_size, &ps_sm->params.req_addr_range[1]);
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "Failed to read patrol scrub parameters\n");
+ return ret;
+ }
+
+ *running = ioread32(&ps_sm->params.flags) & RAS2_PS_FLAG_SCRUB_RUNNING;
+ if (!(*running))
+ ras2_ctx->od_scrub = false;
+
+ return 0;
+}
+
+static int ras2_scrub_monitor_thread(void *p)
+{
+ struct ras2_mem_ctx *ras2_ctx = (struct ras2_mem_ctx *)p;
+ bool running;
+ int ret;
+
+ while (!kthread_should_stop()) {
+ if (!ras2_ctx->reenable_bg_scrub)
+ break;
+
+ /*
+ * If ras2_get_demand_scrub_running() fails here, re-enabling background
+ * scrubbing immediately may not be possible or correct. In that case,
+ * the admin or firmware may need to re-enable background scrubbing
+ * after demand scrubbing has finished.
+ */
+ mutex_lock(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret) {
+ mutex_unlock(ras2_ctx->pcc_lock);
+ break;
+ }
+
+ if (!running) {
+ ret = __ras2_hw_scrub_set_enabled_bg(ras2_ctx->dev, ras2_ctx, true);
+ if (ret)
+ dev_err(ras2_ctx->dev,
+ "Failed to enable background scrub ret=%d\n", ret);
+
+ mutex_unlock(ras2_ctx->pcc_lock);
+ break;
+ }
+
+ mutex_unlock(ras2_ctx->pcc_lock);
+ msleep(1000);
+ }
+
+ mutex_lock(ras2_ctx->pcc_lock);
+ ras2_ctx->thread = NULL;
+ mutex_unlock(ras2_ctx->pcc_lock);
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_min_scrub_cycle(struct device *dev, void *drv_data, u32 *min)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ *min = ras2_ctx->min_scrub_cycle * RAS2_HOUR_IN_SECS;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_max_scrub_cycle(struct device *dev, void *drv_data, u32 *max)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ *max = ras2_ctx->max_scrub_cycle * RAS2_HOUR_IN_SECS;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_cycle_read(struct device *dev, void *drv_data, u32 *scrub_cycle_secs)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ *scrub_cycle_secs = ras2_ctx->scrub_cycle_hrs * RAS2_HOUR_IN_SECS;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_cycle_write(struct device *dev, void *drv_data, u32 scrub_cycle_secs)
+{
+ u32 scrub_cycle_hrs = scrub_cycle_secs / RAS2_HOUR_IN_SECS;
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ bool running;
+ int ret;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ if (running)
+ return -EBUSY;
+
+ if (scrub_cycle_hrs < ras2_ctx->min_scrub_cycle ||
+ scrub_cycle_hrs > ras2_ctx->max_scrub_cycle)
+ return -EINVAL;
+
+ ras2_ctx->set_scrub_cycle = scrub_cycle_hrs;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_addr(struct device *dev, void *drv_data, u64 *base)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ int ret;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ /*
+ * When BG scrubbing is enabled the actual address range is not valid.
+ * Return -EBUSY now unless find out a method to retrieve actual full PA range.
+ */
+ if (ras2_ctx->bg_scrub)
+ return -EBUSY;
+
+ ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+ if (ret)
+ return ret;
+
+ *base = ras2_ctx->base;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_size(struct device *dev, void *drv_data, u64 *size)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ int ret;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ if (ras2_ctx->bg_scrub)
+ return -EBUSY;
+
+ ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+ if (ret)
+ return ret;
+
+ *size = ras2_ctx->size;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_write_addr(struct device *dev, void *drv_data, u64 base)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ bool running;
+ int ret;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ if (running)
+ return -EBUSY;
+
+ ras2_ctx->base = base;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_write_size(struct device *dev, void *drv_data, u64 size)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ bool running;
+ int ret;
+
+ if (!size)
+ return -EINVAL;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ if (running)
+ return -EBUSY;
+
+ ras2_ctx->size = size;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_get_enabled_bg(struct device *dev, void *drv_data, bool *enabled)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ *enabled = ras2_ctx->bg_scrub;
+
+ return 0;
+}
+
+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+ u32 scrub_params_in;
+ bool running;
+ int ret;
+
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+ if (enable) {
+ if (ras2_ctx->bg_scrub || running)
+ return -EBUSY;
+
+ iowrite64(0, &ps_sm->params.req_addr_range[0]);
+ iowrite64(0, &ps_sm->params.req_addr_range[1]);
+ scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+ scrub_params_in &= ~RAS2_PS_SC_HRS_IN_MASK;
+ scrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx->set_scrub_cycle);
+ iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+ iowrite16(RAS2_START_PATROL_SCRUBBER, &ps_sm->params.command);
+ } else {
+ if (!ras2_ctx->bg_scrub)
+ return -EPERM;
+
+ iowrite16(RAS2_STOP_PATROL_SCRUBBER, &ps_sm->params.command);
+ }
+
+ scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+ scrub_params_in &= ~RAS2_PS_EN_BACKGROUND;
+ scrub_params_in |= FIELD_PREP(RAS2_PS_EN_BACKGROUND, enable);
+ iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+ ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+ if (ret) {
+ dev_err(dev, "Failed to %s background scrubbing\n",
+ str_enable_disable(enable));
+ return ret;
+ }
+
+ ras2_ctx->bg_scrub = enable;
+ if (enable) {
+ ras2_ctx->reenable_bg_scrub = false;
+ /* Update the cache to account for rounding of supplied parameters and similar */
+ return ras2_update_patrol_scrub_params_cache(ras2_ctx);
+ }
+
+ return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+
+ return __ras2_hw_scrub_set_enabled_bg(dev, drv_data, enable);
+}
+
+static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ bool running;
+ int ret;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ *enabled = running;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)
+{
+ struct ras2_mem_ctx *ras2_ctx = drv_data;
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+ u32 scrub_params_in;
+ bool running;
+ int ret;
+
+ if (!enable)
+ return -EOPNOTSUPP;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+ if (running)
+ return -EBUSY;
+
+ /* Stop any background scrub currently in progress */
+ if (ras2_ctx->bg_scrub) {
+ ret = __ras2_hw_scrub_set_enabled_bg(dev, drv_data, false);
+ if (ret)
+ return ret;
+
+ ras2_ctx->reenable_bg_scrub = true;
+ }
+
+ /*
+ * The validity checks for the address range to scrub need to be updated
+ * with (base + size) > (mem_base + mem_size) check in the future once a
+ * proper method for determining the NUMA node memory range is available.
+ */
+ if (!ras2_ctx->size || ras2_ctx->base < ras2_ctx->mem_base) {
+ dev_err(dev, "%s: Invalid address range, base=0x%llx size=0x%llx\n",
+ __func__, ras2_ctx->base, ras2_ctx->size);
+ ret = -ERANGE;
+ goto enable_bg_scrub;
+ }
+
+ iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+ scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+ scrub_params_in &= ~RAS2_PS_SC_HRS_IN_MASK;
+ scrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx->set_scrub_cycle);
+ scrub_params_in &= ~RAS2_PS_EN_BACKGROUND;
+ iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+ iowrite64(ras2_ctx->base, &ps_sm->params.req_addr_range[0]);
+ iowrite64(ras2_ctx->size, &ps_sm->params.req_addr_range[1]);
+ iowrite16(RAS2_START_PATROL_SCRUBBER, &ps_sm->params.command);
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+ if (ret) {
+ dev_err(dev, "Failed to start demand scrubbing rc(%d)\n", ret);
+ if (ret != -EBUSY) {
+ iowrite64(0, &ps_sm->params.req_addr_range[0]);
+ iowrite64(0, &ps_sm->params.req_addr_range[1]);
+ ras2_ctx->od_scrub = false;
+ ras2_ctx->base = 0;
+ ras2_ctx->size = 0;
+ }
+ goto enable_bg_scrub;
+ }
+
+ ras2_ctx->od_scrub = enable;
+
+ ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+
+ if (ras2_ctx->reenable_bg_scrub && ras2_ctx->driver_active && !ras2_ctx->thread) {
+ ras2_ctx->thread = kthread_run(ras2_scrub_monitor_thread, ras2_ctx,
+ "ras2_scrub_nid%d", ras2_ctx->sys_comp_nid);
+ if (IS_ERR(ras2_ctx->thread)) {
+ ret = PTR_ERR(ras2_ctx->thread);
+ ras2_ctx->thread = NULL;
+ /*
+ * If kthread_run() fails and the demand scrubbing has started running,
+ * re-enabling background scrub will fail. Thus admin/firmware may need
+ * to re-enable background scrub after demand scrubbing has finished.
+ */
+ goto enable_bg_scrub;
+ }
+ }
+
+ return ret;
+
+enable_bg_scrub:
+ if (ras2_ctx->reenable_bg_scrub) {
+ ras2_ctx->reenable_bg_scrub = false;
+ __ras2_hw_scrub_set_enabled_bg(dev, drv_data, true);
+ }
+
+ return ret;
+}
+
+static const struct edac_scrub_ops ras2_scrub_ops = {
+ .read_addr = ras2_hw_scrub_read_addr,
+ .read_size = ras2_hw_scrub_read_size,
+ .write_addr = ras2_hw_scrub_write_addr,
+ .write_size = ras2_hw_scrub_write_size,
+ .get_enabled_bg = ras2_hw_scrub_get_enabled_bg,
+ .set_enabled_bg = ras2_hw_scrub_set_enabled_bg,
+ .get_enabled_od = ras2_hw_scrub_get_enabled_od,
+ .set_enabled_od = ras2_hw_scrub_set_enabled_od,
+ .get_min_cycle = ras2_hw_scrub_read_min_scrub_cycle,
+ .get_max_cycle = ras2_hw_scrub_read_max_scrub_cycle,
+ .get_cycle_duration = ras2_hw_scrub_cycle_read,
+ .set_cycle_duration = ras2_hw_scrub_cycle_write,
+};
+
+static void ras2_mem_drv_remove(struct auxiliary_device *auxdev)
+{
+ struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+
+ if (!ras2_ctx)
+ return;
+
+ mutex_lock(ras2_ctx->pcc_lock);
+ ras2_ctx->driver_active = false;
+ if (ras2_ctx->thread) {
+ mutex_unlock(ras2_ctx->pcc_lock);
+ kthread_stop(ras2_ctx->thread);
+ ras2_ctx->thread = NULL;
+ return;
+ }
+ mutex_unlock(ras2_ctx->pcc_lock);
+}
+
+static int ras2_mem_drv_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+ struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+ struct edac_dev_feature ras_features;
+ char scrub_name[RAS2_SCRUB_NAME_LEN];
+ unsigned long start_pfn, num_spanned_pages;
+ int ret;
+
+ if (!ras2_is_patrol_scrub_support(ras2_ctx))
+ return -EOPNOTSUPP;
+
+ /*
+ * Retrieve the PA range of the NUMA domain and use it as the
+ * 'Requested Address Range', when send RAS2 command
+ * GET_PATROL_PARAMETERS to get parameters that apply to all addresses
+ * in the NUMA domain as well as when send command START_PATROL_SCRUBBER
+ * to start the demand scrubbing.
+ */
+ start_pfn = node_start_pfn(ras2_ctx->sys_comp_nid);
+ num_spanned_pages = node_spanned_pages(ras2_ctx->sys_comp_nid);
+ if (!num_spanned_pages) {
+ pr_debug("Failed to find PA range of NUMA node(%u)\n", ras2_ctx->sys_comp_nid);
+ return -EPERM;
+ }
+
+ ras2_ctx->mem_base = __pfn_to_phys(start_pfn);
+ ras2_ctx->mem_size = (u64)num_spanned_pages * PAGE_SIZE;
+ guard(mutex)(ras2_ctx->pcc_lock);
+ ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+ if (ret)
+ return ret;
+
+ /* Initialize set_scrub_cycle */
+ if (ras2_ctx->scrub_cycle_hrs > ras2_ctx->min_scrub_cycle)
+ ras2_ctx->set_scrub_cycle = ras2_ctx->scrub_cycle_hrs;
+ else
+ ras2_ctx->set_scrub_cycle = ras2_ctx->min_scrub_cycle;
+
+ sprintf(scrub_name, "acpi_ras_mem%d", auxdev->id);
+
+ ras_features.ft_type = RAS_FEAT_SCRUB;
+ ras_features.instance = 0;
+ ras_features.scrub_ops = &ras2_scrub_ops;
+ ras_features.ctx = ras2_ctx;
+
+ ras2_ctx->driver_active = true;
+
+ return edac_dev_register(&auxdev->dev, scrub_name, NULL, 1, &ras_features);
+}
+
+static const struct auxiliary_device_id ras2_mem_dev_id_table[] = {
+ { .name = RAS2_AUX_DEV_NAME "." RAS2_MEM_DEV_ID_NAME, },
+ { }
+};
+
+MODULE_DEVICE_TABLE(auxiliary, ras2_mem_dev_id_table);
+
+static struct auxiliary_driver ras2_mem_driver = {
+ .name = RAS2_MEM_DEV_ID_NAME,
+ .probe = ras2_mem_drv_probe,
+ .remove = ras2_mem_drv_remove,
+ .id_table = ras2_mem_dev_id_table,
+};
+module_auxiliary_driver(ras2_mem_driver);
+
+MODULE_IMPORT_NS("ACPI_RAS2");
+MODULE_DESCRIPTION("ACPI RAS2 memory driver");
+MODULE_LICENSE("GPL");
diff --git a/include/acpi/ras2.h b/include/acpi/ras2.h
new file mode 100644
index 00000000000000..ac75b0307fa9cc
--- /dev/null
+++ b/include/acpi/ras2.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ACPI RAS2 (RAS Feature Table) methods.
+ *
+ * Copyright (c) 2024-2026 HiSilicon Limited
+ */
+
+#ifndef _ACPI_RAS2_H
+#define _ACPI_RAS2_H
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+struct device;
+
+/*
+ * ACPI spec 6.5 Table 5.82: PCC command codes used by
+ * RAS2 platform communication channel.
+ */
+#define PCC_CMD_EXEC_RAS2 0x01
+
+#define RAS2_AUX_DEV_NAME "ras2"
+#define RAS2_MEM_DEV_ID_NAME "acpi_ras2_mem"
+
+/**
+ * struct ras2_mem_ctx - Context for RAS2 memory features
+ * @adev: Auxiliary device object
+ * @comm_addr: Pointer to RAS2 PCC shared memory region
+ * @dev: Pointer to device backing struct mbox_controller for PCC
+ * @sspcc: Pointer to local data structure for PCC communication
+ * @pcc_lock: Pointer to PCC lock to provide mutually exclusive access
+ * to PCC channel subspace
+ * @sys_comp_nid: Node ID of the system component that the RAS feature
+ * is associated with. See ACPI spec 6.5 Table 5.80: RAS2
+ * Platform Communication Channel Descriptor format,
+ * Field: Instance
+ * @mem_base: Base of the lowest physical continuous memory range
+ * of the memory associated with the NUMA domain
+ * @mem_size Size of the lowest physical continuous memory range
+ * of the memory associated with the NUMA domain
+ * @base: Base address of the memory region to scrub
+ * @size: Size of the memory region to scrub
+ * @scrub_cycle_hrs: Current scrub rate in hours
+ * @set_scrub_cycle: Scrub rate to set in hours
+ * @min_scrub_cycle: Minimum scrub rate supported
+ * @max_scrub_cycle: Maximum scrub rate supported
+ * @od_scrub: Status of demand scrubbing (memory region)
+ * @bg_scrub: Status of background patrol scrubbing
+ * @reenable_bg_scrub: Flag indicates restart background scrubbing after demand
+ * scrubbing is finished
+ * @thread: Demand scrub monitor kthread
+ * @driver_active: Flag indicates RAS2 memory driver is active/removed
+ */
+struct ras2_mem_ctx {
+ struct auxiliary_device adev;
+ struct acpi_ras2_shmem __iomem *comm_addr;
+ struct device *dev;
+ void *sspcc;
+ struct mutex *pcc_lock;
+ u32 sys_comp_nid;
+ u64 mem_base;
+ u64 mem_size;
+ u64 base;
+ u64 size;
+ u8 scrub_cycle_hrs;
+ u8 set_scrub_cycle;
+ u8 min_scrub_cycle;
+ u8 max_scrub_cycle;
+ bool od_scrub;
+ bool bg_scrub;
+ bool reenable_bg_scrub;
+ struct task_struct *thread;
+ bool driver_active;
+};
+
+#ifdef CONFIG_ACPI_RAS2
+void __init acpi_ras2_init(void);
+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd);
+#else
+static inline void acpi_ras2_init(void) { }
+#endif
+
+#endif /* _ACPI_RAS2_H */
diff --git a/include/linux/edac.h b/include/linux/edac.h
index e6b4e51130e5ff..be4f6e52ea38a0 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -684,6 +684,8 @@ enum edac_dev_feat {
* @write_size: set offset of the scrubbing range.
* @get_enabled_bg: check if currently performing background scrub.
* @set_enabled_bg: start or stop a bg-scrub.
+ * @get_enabled_od: check if currently performing demand scrub.
+ * @set_enabled_od: start or stop a demand-scrub.
* @get_min_cycle: get minimum supported scrub cycle duration in seconds.
* @get_max_cycle: get maximum supported scrub cycle duration in seconds.
* @get_cycle_duration: get current scrub cycle duration in seconds.
@@ -696,6 +698,8 @@ struct edac_scrub_ops {
int (*write_size)(struct device *dev, void *drv_data, u64 size);
int (*get_enabled_bg)(struct device *dev, void *drv_data, bool *enable);
int (*set_enabled_bg)(struct device *dev, void *drv_data, bool enable);
+ int (*get_enabled_od)(struct device *dev, void *drv_data, bool *enable);
+ int (*set_enabled_od)(struct device *dev, void *drv_data, bool enable);
int (*get_min_cycle)(struct device *dev, void *drv_data, u32 *min);
int (*get_max_cycle)(struct device *dev, void *drv_data, u32 *max);
int (*get_cycle_duration)(struct device *dev, void *drv_data, u32 *cycle);
|
| 3/2 | 2026/07/06 00:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20559 output: 21 thoughts: 727 Thoughts: **Analyzing the ACPI RAS2 Driver Patch** Okay, so I'm looking at this patch series that adds ACPI RAS2 support and a memory driver. It looks pretty straightforward: documentation updates, Kconfig and Makefile changes, some new files (`ras2.c`, `acpi_ras2.c`, `ras2.h`), and modifications to existing drivers (`bus.c`, `scrub.c`). It's all about enabling hardware-based memory scrubbing using the ACPI RAS2 table and the Platform Communication Channel (PCC). The driver integrates with EDAC and exposes sysfs attributes for control. The big question in my mind is always, how does this interact with virtualization? Does this even *work* in environments like QEMU or GCE? The core dependency is the presence of an ACPI RAS2 table. My immediate concern is, does the firmware *provide* that table in a virtualized context? I know QEMU often doesn't have it by default. I have to think about syzkaller. Can syzkaller inject ACPI tables? I know it does, but is it able to load an ACPI RAS2 table? Wait, syzkaller *can't* inject arbitrary tables unless it's specifically supported. It does have ACPI fuzzing, but it's not like a blanket injection. Okay, I see. The driver uses `acpi_get_table(ACPI_SIG_RAS2, ...)`. If the table *isn't* present, `acpi_ras2_init` just returns. That's good defensive programming, but is that reachable in syzkaller? If there's no RAS2 table, the driver just silently does nothing. So, the question becomes: how can I *force* this code to run, or at least attempt to run? Syzkaller *can* load ACPI tables via `acpi_configfs` or `initrd` table override...but I'm not sure if it actively does this for RAS2. Let me check if `parse_ras2_table` is ever reached. If syzkaller *can* provide custom ACPI tables, then the code *is* reachable. Okay, let's assume it can for the time being. Now, let's look at the configuration options. I see `CONFIG_ACPI_RAS2`, `CONFIG_MEM_ACPI_RAS2`, `CONFIG_EDAC`, and `CONFIG_EDAC_SCRUB`. Standard stuff. Next, I need to check how the driver is actually initialized. I need to make sure `acpi_ras2_init` is called. Okay, it looks like `acpi_ras2_init` is called from `acpi_init` in `drivers/acpi/bus.c`. Excellent. Finally, I need to know if there is any software emulation or purely hardware dependent on it. I need to check if there is any mention of `ACPI_SIG_RAS2` outside the driver itself. Let me fire up `grepper` and see what comes back. |
| 4/2 | 2026/07/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "ACPI_SIG_RAS2"
}Results: {
"Output": "drivers/acpi/ras2.c=488=void __init acpi_ras2_init(void)\n--\ndrivers/acpi/ras2.c-492-\ndrivers/acpi/ras2.c:493:\tstatus = acpi_get_table(ACPI_SIG_RAS2, 0, (struct acpi_table_header **)\u0026ras2_tab);\ndrivers/acpi/ras2.c-494-\tif (ACPI_FAILURE(status)) {\n--\ninclude/acpi/actbl2.h-53-#define ACPI_SIG_RASF \"RASF\"\t/* RAS Feature table */\ninclude/acpi/actbl2.h:54:#define ACPI_SIG_RAS2 \"RAS2\"\t/* RAS2 Feature table */\ninclude/acpi/actbl2.h-55-#define ACPI_SIG_RGRT \"RGRT\"\t/* Regulatory Graphics Resource Table */\n"
} |
| 5/2 | 2026/07/06 00:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22208 output: 21 thoughts: 1146 Thoughts: **Unreachable Code Analysis: ACPI RAS2 Table and Syzkaller Fuzzing** Okay, so I'm looking at this `drivers/acpi/ras2.c` code, and the key seems to be the ACPI RAS2 table signature. Can syzkaller even fuzz ACPI tables? I know it has *some* support. But the entry point, `acpi_ras2_init`, is called during `acpi_init`, which is an `subsys_initcall`. That's a red flag. If syzkaller loads tables through configfs later, does that even matter? Does `acpi_get_table` pick up tables loaded after `acpi_init` runs? Actually, the more I think about it, `acpi_ras2_init` seems to be a *one-shot* deal during boot. If the table isn't there at that exact moment, it bails out. Could we inject it via the QEMU command line? Possible, but syzkaller doesn't dynamically swap ACPI tables per test case, it has a fixed QEMU config. Wait, does syzkaller have an "inject ACPI table" syzcall like `syz_acpi_load`? No, there isn't one. Hmm...is there even any way to test this with syzkaller? If `acpi_ras2_init` only runs at boot, and syzkaller doesn't reboot with custom ACPI tables, the code is unreachable during fuzzing. Let me confirm that `acpi_ras2_init` is the sole entry point. Yep, it parses the table and creates auxiliary devices. The `ras2_mem_driver` binds to these devices. No devices, no probe, and the whole `drivers/acpi/ras2.c` and `drivers/ras/acpi_ras2.c` are dead code unless the RAS2 table is present at boot. Since QEMU doesn't provide the RAS2 table by default, and syzkaller doesn't inject it at boot, this code is likely *unreachable* in a virtualized environment. Let me double-check: is there ANY other way to call `parse_ras2_table` or `ras2_mem_drv_probe`? `ras2_mem_drv_probe` gets called by the auxiliary bus when a device matching `RAS2_MEM_DEV_ID_NAME` is added. And where does that device come from? It's `parse_ras2_table`! Which is only called by `acpi_ras2_init`, and which is only called by `acpi_init`. Can we create an auxiliary device from userspace? No, auxiliary devices are created by kernel code. Is there any way to trigger `acpi_init` again? No, it's an `__init` function. So, without a RAS2 ACPI table provided at boot, this code is completely unreachable. Since we're evaluating if syzkaller should even try, and syzkaller runs in QEMU/GCE *without* custom firmware with RAS2, this code is dead. Wait, what if syzkaller loads the table via `acpi_configfs`? It wouldn't matter, `acpi_ras2_init` has already run and returned. Wait, does `acpi_get_table` get called again on table load? No. `acpi_ras2_init` is called directly from `acpi_init`. There's no ACPI table notifier for RAS2. Compare that to `acpi_nfit_init` which *does* register an ACPI table handler... Let's see if `acpi_ras2_init` does something similar? No, it just does a one-time `acpi_get_table` at boot and calls `parse_ras2_table` if it finds it. Let's double-check again, is there any other way to call `ras2_mem_drv_probe`? Maybe some other driver somehow creates the auxiliary device? No, the device ID is `"acpi_ras2_mem"`. Let me search for it and be sure... Nope. Confirmed. This code is *unreachable* in the standard syzkaller setup. |
| 6/2 | 2026/07/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "acpi_ras2_mem"
}Results: {
"Output": "include/acpi/ras2.h=17=struct device;\n--\ninclude/acpi/ras2.h-25-#define RAS2_AUX_DEV_NAME \"ras2\"\ninclude/acpi/ras2.h:26:#define RAS2_MEM_DEV_ID_NAME \"acpi_ras2_mem\"\ninclude/acpi/ras2.h-27-\n"
} |
| 7/2 | 2026/07/06 00:36 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24414 output: 19 thoughts: 1149 Thoughts: **Analyzing the RAS2 Device and EDAC Scrub Interface** Okay, so I'm looking at this RAS2 device, and it seems the only way it gets created is through `acpi_ras2_init`. This is a bit of a problem because it's hardware-dependent, needing specific ACPI tables from the firmware, and it only checks this at boot time. That means it's basically untouchable in virtualized environments like QEMU or GCE unless we specifically configure QEMU to pass a custom ACPI table, and Syzkaller isn't doing that dynamically for RAS2 at the moment. It is going to be incredibly difficult to reproduce any issues. Wait a second, there's a related change in `drivers/edac/scrub.c`! I missed this initially. It looks like a new attribute `enable_demand` has been added to the EDAC scrub sysfs interface. This is interesting. Is any other driver using this new attribute? I should probably check for any other drivers setting `get_enabled_od` or `set_enabled_od`. |
| 8/2 | 2026/07/06 00:37 | tool | grepper |
0mArgs:{
"Expression": "set_enabled_od"
}Results: {
"Output": "drivers/edac/scrub.c=88=EDAC_SCRUB_ATTR_STORE(enable_background, set_enabled_bg, unsigned long, kstrtoul)\ndrivers/edac/scrub.c:89:EDAC_SCRUB_ATTR_STORE(enable_demand, set_enabled_od, unsigned long, kstrtoul)\ndrivers/edac/scrub.c-90-EDAC_SCRUB_ATTR_STORE(current_cycle_duration, set_cycle_duration, unsigned long, kstrtoul)\n--\ndrivers/edac/scrub.c=92=static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)\n--\ndrivers/edac/scrub.c-126-\t\tif (ops-\u003eget_enabled_od) {\ndrivers/edac/scrub.c:127:\t\t\tif (ops-\u003eset_enabled_od)\ndrivers/edac/scrub.c-128-\t\t\t\treturn a-\u003emode;\n--\ndrivers/ras/acpi_ras2.c=381=static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled)\n--\ndrivers/ras/acpi_ras2.c-396-\ndrivers/ras/acpi_ras2.c:397:static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)\ndrivers/ras/acpi_ras2.c-398-{\n--\ndrivers/ras/acpi_ras2.c=489=static const struct edac_scrub_ops ras2_scrub_ops = {\n--\ndrivers/ras/acpi_ras2.c-496-\t.get_enabled_od = ras2_hw_scrub_get_enabled_od,\ndrivers/ras/acpi_ras2.c:497:\t.set_enabled_od = ras2_hw_scrub_set_enabled_od,\ndrivers/ras/acpi_ras2.c-498-\t.get_min_cycle = ras2_hw_scrub_read_min_scrub_cycle,\n--\ninclude/linux/edac.h=672=enum edac_dev_feat {\n--\ninclude/linux/edac.h-687- * @get_enabled_od: check if currently performing demand scrub.\ninclude/linux/edac.h:688: * @set_enabled_od: start or stop a demand-scrub.\ninclude/linux/edac.h-689- * @get_min_cycle: get minimum supported scrub cycle duration in seconds.\n--\ninclude/linux/edac.h=694=struct edac_scrub_ops {\n--\ninclude/linux/edac.h-701-\tint (*get_enabled_od)(struct device *dev, void *drv_data, bool *enable);\ninclude/linux/edac.h:702:\tint (*set_enabled_od)(struct device *dev, void *drv_data, bool enable);\ninclude/linux/edac.h-703-\tint (*get_min_cycle)(struct device *dev, void *drv_data, u32 *min);\n"
} |
| 9/2 | 2026/07/06 00:37 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26627 output: 134 thoughts: 366 Thoughts: **Unreachable Code Analysis** Okay, so I've been digging into this patch series. My initial observation is that the new `set_enabled_od` function is *only* used within `acpi_ras2.c`. This strongly suggests the new EDAC scrub attribute is exclusively tied to the new ACPI RAS2 driver. Given that, the critical question is whether we can even *hit* this code. My first thought was, "Could our fuzzing environment, specifically syzkaller, provide a pathway?" I need to consider that. Then I remembered the ACPI table injection mechanism in syzkaller via `acpi_configfs`. However, a quick look reveals that `acpi_ras2_init` is called directly during the kernel's boot-up sequence from `acpi_init`, and doesn't appear to register itself to be called later. I need to confirm this... Okay, wait... `acpi_nfit_init` is also called from `acpi_init`, and that *does* register an ACPI driver. That's a module init function! `acpi_ras2_init` does not. It is called directly from `acpi_init` and just calls `acpi_get_table(ACPI_SIG_RAS2, ...)`. This means that if the ACPI table gets loaded later via configfs, `acpi_ras2_init` won't be re-executed. It's strictly a boot-time thing. So, realistically, we can't trigger this functionality in syzkaller. Therefore, my conclusion is that this is unreachable code, which is hardware-dependent (needing an ACPI RAS2 table present in firmware at boot) and, as such, unreachable in virtualized environments like QEMU and GCE. I will return WorthFuzzing=false, citing these reasons. |
| 10/2 | 2026/07/06 00:37 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch introduces a new driver for ACPI RAS2 hardware-based memory scrubbing. The driver initialization (acpi_ras2_init) is called exactly once during boot (from acpi_init) and checks for the presence of the ACPI RAS2 table. Since QEMU/GCE do not provide this specific ACPI table by default, and the driver does not support dynamic table loading (e.g., via configfs), the code is completely unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces a new driver for ACPI RAS2 hardware-based memory scrubbing. The driver initialization (acpi_ras2_init) is called exactly once during boot (from acpi_init) and checks for the presence of the ACPI RAS2 table. Since QEMU/GCE do not provide this specific ACPI table by default, and the driver does not support dynamic table loading (e.g., via configfs), the code is completely unreachable in virtualized fuzzing environments.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|