| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/04 22:26 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces an ACPI RAS2 memory driver and adds demand scrubbing support to the EDAC scrub interface. The driver relies on the ACPI RAS2 table and the Platform Communication Channel (PCC) mailbox to communicate with the hardware. Since PCC hardware and the associated ACPI tables are not emulated in virtualized environments like QEMU or GCE, the driver will fail to probe (specifically timing out while polling for PCC command completion). Consequently, the core logic and the new sysfs interfaces are impossible to reach during fuzzing.",
"WorthFuzzing": false
} |
| 1/1 | 2026/07/04 22:26 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d984e8d27a21006c16b884c92bdb6d710a36f0f9\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Jul 4 22:26:43 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..31ddf5c9688c67\n--- /dev/null\n+++ b/drivers/acpi/ras2.c\n@@ -0,0 +1,509 @@\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_get(struct ras2_sspcc *sspcc)\n+{\n+\tkref_get(\u0026sspcc-\u003ekref);\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_get_sspcc(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+\t\tras2_sspcc_get(sspcc);\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_get_sspcc(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(sspcc-\u003epcc_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+\tras2_sspcc_get(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+\tstruct ras2_sspcc *sspcc;\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+\tsspcc = ras2_ctx-\u003esspcc;\n+\tpcc_mbox_free_channel(sspcc-\u003epcc_chan);\n+\tkfree(sspcc);\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..8bf71375ec5068\n--- /dev/null\n+++ b/drivers/ras/acpi_ras2.c\n@@ -0,0 +1,562 @@\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\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\t\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\tif (ret)\n+\t\t\treturn ret;\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\treturn ret;\n+\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tmsleep(1000);\n+\t}\n+\n+\tras2_ctx-\u003ethread = NULL;\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+\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+\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+\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+\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+\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*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+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\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*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+\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+\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+\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_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+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\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+\tmutex_lock(ras2_ctx-\u003epcc_lock);\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-\u003edrv_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 user/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+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\n+\treturn ret;\n+\n+enable_bg_scrub:\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\tif (ras2_ctx-\u003ereenable_bg_scrub) {\n+\t\tras2_ctx-\u003ereenable_bg_scrub = false;\n+\t\tras2_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+\tras2_ctx-\u003edrv_active = false;\n+\tif (ras2_ctx \u0026\u0026 ras2_ctx-\u003ethread) {\n+\t\tkthread_stop(ras2_ctx-\u003ethread);\n+\t\tras2_ctx-\u003ethread = NULL;\n+\t}\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 = 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-\u003edrv_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..3efb580b5e3618\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+ * @drv_active:\t\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\tdrv_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/04 22:26 | agent | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces an ACPI RAS2 memory driver and adds demand scrubbing support to the EDAC scrub interface. The driver relies on the ACPI RAS2 table and the Platform Communication Channel (PCC) mailbox to communicate with the hardware. Since PCC hardware and the associated ACPI tables are not emulated in virtualized environments like QEMU or GCE, the driver will fail to probe (specifically timing out while polling for PCC command completion). Consequently, the core logic and the new sysfs interfaces are impossible to reach during fuzzing.",
"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 d984e8d27a21006c16b884c92bdb6d710a36f0f9
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Jul 4 22:26:43 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..31ddf5c9688c67
--- /dev/null
+++ b/drivers/acpi/ras2.c
@@ -0,0 +1,509 @@
+// 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_get(struct ras2_sspcc *sspcc)
+{
+ kref_get(&sspcc->kref);
+}
+
+static void ras2_sspcc_put(struct ras2_sspcc *sspcc)
+{
+ kref_put(&sspcc->kref, &ras2_list_pcc_release);
+}
+
+static struct ras2_sspcc *ras2_get_sspcc(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;
+ ras2_sspcc_get(sspcc);
+ 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_get_sspcc(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(sspcc->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);
+ ras2_sspcc_get(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;
+ struct ras2_sspcc *sspcc;
+ 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:
+ sspcc = ras2_ctx->sspcc;
+ pcc_mbox_free_channel(sspcc->pcc_chan);
+ kfree(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..8bf71375ec5068
--- /dev/null
+++ b/drivers/ras/acpi_ras2.c
@@ -0,0 +1,562 @@
+// 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;
+
+ mutex_lock(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ mutex_unlock(ras2_ctx->pcc_lock);
+ if (ret)
+ return ret;
+
+ if (!running) {
+ ret = ras2_hw_scrub_set_enabled_bg(ras2_ctx->dev, ras2_ctx, true);
+ if (ret)
+ return ret;
+
+ break;
+ }
+
+ msleep(1000);
+ }
+
+ ras2_ctx->thread = NULL;
+
+ 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;
+
+ *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;
+
+ *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;
+
+ *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;
+
+ /*
+ * 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;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ 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;
+
+ if (ras2_ctx->bg_scrub)
+ return -EBUSY;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ 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;
+
+ *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;
+
+ guard(mutex)(ras2_ctx->pcc_lock);
+ 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_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;
+
+ mutex_lock(ras2_ctx->pcc_lock);
+ ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+ mutex_unlock(ras2_ctx->pcc_lock);
+ 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;
+ }
+
+ mutex_lock(ras2_ctx->pcc_lock);
+ /*
+ * 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->drv_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 user/firmware may need
+ * to re-enable background scrub after demand scrubbing has finished.
+ */
+ goto enable_bg_scrub;
+ }
+ }
+ mutex_unlock(ras2_ctx->pcc_lock);
+
+ return ret;
+
+enable_bg_scrub:
+ mutex_unlock(ras2_ctx->pcc_lock);
+ 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);
+
+ ras2_ctx->drv_active = false;
+ if (ras2_ctx && ras2_ctx->thread) {
+ kthread_stop(ras2_ctx->thread);
+ ras2_ctx->thread = NULL;
+ }
+}
+
+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 = 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->drv_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..3efb580b5e3618
--- /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
+ * @drv_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 drv_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/04 22:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20322 output: 21 thoughts: 768 Thoughts: **Analyzing the Reachability and Fuzzability of ACPI RAS2 Support and Memory Scrubbing** Okay, so I'm looking at this patch series introducing ACPI RAS2 support and memory scrubbing. It's got changes in `drivers/acpi/ras2.c`, `drivers/ras/acpi_ras2.c`, and `drivers/edac/scrub.c`. My immediate thought is, how easily can we actually test this, especially in a virtualized environment? ACPI RAS2 leans heavily on ACPI tables, specifically the RAS2 table, and the Platform Communication Channel (PCC). First, the QEMU/GCE question. Does the firmware actually *provide* a RAS2 table by default? Typically, no. QEMU doesn't usually expose them unless specifically configured. But there's Syzkaller. Can it inject ACPI tables? Or is there some other platform-specific driver angle we can exploit? Let me see... `acpi_ras2_init` gets called from `acpi_init` in `drivers/acpi/bus.c`. And that in turn calls `acpi_get_table(ACPI_SIG_RAS2, ...)`. If the table isn't present, it just returns. That's a good sanity check in the code, but it makes the table presence *critical*. Syzkaller *can* inject tables, right? I seem to remember something about `CONFIG_ACPI_TABLE_UPGRADE` and initrd. But, it's often a pain to target specific ACPI table injections. And, that PCC interaction... does Syzkaller's table injection cover that properly, or does it leave us exposed? Honestly, the injection through `config ACPI_TABLE_UPGRADE` and initrd is hard to reach unless we are specifically targeting it. So, if `acpi_get_table` fails, `acpi_ras2_init` does nothing. Is there *any* other entry point? Software emulation, perhaps? PCC is the ACPI Platform Communication Channel. The driver just quietly does nothing if it can't find the table. But hang on, Syzkaller does fuzz ACPI tables sometimes, so that is a thing to keep in mind. Let's look at the configs: `CONFIG_ACPI_RAS2`, `CONFIG_MEM_ACPI_RAS2`, and `CONFIG_EDAC_SCRUB`. Got it. Okay, can we fuzz this? The driver registers an auxiliary device if the RAS2 table *is* present, and then `ras2_mem_drv_probe` gets called. That uses PCC. To fuzz effectively, we'd *need* that RAS2 table. Is it even worth the effort? Absolutely, this is a new driver, parsing ACPI tables, and exposing sysfs interfaces. High potential for issues there. But if we can't reliably inject the ACPI table... it will be tough for Syzkaller to trigger it. It would need to have something like a `syz_acpi_setup` call, or the capability to mount configfs and inject ACPI tables. Let's look for other entry points. The `drivers/edac/scrub.c` change adds `enable_demand` to the sysfs interface. That's reachable if *any* EDAC driver calls `set_enabled_od`. It seems `acpi_ras2.c` *is* the only one currently using it. So it makes the dependency on the RAS2 table less of a limiting factor. Let me check if there are other users of `edac_scrub_ops`. |
| 4/2 | 2026/07/04 22:26 | tool | grepper |
0mArgs:{
"Expression": "edac_scrub_ops"
}Results: {
"Output": "drivers/cxl/core/edac.c=324=static int cxl_patrol_scrub_set_scrub_cycle(struct device *dev, void *drv_data,\n--\ndrivers/cxl/core/edac.c-355-\ndrivers/cxl/core/edac.c:356:static const struct edac_scrub_ops cxl_ps_scrub_ops = {\ndrivers/cxl/core/edac.c-357-\t.get_enabled_bg = cxl_patrol_scrub_get_enabled_bg,\n--\ndrivers/edac/scrub.c=40=static ssize_t attrib##_show(struct device *ras_feat_dev,\t\t\t\\\n--\ndrivers/edac/scrub.c-44-\tstruct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);\t\t\\\ndrivers/edac/scrub.c:45:\tconst struct edac_scrub_ops *ops = ctx-\u003escrub[inst].scrub_ops;\t\t\\\ndrivers/edac/scrub.c-46-\ttype data;\t\t\t\t\t\t\t\t\\\n--\ndrivers/edac/scrub.c=65=static ssize_t attrib##_store(struct device *ras_feat_dev,\t\t\t\\\n--\ndrivers/edac/scrub.c-70-\tstruct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);\t\t\\\ndrivers/edac/scrub.c:71:\tconst struct edac_scrub_ops *ops = ctx-\u003escrub[inst].scrub_ops;\t\t\\\ndrivers/edac/scrub.c-72-\ttype data;\t\t\t\t\t\t\t\t\\\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-97-\tstruct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);\ndrivers/edac/scrub.c:98:\tconst struct edac_scrub_ops *ops = ctx-\u003escrub[inst].scrub_ops;\ndrivers/edac/scrub.c-99-\n--\ndrivers/ras/acpi_ras2.c=373=static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)\n--\ndrivers/ras/acpi_ras2.c-468-\ndrivers/ras/acpi_ras2.c:469:static const struct edac_scrub_ops ras2_scrub_ops = {\ndrivers/ras/acpi_ras2.c-470-\t.read_addr = ras2_hw_scrub_read_addr,\n--\ninclude/linux/edac.h=672=enum edac_dev_feat {\n--\ninclude/linux/edac.h-679-/**\ninclude/linux/edac.h:680: * struct edac_scrub_ops - scrub device operations (all elements optional)\ninclude/linux/edac.h-681- * @read_addr: read base address of scrubbing range.\n--\ninclude/linux/edac.h-693- */\ninclude/linux/edac.h:694:struct edac_scrub_ops {\ninclude/linux/edac.h-695-\tint (*read_addr)(struct device *dev, void *drv_data, u64 *base);\n--\ninclude/linux/edac.h=861=struct edac_dev_data {\ninclude/linux/edac.h-862-\tunion {\ninclude/linux/edac.h:863:\t\tconst struct edac_scrub_ops *scrub_ops;\ninclude/linux/edac.h-864-\t\tconst struct edac_ecs_ops *ecs_ops;\n--\ninclude/linux/edac.h=879=struct edac_dev_feature {\n--\ninclude/linux/edac.h-882-\tunion {\ninclude/linux/edac.h:883:\t\tconst struct edac_scrub_ops *scrub_ops;\ninclude/linux/edac.h-884-\t\tconst struct edac_ecs_ops *ecs_ops;\n"
} |
| 5/2 | 2026/07/04 22:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22985 output: 22 thoughts: 260 Thoughts: **My Assessment of the CXL Driver's RAS2 Dependency** Okay, so the CXL driver relies on `edac_scrub_ops`, that much is clear. But the new `enable_demand` feature, that's exclusively in `acpi_ras2.c`. The critical question now is, *can* we even reach `acpi_ras2.c`? It hinges on the presence of an ACPI RAS2 table. My immediate thought is, can syzkaller help here? I know it can inject ACPI tables through configfs at `/sys/kernel/config/acpi/table`, so let me check if there are descriptions for ACPI table injection available in syzkaller. Right, syzkaller *can* do it, that's good news. However, constructing a valid RAS2 table and a usable PCC channel is a different challenge. I see the patch introduces the new `acpi_ras2.c` driver, and that it parses ACPI tables and interacts with PCC. It is likely the author's intention is to ensure this new driver is used and that there are no side effects when it is used. I am no longer worried about reachability. The real question is, can this new driver be exploited. Now, a critical thought: is this even *possible* in a virtualized environment like Google Compute Engine or QEMU? Then I remembered: QEMU *does* support ACPI table injection. And syzkaller has the capability to fuzz ACPI tables. But here's the snag, PCC, the Platform Communication Channel, needs a PCC mailbox channel. Now, does QEMU provide one? This is the next thing to investigate. I need to see if there's a PCC mailbox driver that can be loaded, or, ideally, fuzzed. That's the key to making this vulnerability work, if it exists. |
| 6/2 | 2026/07/04 22:27 | tool | grepper |
0mArgs:{
"Expression": "pcc_mbox_request_channel"
}Results: {
"Output": "drivers/acpi/acpi_pcc.c=49=acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function,\n--\ndrivers/acpi/acpi_pcc.c-67-\tinit_completion(\u0026data-\u003edone);\ndrivers/acpi/acpi_pcc.c:68:\tdata-\u003epcc_chan = pcc_mbox_request_channel(\u0026data-\u003ecl, ctx-\u003esubspace_id);\ndrivers/acpi/acpi_pcc.c-69-\tif (IS_ERR(data-\u003epcc_chan)) {\n--\ndrivers/acpi/cppc_acpi.c=566=static int register_pcc_channel(int pcc_ss_idx)\n--\ndrivers/acpi/cppc_acpi.c-571-\tif (pcc_ss_idx \u003e= 0) {\ndrivers/acpi/cppc_acpi.c:572:\t\tpcc_chan = pcc_mbox_request_channel(\u0026cppc_mbox_cl, pcc_ss_idx);\ndrivers/acpi/cppc_acpi.c-573-\n--\ndrivers/acpi/ras2.c=283=static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)\n--\ndrivers/acpi/ras2.c-304-\ndrivers/acpi/ras2.c:305:\tpcc_chan = pcc_mbox_request_channel(\u0026sspcc-\u003embox_client, pcc_id);\ndrivers/acpi/ras2.c-306-\tif (IS_ERR(pcc_chan)) {\n--\ndrivers/devfreq/hisi_uncore_freq.c=107=static int hisi_uncore_request_pcc_chan(struct hisi_uncore_freq *uncore)\n--\ndrivers/devfreq/hisi_uncore_freq.c-117-\ndrivers/devfreq/hisi_uncore_freq.c:118:\tpcc_chan = pcc_mbox_request_channel(\u0026uncore-\u003ecl, uncore-\u003echan_id);\ndrivers/devfreq/hisi_uncore_freq.c-119-\tif (IS_ERR(pcc_chan))\n--\ndrivers/hwmon/xgene-hwmon.c=609=static int xgene_hwmon_probe(struct platform_device *pdev)\n--\ndrivers/hwmon/xgene-hwmon.c-667-\t\tcl-\u003erx_callback = xgene_hwmon_pcc_rx_cb;\ndrivers/hwmon/xgene-hwmon.c:668:\t\tpcc_chan = pcc_mbox_request_channel(cl, ctx-\u003embox_idx);\ndrivers/hwmon/xgene-hwmon.c-669-\t\tif (IS_ERR(pcc_chan)) {\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c=436=static int xgene_slimpro_i2c_probe(struct platform_device *pdev)\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c-477-\t\tcl-\u003erx_callback = slimpro_i2c_pcc_rx_cb;\ndrivers/i2c/busses/i2c-xgene-slimpro.c:478:\t\tpcc_chan = pcc_mbox_request_channel(cl, ctx-\u003embox_idx);\ndrivers/i2c/busses/i2c-xgene-slimpro.c-479-\t\tif (IS_ERR(pcc_chan))\n--\ndrivers/mailbox/pcc.c=313=static irqreturn_t pcc_mbox_irq(int irq, void *p)\n--\ndrivers/mailbox/pcc.c-348-/**\ndrivers/mailbox/pcc.c:349: * pcc_mbox_request_channel - PCC clients call this function to\ndrivers/mailbox/pcc.c-350- *\t\trequest a pointer to their PCC subspace, from which they\n--\ndrivers/mailbox/pcc.c=360=struct pcc_mbox_chan *\ndrivers/mailbox/pcc.c:361:pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id)\ndrivers/mailbox/pcc.c-362-{\n--\ndrivers/mailbox/pcc.c-392-}\ndrivers/mailbox/pcc.c:393:EXPORT_SYMBOL_GPL(pcc_mbox_request_channel);\ndrivers/mailbox/pcc.c-394-\n--\ndrivers/mailbox/pcc.c-398- * @pchan: Pointer to the PCC mailbox channel as returned by\ndrivers/mailbox/pcc.c:399: *\t pcc_mbox_request_channel()\ndrivers/mailbox/pcc.c-400- */\n--\ndrivers/soc/hisilicon/kunpeng_hccs.c=173=static int hccs_register_pcc_channel(struct hccs_dev *hdev)\n--\ndrivers/soc/hisilicon/kunpeng_hccs.c-188-\ndrivers/soc/hisilicon/kunpeng_hccs.c:189:\tpcc_chan = pcc_mbox_request_channel(cl, hdev-\u003echan_id);\ndrivers/soc/hisilicon/kunpeng_hccs.c-190-\tif (IS_ERR(pcc_chan)) {\n--\ninclude/acpi/pcc.h=37=extern struct pcc_mbox_chan *\ninclude/acpi/pcc.h:38:pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id);\ninclude/acpi/pcc.h-39-extern void pcc_mbox_free_channel(struct pcc_mbox_chan *chan);\n--\ninclude/acpi/pcc.h=41=static inline struct pcc_mbox_chan *\ninclude/acpi/pcc.h:42:pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id)\ninclude/acpi/pcc.h-43-{\n"
} |
| 7/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25217 output: 30 thoughts: 513 Thoughts: **Analyzing the Accessibility of the `ras2_mem_drv_probe` Function**
Okay, let's break this down. My initial thought was that syzkaller, with its ACPI table injection capabilities, *might* be able to trigger the `ras2_mem_drv_probe` function, specifically through the PCC mailbox pathway. The logic goes: `pcc_mbox_request_channel` requests the mailbox channel, and the PCC mailbox driver initializes from the ACPI PCCT table. Syzkaller injecting both a PCCT table and a RAS2 table could *theoretically* create this setup.
However, given syzkaller's current limitations in ACPI table manipulation, successfully crafting a coherent PCCT, PCC channel, and RAS2 table that all link correctly to the `acpi_ras2` memory driver and its sysfs interfaces seemed incredibly unlikely. It's just a lot to get right.
Then I shifted gears – is there *any* other route? I see this `ras2_mem_drv_probe` is part of an auxiliary driver, specifically:
```c
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,
};
```
Can *we* create an auxiliary device from userspace? No, that's not how it works; auxiliary devices are created by other kernel drivers. In this case, it appears the `drivers/acpi/ras2.c` driver is responsible, specifically in the `parse_ras2_table` function. Therefore, this whole pathway *strictly* requires parsing the ACPI RAS2 table.
So, could syzkaller fuzz this? It's possible! If it can inject ACPI tables, it *might* reach `parse_ras2_table`. So the next step is to check whether syzkaller actually *does* have ACPI table injection available. Yes, it does! `syz_acpi_setup` or something similar is used.
But wait. Even if it *does* parse the RAS2 table, that `parse_ras2_table` function then calls `register_pcc_channel`, which calls `pcc_mbox_request_channel`. And, if there isn't a PCCT table, `pcc_mbox_request_channel` is going to fail. That failure will propagate to `add_aux_device`, and therefore the auxiliary device won't be created. Meaning, `ras2_mem_drv_probe` won't ever be called unless a *valid* PCCT table is present and a PCC channel is successfully established.
The conclusion is this is *extremely* hard to reach in a virtualized environment without very specific emulation. Unless there's some kind of mock or software emulation for PCC, we're probably not going to be able to hit this easily with fuzzing. Let me just quickly check for some PCC emulations...
|
| 8/2 | 2026/07/04 22:27 | tool | grepper |
0mError:no matches Args: {
"Expression": "pcc_mbox_request_channel",
"PathPrefix": "tools/"
}Results: {
"error": "no matches"
} |
| 9/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26447 output: 32 thoughts: 15 Thoughts: Here's a summary of my thought process: **Diving into the Mailbox Driver Implementation** Okay, so I'm looking at the `drivers/mailbox/` directory. I'm immediately wondering about the PCC (Platform Communication Channel) in this context. Are we using a mocked version of it here? That's the first question that pops into my head. I need to understand how the Mailbox driver interacts with the underlying hardware, and if we're not dealing with real hardware during testing or development, then a mock PCC would be the logical way to go. This is a critical detail in figuring out how this driver is tested and how robustly it’s designed. Without understanding the PCC interaction, the overall function of the mailbox driver is unclear. |
| 10/2 | 2026/07/04 22:27 | tool | grepper |
0mArgs:{
"Expression": "pcc_mbox_request_channel",
"PathPrefix": "drivers/mailbox/"
}Results: {
"Output": "drivers/mailbox/pcc.c=313=static irqreturn_t pcc_mbox_irq(int irq, void *p)\n--\ndrivers/mailbox/pcc.c-348-/**\ndrivers/mailbox/pcc.c:349: * pcc_mbox_request_channel - PCC clients call this function to\ndrivers/mailbox/pcc.c-350- *\t\trequest a pointer to their PCC subspace, from which they\n--\ndrivers/mailbox/pcc.c=360=struct pcc_mbox_chan *\ndrivers/mailbox/pcc.c:361:pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id)\ndrivers/mailbox/pcc.c-362-{\n--\ndrivers/mailbox/pcc.c-392-}\ndrivers/mailbox/pcc.c:393:EXPORT_SYMBOL_GPL(pcc_mbox_request_channel);\ndrivers/mailbox/pcc.c-394-\n--\ndrivers/mailbox/pcc.c-398- * @pchan: Pointer to the PCC mailbox channel as returned by\ndrivers/mailbox/pcc.c:399: *\t pcc_mbox_request_channel()\ndrivers/mailbox/pcc.c-400- */\n"
} |
| 11/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26980 output: 35 thoughts: 272 Thoughts: **My Analysis of the RAS2 Memory Driver Probe** Okay, so there's no "mock PCC" in this scenario, which means the path to `ras2_mem_drv_probe` is very specific and hinges on a chain of dependencies. Let's break it down: First, I need to have a valid ACPI RAS2 table, that's step one. Then, I *must* have an ACPI PCCT table with a valid subspace defined; that's critical. Following this, the described subspace *has* to have a functional, shared memory region assigned to it, which is the third necessary component. Fourth, the PCC mailbox driver has to succeed in its probing process, and actually register the channel; I need that established link. Fifth, `acpi_ras2_init` needs to come into play, parse the RAS2 table, find the PCC channel, and trigger the call to `pcc_mbox_request_channel`; this is where the real work begins. Sixth, an auxiliary device must be successfully created; this is a prerequisite. Now, once those steps are completed, I'll arrive at `ras2_mem_drv_probe`. Here's where it gets interesting. The driver sends a PCC command to retrieve patrol parameters. This is where I have to pause and seriously consider how this command interacts with the shared memory region. It seems the shared memory region has to actually *respond*, or at the very least, not time out. Or, perhaps it just writes something to memory and then rings a doorbell to indicate completion? Let's clarify this. *Aha!* `ras2_send_pcc_cmd` polls the status register within that shared memory region. It's looking for `PCC_STATUS_CMD_COMPLETE`. So, if there's no hardware or emulator present to set that bit in the status register, `check_pcc_chan` is going to time out and return an error; it's a dead end. I'd better confirm this, let me check the `check_pcc_chan` function in `drivers/acpi/ras2.c` and verify my assumptions. |
| 12/2 | 2026/07/04 22:27 | tool | grepper |
0mArgs:{
"Expression": "check_pcc_chan",
"PathPrefix": "drivers/acpi/ras2.c"
}Results: {
"Output": "drivers/acpi/ras2.c=75=static LIST_HEAD(ras2_sspcc);\ndrivers/acpi/ras2.c-76-\ndrivers/acpi/ras2.c:77:static int check_pcc_chan(struct ras2_sspcc *sspcc)\ndrivers/acpi/ras2.c-78-{\n--\ndrivers/acpi/ras2.c=144=int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)\n--\ndrivers/acpi/ras2.c-159-\ndrivers/acpi/ras2.c:160:\trc = check_pcc_chan(sspcc);\ndrivers/acpi/ras2.c-161-\tif (rc \u003c 0)\n--\ndrivers/acpi/ras2.c-234-\tif (cmd == PCC_CMD_EXEC_RAS2 || sspcc-\u003epcc_mrtt) {\ndrivers/acpi/ras2.c:235:\t\trc = check_pcc_chan(sspcc);\ndrivers/acpi/ras2.c-236-\t\tif (sspcc-\u003epcc_mrtt)\n"
} |
| 13/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28059 output: 36 thoughts: 25 Thoughts: Here's a first-person summary, tailored for an expert audience, based on your prompt: **Diving into `check_pcc_chan`** Okay, so I'm looking at `check_pcc_chan` within `drivers/acpi/ras2.c`. Immediately, my brain starts running through the implications. I assume this function is central to the Platform Communication Channel (PCC) error checking or initialization within the ACPI RAS framework. Given the file context, it's likely dealing with some level of hardware error reporting or potentially even configuring the PCC for inter-processor communication or management of some hardware resource. I need to understand its role in the overall RAS architecture. Is it called during system initialization, or is it triggered by specific ACPI events? What are the failure modes it's designed to detect, and how does it respond to those failures? I need to think about potential race conditions or locking issues that could arise in a multi-threaded or multi-processor environment, especially if it's interacting with shared hardware resources. I'll need to look at how it interacts with the ACPI namespace and whether there are any potential security implications from an unprivileged user space. I'm also wondering if it is doing something different or specific on different architectures and what the underlying hardware support requirements are. |
| 14/2 | 2026/07/04 22:27 | tool | read-file |
0mArgs:{
"File": "drivers/acpi/ras2.c",
"FirstLine": 77,
"LineCount": 50
}Results: {
"Contents": " 77:\tstatic int check_pcc_chan(struct ras2_sspcc *sspcc)\n 78:\t{\n 79:\t\tstruct acpi_ras2_shmem __iomem *gen_comm_base = sspcc-\u003ecomm_addr;\n 80:\t\tu32 cap_status;\n 81:\t\tu16 status;\n 82:\t\tint rc;\n 83:\t\n 84:\t\t/*\n 85:\t\t * As per ACPI spec, the PCC space will be initialized by the\n 86:\t\t * platform and should have set the command completion bit when\n 87:\t\t * PCC can be used by OSPM.\n 88:\t\t *\n 89:\t\t * Poll PCC status register every PCC_MIN_POLL_USECS for maximum of\n 90:\t\t * PCC_NUM_RETRIES * PCC channel latency until PCC command complete\n 91:\t\t * bit is set.\n 92:\t\t */\n 93:\t\trc = readw_relaxed_poll_timeout(\u0026gen_comm_base-\u003estatus, status,\n 94:\t\t\t\t\t\tstatus \u0026 PCC_STATUS_CMD_COMPLETE,\n 95:\t\t\t\t\t\tPCC_MIN_POLL_USECS, sspcc-\u003edeadline_us);\n 96:\t\tif (rc) {\n 97:\t\t\tpr_warn(\"PCC ID: 0x%x: PCC check channel timeout for last command: 0x%x rc=%d\\n\",\n 98:\t\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd, rc);\n 99:\t\n 100:\t\t\treturn rc;\n 101:\t\t}\n 102:\t\n 103:\t\tif (status \u0026 PCC_STATUS_ERROR) {\n 104:\t\t\tpr_warn(\"PCC ID: 0x%x: Error in executing last command: 0x%x\\n\",\n 105:\t\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd);\n 106:\t\t\tstatus \u0026= ~PCC_STATUS_ERROR;\n 107:\t\t\tiowrite16(status, \u0026gen_comm_base-\u003estatus);\n 108:\t\t\tiowrite32(0x0, \u0026gen_comm_base-\u003eset_caps_status);\n 109:\t\t\treturn -EIO;\n 110:\t\t}\n 111:\t\n 112:\t\t/* Ensure get updated PCC status */\n 113:\t\trmb();\n 114:\t\tcap_status = ioread32(\u0026gen_comm_base-\u003eset_caps_status);\n 115:\t\tswitch (cap_status) {\n 116:\t\tcase ACPI_RAS2_NOT_VALID:\n 117:\t\tcase ACPI_RAS2_NOT_SUPPORTED:\n 118:\t\t\trc = -EPERM;\n 119:\t\t\tbreak;\n 120:\t\tcase ACPI_RAS2_BUSY:\n 121:\t\t\trc = -EBUSY;\n 122:\t\t\tbreak;\n 123:\t\tcase ACPI_RAS2_FAILED:\n 124:\t\tcase ACPI_RAS2_ABORTED:\n 125:\t\tcase ACPI_RAS2_INVALID_DATA:\n 126:\t\t\trc = -EINVAL;\n"
} |
| 15/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29229 output: 37 thoughts: 243 Thoughts: **Analyzing the PCC Status and Potential Timeouts** Okay, so I'm looking at this `check_pcc_chan` function and how it interacts with `gen_comm_base->status`. My initial thought is that it's polling `gen_comm_base->status` for `PCC_STATUS_CMD_COMPLETE`. Seems straightforward. But, hold on a second... if there's no actual hardware emulator to set that bit, we're going to time out, right? Makes sense. Then I got to thinking about memory initialization, like, what if this memory region is just regular RAM? Say it's being pointed to by an ACPI table. The initial value of the status register could very well be zero. In that case, we'd immediately time out on the first read. Ugh! Wait a second, there's a problem here. What if the initial value *does* happen to have `PCC_STATUS_CMD_COMPLETE` set? Then `check_pcc_chan` would succeed the very first time, which would be wrong. And then `ras2_send_pcc_cmd` goes and clears that bit with `iowrite16(0, &gen_comm_base->status)`. So, it flips the CMD COMPLETE bit to zero, which is good. Then, and this is where it gets interesting, it rings the doorbell and then calls `check_pcc_chan` again, but this time, since we just cleared the bit, and there's no actual hardware to set it back, the second `check_pcc_chan` call is *definitely* going to time out! That's a huge problem. I need to verify this behavior specifically within `ras2_send_pcc_cmd` to be certain. |
| 16/2 | 2026/07/04 22:27 | tool | read-file |
0mArgs:{
"File": "drivers/acpi/ras2.c",
"FirstLine": 198,
"LineCount": 50
}Results: {
"Contents": " 198:\t\t\t\tsspcc-\u003empar_count = sspcc-\u003epcc_mpar;\n 199:\t\t\t}\n 200:\t\t\tsspcc-\u003empar_count--;\n 201:\t\t}\n 202:\t\n 203:\t\t/* Write to the shared comm region */\n 204:\t\tiowrite16(cmd, \u0026gen_comm_base-\u003ecommand);\n 205:\t\n 206:\t\t/* Flip CMD COMPLETE bit */\n 207:\t\tiowrite16(0, \u0026gen_comm_base-\u003estatus);\n 208:\t\n 209:\t\t/* Ring doorbell */\n 210:\t\trc = mbox_send_message(pcc_channel, \u0026cmd);\n 211:\t\t/*\n 212:\t\t * mbox_send_message() returns a non-negative integer for successful submission\n 213:\t\t * and a negative value on failure.\n 214:\t\t */\n 215:\t\tif (rc \u003c 0) {\n 216:\t\t\tdev_warn(ras2_ctx-\u003edev,\n 217:\t\t\t\t \"Error sending PCC mbox message command: 0x%x, rc:%d\\n\", cmd, rc);\n 218:\t\t\t/* Restore CMD COMPLETE bit on error */\n 219:\t\t\tval = ioread16(\u0026gen_comm_base-\u003estatus);\n 220:\t\t\tval |= PCC_STATUS_CMD_COMPLETE;\n 221:\t\t\tiowrite16(val, \u0026gen_comm_base-\u003estatus);\n 222:\t\t\treturn rc;\n 223:\t\t} else {\n 224:\t\t\trc = 0;\n 225:\t\t}\n 226:\t\n 227:\t\tsspcc-\u003elast_cmd = cmd;\n 228:\t\n 229:\t\t/*\n 230:\t\t * If Minimum Request Turnaround Time is non-zero, need to record the\n 231:\t\t * completion time of both READ and WRITE commands for proper handling\n 232:\t\t * of MRTT, so need to check for pcc_mrtt in addition to PCC_CMD_EXEC_RAS2.\n 233:\t\t */\n 234:\t\tif (cmd == PCC_CMD_EXEC_RAS2 || sspcc-\u003epcc_mrtt) {\n 235:\t\t\trc = check_pcc_chan(sspcc);\n 236:\t\t\tif (sspcc-\u003epcc_mrtt)\n 237:\t\t\t\tsspcc-\u003elast_cmd_cmpl_time = ktime_get();\n 238:\t\t}\n 239:\t\n 240:\t\tif (!pcc_channel-\u003embox-\u003etxdone_irq)\n 241:\t\t\tmbox_client_txdone(pcc_channel, rc);\n 242:\t\n 243:\t\treturn rc;\n 244:\t}\n 245:\tEXPORT_SYMBOL_FOR_MODULES(ras2_send_pcc_cmd, \"acpi_ras2\");\n 246:\t\n 247:\tstatic void ras2_list_pcc_release(struct kref *kref)\n"
} |
| 17/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30707 output: 38 thoughts: 185 Thoughts: **Analyzing the RAS2 Command Execution** Okay, so I've traced the execution flow here. It's clear that `ras2_send_pcc_cmd` has a crucial role – specifically, it's the function that *clears* the `PCC_STATUS_CMD_COMPLETE` bit. That's step one. Then, it moves on by calling `mbox_send_message`, which is expected. Now, if the command (`cmd`) happens to be `PCC_CMD_EXEC_RAS2` – and, in this specific `ras2_update_patrol_scrub_params_cache` context, it *is* – then the code calls `check_pcc_chan` again. Here's where it gets interesting. Since we don't have a hardware emulator present to manually toggle the `PCC_STATUS_CMD_COMPLETE` bit back to '1', the subsequent call to `check_pcc_chan` is going to time out. That timeout is an error condition. Therefore, if `check_pcc_chan` times out, it's pretty much a guarantee that `ras2_update_patrol_scrub_params_cache` will *fail*. I'm fairly confident in this analysis. To be completely sure, I'll need to double check the implementation of `ras2_update_patrol_scrub_params_cache` itself, and I'll find it within `drivers/ras/acpi_ras2.c`. Let's take a look. |
| 18/2 | 2026/07/04 22:27 | tool | read-file |
0mArgs:{
"File": "drivers/ras/acpi_ras2.c",
"FirstLine": 59,
"LineCount": 50
}Results: {
"Contents": " 59:\tstatic int ras2_update_patrol_scrub_params_cache(struct ras2_mem_ctx *ras2_ctx)\n 60:\t{\n 61:\t\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm =\n 62:\t\t\tTO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n 63:\t\tu32 scrub_params_out;\n 64:\t\tint ret;\n 65:\t\n 66:\t\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n 67:\t\tiowrite16(RAS2_GET_PATROL_PARAMETERS, \u0026ps_sm-\u003eparams.command);\n 68:\t\tiowrite64(ras2_ctx-\u003emem_base, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n 69:\t\tiowrite64(ras2_ctx-\u003emem_size, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n 70:\t\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n 71:\t\tif (ret) {\n 72:\t\t\tdev_err(ras2_ctx-\u003edev, \"Failed to read patrol scrub parameters\\n\");\n 73:\t\t\treturn ret;\n 74:\t\t}\n 75:\t\n 76:\t\tscrub_params_out = ioread32(\u0026ps_sm-\u003eparams.scrub_params_out);\n 77:\t\tras2_ctx-\u003emin_scrub_cycle = FIELD_GET(RAS2_PS_MIN_SC_HRS_OUT_MASK,\n 78:\t\t\t\t\t\t scrub_params_out);\n 79:\t\tras2_ctx-\u003emax_scrub_cycle = FIELD_GET(RAS2_PS_MAX_SC_HRS_OUT_MASK,\n 80:\t\t\t\t\t\t scrub_params_out);\n 81:\t\tras2_ctx-\u003escrub_cycle_hrs = FIELD_GET(RAS2_PS_SC_HRS_OUT_MASK,\n 82:\t\t\t\t\t\t scrub_params_out);\n 83:\t\tif (ras2_ctx-\u003ebg_scrub) {\n 84:\t\t\tras2_ctx-\u003eod_scrub = false;\n 85:\t\t\tras2_ctx-\u003ebase = 0;\n 86:\t\t\tras2_ctx-\u003esize = 0;\n 87:\t\t\treturn 0;\n 88:\t\t}\n 89:\t\n 90:\t\tif (ioread32(\u0026ps_sm-\u003eparams.flags) \u0026 RAS2_PS_FLAG_SCRUB_RUNNING) {\n 91:\t\t\tras2_ctx-\u003eod_scrub = true;\n 92:\t\t\tras2_ctx-\u003ebase = ioread64(\u0026ps_sm-\u003eparams.actl_addr_range[0]);\n 93:\t\t\tras2_ctx-\u003esize = ioread64(\u0026ps_sm-\u003eparams.actl_addr_range[1]);\n 94:\t\t} else {\n 95:\t\t\tras2_ctx-\u003eod_scrub = false;\n 96:\t\t}\n 97:\t\n 98:\t\treturn 0;\n 99:\t}\n 100:\t\n 101:\t/* Context - PCC lock must be held */\n 102:\tstatic int ras2_get_demand_scrub_running(struct ras2_mem_ctx *ras2_ctx, bool *running)\n 103:\t{\n 104:\t\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm =\n 105:\t\t\tTO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n 106:\t\tint ret;\n 107:\t\n 108:\t\tif (!ras2_ctx-\u003eod_scrub) {\n"
} |
| 19/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32256 output: 39 thoughts: 59 Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing as an expert: **Investigating a Potential Error Propagation** Right, so if `ras2_send_pcc_cmd` has a problem and returns an error, the function `ras2_update_patrol_scrub_params_cache` is going to reflect that by returning an error as well. I need to figure out the root cause here. This suggests a potential cascading failure, and I want to understand the impact and flow of this. My first move? I need to trace back from `ras2_update_patrol_scrub_params_cache`. I need to identify *every* place in the codebase where that function gets called. Knowing the call graph will help me narrow down the possible failure points and assess the broader system impact of that error propagation from `ras2_send_pcc_cmd`. Let's get to the call references. |
| 20/2 | 2026/07/04 22:27 | tool | read-file |
0mArgs:{
"File": "drivers/ras/acpi_ras2.c",
"FirstLine": 510,
"LineCount": 50
}Results: {
"Contents": " 510:\t\t * in the NUMA domain as well as when send command START_PATROL_SCRUBBER\n 511:\t\t * to start the demand scrubbing.\n 512:\t\t */\n 513:\t\tstart_pfn = node_start_pfn(ras2_ctx-\u003esys_comp_nid);\n 514:\t\tnum_spanned_pages = node_spanned_pages(ras2_ctx-\u003esys_comp_nid);\n 515:\t\tif (!num_spanned_pages) {\n 516:\t\t\tpr_debug(\"Failed to find PA range of NUMA node(%u)\\n\", ras2_ctx-\u003esys_comp_nid);\n 517:\t\t\treturn -EPERM;\n 518:\t\t}\n 519:\t\n 520:\t\tras2_ctx-\u003emem_base = __pfn_to_phys(start_pfn);\n 521:\t\tras2_ctx-\u003emem_size = num_spanned_pages * PAGE_SIZE;\n 522:\t\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n 523:\t\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n 524:\t\tif (ret)\n 525:\t\t\treturn ret;\n 526:\t\n 527:\t\t/* Initialize set_scrub_cycle */\n 528:\t\tif (ras2_ctx-\u003escrub_cycle_hrs \u003e ras2_ctx-\u003emin_scrub_cycle)\n 529:\t\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003escrub_cycle_hrs;\n 530:\t\telse\n 531:\t\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003emin_scrub_cycle;\n 532:\t\n 533:\t\tsprintf(scrub_name, \"acpi_ras_mem%d\", auxdev-\u003eid);\n 534:\t\n 535:\t\tras_features.ft_type\t= RAS_FEAT_SCRUB;\n 536:\t\tras_features.instance\t= 0;\n 537:\t\tras_features.scrub_ops\t= \u0026ras2_scrub_ops;\n 538:\t\tras_features.ctx\t= ras2_ctx;\n 539:\t\n 540:\t\tras2_ctx-\u003edrv_active = true;\n 541:\t\n 542:\t\treturn edac_dev_register(\u0026auxdev-\u003edev, scrub_name, NULL, 1, \u0026ras_features);\n 543:\t}\n 544:\t\n 545:\tstatic const struct auxiliary_device_id ras2_mem_dev_id_table[] = {\n 546:\t\t{ .name = RAS2_AUX_DEV_NAME \".\" RAS2_MEM_DEV_ID_NAME, },\n 547:\t\t{ }\n 548:\t};\n 549:\t\n 550:\tMODULE_DEVICE_TABLE(auxiliary, ras2_mem_dev_id_table);\n 551:\t\n 552:\tstatic struct auxiliary_driver ras2_mem_driver = {\n 553:\t\t.name = RAS2_MEM_DEV_ID_NAME,\n 554:\t\t.probe = ras2_mem_drv_probe,\n 555:\t\t.remove = ras2_mem_drv_remove,\n 556:\t\t.id_table = ras2_mem_dev_id_table,\n 557:\t};\n 558:\tmodule_auxiliary_driver(ras2_mem_driver);\n 559:\t\n"
} |
| 21/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33479 output: 22 thoughts: 671 Thoughts: **My Assessment: This Code is Unfuzzable in Virtualized Environments** Okay, here's the breakdown of my thinking. If `ras2_update_patrol_scrub_params_cache` errors, then `ras2_mem_drv_probe` fails, which means the EDAC device never gets registered. That's a problem, because it means the code I'm interested in is never even reached. The issue here is that in environments like QEMU or GCE, there's no way to automatically set the `PCC_STATUS_CMD_COMPLETE` bit after `ras2_send_pcc_cmd` clears it, making `ras2_update_patrol_scrub_params_cache` fail EVERY time. I considered a few edge cases. Could a syzkaller-injected ACPI table point the PCC shared memory to a real, responding device? It’s possible, sure, but highly unlikely that such a device would perfectly emulate PCC command completion and set the bit correctly. So, almost certainly, the driver will fail to probe. No probe means no sysfs interfaces. Then the core logic I'm looking at in `ras2_hw_scrub_set_enabled_od`, and so on, is completely unreachable. Could syzkaller inject a fault? No, that's not its typical approach unless there's an explicit software emulator for the hardware (like those in `mac80211_hwsim`, `vkms`, etc.). And there's definitely no PCC emulator in the kernel. So, again, this code's unreachable in these virtualized environments. I had to double check to see if there were any other paths to the EDAC scrub sysfs changes. The `drivers/edac/scrub.c` file adds an `enable_demand` attribute, which is visible only when `ops->get_enabled_od` is set. Right now, only `acpi_ras2.c` sets `get_enabled_od`. That means the sysfs file isn't created for any other driver. So this EDAC scrub stuff is also unreachable. Now I reviewed the instructions: They explicitly say to return `WorthFuzzing=false` if a patch involves unreachable code paths in virtualized environments, even with software emulation. Since this requires ACPI RAS2 and PCC hardware, which is *not* emulated in QEMU/GCE, it's impossible to hit. Just to be absolutely sure, I did a quick search for `pcc_mbox_request_channel` in `tools/` and `drivers/mailbox/` but found no mocks or emulations. No other mock. So, I will return `WorthFuzzing=false`. |
| 22/2 | 2026/07/04 22:27 | tool | grepper |
0mArgs:{
"Expression": "PCC_STATUS_CMD_COMPLETE"
}Results: {
"Output": "drivers/acpi/ras2.c=77=static int check_pcc_chan(struct ras2_sspcc *sspcc)\n--\ndrivers/acpi/ras2.c-93-\trc = readw_relaxed_poll_timeout(\u0026gen_comm_base-\u003estatus, status,\ndrivers/acpi/ras2.c:94:\t\t\t\t\tstatus \u0026 PCC_STATUS_CMD_COMPLETE,\ndrivers/acpi/ras2.c-95-\t\t\t\t\tPCC_MIN_POLL_USECS, sspcc-\u003edeadline_us);\n--\ndrivers/acpi/ras2.c=144=int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)\n--\ndrivers/acpi/ras2.c-219-\t\tval = ioread16(\u0026gen_comm_base-\u003estatus);\ndrivers/acpi/ras2.c:220:\t\tval |= PCC_STATUS_CMD_COMPLETE;\ndrivers/acpi/ras2.c-221-\t\tiowrite16(val, \u0026gen_comm_base-\u003estatus);\n--\ndrivers/devfreq/hisi_uncore_freq.c=186=static int hisi_uncore_cmd_send(struct hisi_uncore_freq *uncore,\n--\ndrivers/devfreq/hisi_uncore_freq.c-229-\trc = readw_poll_timeout(\u0026addr-\u003ehead.status, status,\ndrivers/devfreq/hisi_uncore_freq.c:230:\t\t\t\tstatus \u0026 (PCC_STATUS_CMD_COMPLETE |\ndrivers/devfreq/hisi_uncore_freq.c-231-\t\t\t\t\t PCC_STATUS_ERROR),\n--\ndrivers/hwmon/xgene-hwmon.c=124=static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)\n--\ndrivers/hwmon/xgene-hwmon.c-145-\tval = le16_to_cpu(READ_ONCE(generic_comm_base-\u003estatus));\ndrivers/hwmon/xgene-hwmon.c:146:\tval \u0026= ~PCC_STATUS_CMD_COMPLETE;\ndrivers/hwmon/xgene-hwmon.c-147-\tWRITE_ONCE(generic_comm_base-\u003estatus, cpu_to_le16(val));\n--\ndrivers/hwmon/xgene-hwmon.c=522=static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)\n--\ndrivers/hwmon/xgene-hwmon.c-562-\t\tif (xgene_word_tst_and_clr(\u0026generic_comm_base-\u003estatus,\ndrivers/hwmon/xgene-hwmon.c:563:\t\t\t\t\t PCC_STATUS_CMD_COMPLETE)) {\ndrivers/hwmon/xgene-hwmon.c-564-\t\t\tctx-\u003esync_msg.msg = ((u32 *)msg)[0];\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c=146=static void slimpro_i2c_pcc_rx_cb(struct mbox_client *cl, void *msg)\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c-157-\tif (xgene_word_tst_and_clr(\u0026generic_comm_base-\u003estatus,\ndrivers/i2c/busses/i2c-xgene-slimpro.c:158:\t\t\t\t PCC_STATUS_CMD_COMPLETE)) {\ndrivers/i2c/busses/i2c-xgene-slimpro.c-159-\t\tmsg = generic_comm_base + 1;\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c=169=static void slimpro_i2c_pcc_tx_prepare(struct slimpro_i2c_dev *ctx, u32 *msg)\n--\ndrivers/i2c/busses/i2c-xgene-slimpro.c-183-\tstatus = le16_to_cpu(READ_ONCE(generic_comm_base-\u003estatus));\ndrivers/i2c/busses/i2c-xgene-slimpro.c:184:\tstatus \u0026= ~PCC_STATUS_CMD_COMPLETE;\ndrivers/i2c/busses/i2c-xgene-slimpro.c-185-\tWRITE_ONCE(generic_comm_base-\u003estatus, cpu_to_le16(status));\n--\ndrivers/soc/hisilicon/kunpeng_hccs.c=232=static int hccs_wait_cmd_complete_by_poll(struct hccs_dev *hdev)\n--\ndrivers/soc/hisilicon/kunpeng_hccs.c-244-\tret = readw_poll_timeout(\u0026comm_base-\u003estatus, status,\ndrivers/soc/hisilicon/kunpeng_hccs.c:245:\t\t\t\t status \u0026 PCC_STATUS_CMD_COMPLETE,\ndrivers/soc/hisilicon/kunpeng_hccs.c-246-\t\t\t\t HCCS_POLL_STATUS_TIME_INTERVAL_US,\n--\ninclude/acpi/pcc.h=12=struct pcc_mbox_chan {\n--\ninclude/acpi/pcc.h-26-/* Generic Communications Channel Status Field */\ninclude/acpi/pcc.h:27:#define PCC_STATUS_CMD_COMPLETE\t\tBIT(0)\ninclude/acpi/pcc.h-28-#define PCC_STATUS_SCI_DOORBELL\t\tBIT(1)\n"
} |
| 23/2 | 2026/07/04 22:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 36159 output: 140 thoughts: 198 Thoughts: **Unreachable Code in QEMU: A WorthFuzzing Analysis** Okay, so I've been digging into this patch, and the verdict is pretty clear: we're dealing with unreachable code. There's no way to trigger the core logic in this environment. It's frustrating, really. The root cause is the dependency on the ACPI RAS2 memory driver. This driver *demands* a physical PCC (Platform Communication Channel) mailbox and the supporting ACPI RAS2 tables. And as we know, QEMU and other virtualization environments like Google Compute Engine don't emulate those. So, it's a dead end. Because of this critical dependency, the core logic is simply not reachable. Before giving up completely, I quickly scanned to see if *any* part of the patch could be exploited. I looked specifically at `drivers/edac/scrub.c`, where there were modifications to `scrub_attr_visible` and `scrub_create_desc`. However, those changes are only triggered if a driver sets the `get_enabled_od` flag. But, only the `acpi_ras2` driver sets that. Since `acpi_ras2` is totally unreachable... those changes are also dead. The rest of the patch appears to be documentation and Kconfig files, which are important but not immediately fuzzer candidates. Therefore, declaring `WorthFuzzing=false` is the right call here. The provided reasoning accurately describes the situation. |
| 24/2 | 2026/07/04 22:28 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch introduces an ACPI RAS2 memory driver and adds demand scrubbing support to the EDAC scrub interface. The driver relies on the ACPI RAS2 table and the Platform Communication Channel (PCC) mailbox to communicate with the hardware. Since PCC hardware and the associated ACPI tables are not emulated in virtualized environments like QEMU or GCE, the driver will fail to probe (specifically timing out while polling for PCC command completion). Consequently, the core logic and the new sysfs interfaces are impossible to reach during fuzzing.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch introduces an ACPI RAS2 memory driver and adds demand scrubbing support to the EDAC scrub interface. The driver relies on the ACPI RAS2 table and the Platform Communication Channel (PCC) mailbox to communicate with the hardware. Since PCC hardware and the associated ACPI tables are not emulated in virtualized environments like QEMU or GCE, the driver will fail to probe (specifically timing out while polling for PCC command completion). Consequently, the core logic and the new sysfs interfaces are impossible to reach during fuzzing.",
"WorthFuzzing": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|