AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/10 18:47 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "ACPI_RAS2",
    "MEM_ACPI_RAS2",
    "EDAC_SCRUB",
    "PCC"
  ],
  "FocusSymbols": [
    "parse_ras2_table",
    "ras2_send_pcc_cmd",
    "ras2_mem_drv_probe",
    "ras2_update_patrol_scrub_params_cache",
    "ras2_get_demand_scrub_running",
    "ras2_scrub_monitor_thread",
    "ras2_hw_scrub_cycle_write",
    "ras2_hw_scrub_write_addr",
    "ras2_hw_scrub_write_size",
    "__ras2_hw_scrub_set_enabled_bg",
    "ras2_hw_scrub_set_enabled_od"
  ],
  "Reasoning": "The patch introduces a new ACPI RAS2 driver that parses the RAS2 ACPI table and registers auxiliary devices for memory scrubbing. It also adds a driver that binds to these auxiliary devices and exposes EDAC scrub sysfs interfaces to control hardware-based memory scrubbing via PCC (Platform Communication Channel). Since syzkaller can inject ACPI tables (including RAS2 and PCCT) and interact with sysfs, this code is reachable and functional in virtualized environments.",
  "WorthFuzzing": true
}

1/1 2026/07/10 18:47 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 3becb4f2b72343bb754f14c619db35c7a4f38305\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 10 18:47:08 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..b66700e0e7d352\n--- /dev/null\n+++ b/drivers/acpi/ras2.c\n@@ -0,0 +1,502 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * ACPI RAS2 feature table driver.\n+ *\n+ * Copyright (c) 2024-2026 HiSilicon Limited.\n+ *\n+ * Support for RAS2 table - ACPI 6.5 Specification, section 5.2.21, which\n+ * provides interfaces for platform RAS features, e.g., for HW-based memory\n+ * scrubbing, and logical to physical address translation service. RAS2 uses\n+ * PCC channel subspace for communicating with the ACPI compliant HW platform.\n+ */\n+\n+#undef pr_fmt\n+#define pr_fmt(fmt) \"ACPI RAS2: \" fmt\n+\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/export.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/ktime.h\u003e\n+#include \u003cacpi/pcc.h\u003e\n+#include \u003cacpi/ras2.h\u003e\n+\n+/**\n+ * struct ras2_sspcc - Data structure for PCC communication\n+ * @mbox_client:\tstruct mbox_client object\n+ * @pcc_chan:\t\tPointer to struct pcc_mbox_chan\n+ * @comm_addr:\t\tPointer to RAS2 PCC shared memory region\n+ * @elem:\t\tList for registered RAS2 PCC channel subspaces\n+ * @pcc_lock:\t\tPCC lock to provide mutually exclusive access\n+ *\t\t\tto PCC channel subspace\n+ * @deadline_us:\tPoll PCC status register timeout in microsecs\n+ *\t\t\tfor PCC command completion\n+ * @pcc_mpar:\t\tMaximum Periodic Access Rate (MPAR) for PCC channel\n+ * @pcc_mrtt:\t\tMinimum Request Turnaround Time (MRTT) in microsecs\n+ *\t\t\tOS must wait after completion of a PCC command before\n+ *\t\t\tissuing next command\n+ * @last_cmd_cmpl_time: Completion time of last PCC command\n+ * @last_mpar_reset:\tTime of last MPAR count reset\n+ * @mpar_count:\t\tMPAR count\n+ * @pcc_id:\t\tIdentifier of the RAS2 platform communication channel\n+ * @last_cmd:\t\tLast PCC command\n+ * @kref:\t\tkref object\n+ */\n+struct ras2_sspcc {\n+\tstruct mbox_client\t\tmbox_client;\n+\tstruct pcc_mbox_chan\t\t*pcc_chan;\n+\tstruct acpi_ras2_shmem __iomem\t*comm_addr;\n+\tstruct list_head\t\telem;\n+\tstruct mutex\t\t\tpcc_lock;\n+\tu64\t\t\t\tdeadline_us;\n+\tunsigned int\t\t\tpcc_mpar;\n+\tunsigned int\t\t\tpcc_mrtt;\n+\tktime_t\t\t\t\tlast_cmd_cmpl_time;\n+\tktime_t\t\t\t\tlast_mpar_reset;\n+\tint\t\t\t\tmpar_count;\n+\tint\t\t\t\tpcc_id;\n+\tu16\t\t\t\tlast_cmd;\n+\tstruct kref\t\t\tkref;\n+};\n+\n+/*\n+ * Arbitrary retries for PCC commands because the remote processor could be\n+ * much slower to reply. Keep it high enough to cover emulators where the\n+ * processors run painfully slow.\n+ */\n+#define PCC_NUM_RETRIES 600ULL\n+#define PCC_CHNL_DEFAULT_LATENCY 1000\n+#define PCC_MIN_POLL_USECS 3\n+\n+#define RAS2_MAX_NUM_PCC_DESCS 100\n+#define RAS2_FEAT_TYPE_MEMORY 0x00\n+\n+/* Static variables for the RAS2 PCC subspaces */\n+static DEFINE_MUTEX(ras2_pcc_list_lock);\n+static LIST_HEAD(ras2_sspcc);\n+\n+static int check_pcc_chan(struct ras2_sspcc *sspcc)\n+{\n+\tstruct acpi_ras2_shmem __iomem *gen_comm_base = sspcc-\u003ecomm_addr;\n+\tu32 cap_status;\n+\tu16 status;\n+\tint rc;\n+\n+\t/*\n+\t * As per ACPI spec, the PCC space will be initialized by the\n+\t * platform and should have set the command completion bit when\n+\t * PCC can be used by OSPM.\n+\t *\n+\t * Poll PCC status register every PCC_MIN_POLL_USECS for maximum of\n+\t * PCC_NUM_RETRIES * PCC channel latency until PCC command complete\n+\t * bit is set.\n+\t */\n+\trc = readw_relaxed_poll_timeout(\u0026gen_comm_base-\u003estatus, status,\n+\t\t\t\t\tstatus \u0026 PCC_STATUS_CMD_COMPLETE,\n+\t\t\t\t\tPCC_MIN_POLL_USECS, sspcc-\u003edeadline_us);\n+\tif (rc) {\n+\t\tpr_warn(\"PCC ID: 0x%x: PCC check channel timeout for last command: 0x%x rc=%d\\n\",\n+\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd, rc);\n+\n+\t\treturn rc;\n+\t}\n+\n+\tif (status \u0026 PCC_STATUS_ERROR) {\n+\t\tpr_warn(\"PCC ID: 0x%x: Error in executing last command: 0x%x\\n\",\n+\t\t\tsspcc-\u003epcc_id, sspcc-\u003elast_cmd);\n+\t\tstatus \u0026= ~PCC_STATUS_ERROR;\n+\t\tiowrite16(status, \u0026gen_comm_base-\u003estatus);\n+\t\tiowrite32(0x0, \u0026gen_comm_base-\u003eset_caps_status);\n+\t\treturn -EIO;\n+\t}\n+\n+\t/* Ensure get updated PCC status */\n+\trmb();\n+\tcap_status = ioread32(\u0026gen_comm_base-\u003eset_caps_status);\n+\tswitch (cap_status) {\n+\tcase ACPI_RAS2_NOT_VALID:\n+\tcase ACPI_RAS2_NOT_SUPPORTED:\n+\t\trc = -EPERM;\n+\t\tbreak;\n+\tcase ACPI_RAS2_BUSY:\n+\t\trc = -EBUSY;\n+\t\tbreak;\n+\tcase ACPI_RAS2_FAILED:\n+\tcase ACPI_RAS2_ABORTED:\n+\tcase ACPI_RAS2_INVALID_DATA:\n+\t\trc = -EINVAL;\n+\t\tbreak;\n+\tdefault:\n+\t\trc = 0;\n+\t}\n+\n+\tiowrite32(0x0, \u0026gen_comm_base-\u003eset_caps_status);\n+\n+\treturn rc;\n+}\n+\n+/**\n+ * ras2_send_pcc_cmd() - Send RAS2 command via PCC channel\n+ * @ras2_ctx:\tpointer to the RAS2 context structure\n+ * @cmd:\tRAS2 command to send\n+ *\n+ * Returns: 0 on success, an error otherwise\n+ */\n+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)\n+{\n+\tstruct acpi_ras2_shmem __iomem *gen_comm_base;\n+\tstruct mbox_chan *pcc_channel;\n+\tstruct ras2_sspcc *sspcc;\n+\ts64 time_delta;\n+\tu16 val;\n+\tint rc;\n+\n+\tif (!ras2_ctx)\n+\t\treturn -EINVAL;\n+\n+\tlockdep_assert_held(ras2_ctx-\u003epcc_lock);\n+\tsspcc = ras2_ctx-\u003esspcc;\n+\tgen_comm_base = sspcc-\u003ecomm_addr;\n+\n+\trc = check_pcc_chan(sspcc);\n+\tif (rc \u003c 0)\n+\t\treturn rc;\n+\n+\tpcc_channel = sspcc-\u003epcc_chan-\u003emchan;\n+\n+\t/*\n+\t * Handle the Minimum Request Turnaround Time (MRTT): the minimum\n+\t * amount of time that OSPM must wait after the completion of\n+\t * a command before issuing the next command, in microseconds.\n+\t */\n+\tif (sspcc-\u003epcc_mrtt) {\n+\t\ttime_delta = ktime_us_delta(ktime_get(), sspcc-\u003elast_cmd_cmpl_time);\n+\t\tif (sspcc-\u003epcc_mrtt \u003e time_delta)\n+\t\t\tfsleep(sspcc-\u003epcc_mrtt - time_delta);\n+\t}\n+\n+\t/*\n+\t * Handle the non-zero Maximum Periodic Access Rate (MPAR): the\n+\t * maximum number of periodic requests that the subspace channel can\n+\t * support, reported in commands per minute. 0 indicates no\n+\t * limitation.\n+\t *\n+\t * This parameter should be ideally zero or large enough so that it\n+\t * can handle maximum number of requests that all the cores in the\n+\t * system can collectively generate. If it is not, follow the spec and\n+\t * just not send the request to the platform after hitting the MPAR\n+\t * limit in any 60s window.\n+\t */\n+\tif (sspcc-\u003epcc_mpar) {\n+\t\tif (!sspcc-\u003empar_count) {\n+\t\t\ttime_delta = ktime_ms_delta(ktime_get(), sspcc-\u003elast_mpar_reset);\n+\t\t\tif ((time_delta \u003c 60 * MSEC_PER_SEC) \u0026\u0026 sspcc-\u003elast_mpar_reset) {\n+\t\t\t\tdev_dbg(ras2_ctx-\u003edev,\n+\t\t\t\t\t\"PCC command 0x%x not sent due to MPAR limit\", cmd);\n+\t\t\t\treturn -EIO;\n+\t\t\t}\n+\t\t\tsspcc-\u003elast_mpar_reset = ktime_get();\n+\t\t\tsspcc-\u003empar_count = sspcc-\u003epcc_mpar;\n+\t\t}\n+\t\tsspcc-\u003empar_count--;\n+\t}\n+\n+\t/* Write to the shared comm region */\n+\tiowrite16(cmd, \u0026gen_comm_base-\u003ecommand);\n+\n+\t/* Flip CMD COMPLETE bit */\n+\tiowrite16(0, \u0026gen_comm_base-\u003estatus);\n+\n+\t/* Ring doorbell */\n+\trc = mbox_send_message(pcc_channel, \u0026cmd);\n+\t/*\n+\t * mbox_send_message() returns a non-negative integer for successful submission\n+\t * and a negative value on failure.\n+\t */\n+\tif (rc \u003c 0) {\n+\t\tdev_warn(ras2_ctx-\u003edev,\n+\t\t\t \"Error sending PCC mbox message command: 0x%x, rc:%d\\n\", cmd, rc);\n+\t\t/* Restore CMD COMPLETE bit on error */\n+\t\tval = ioread16(\u0026gen_comm_base-\u003estatus);\n+\t\tval |= PCC_STATUS_CMD_COMPLETE;\n+\t\tiowrite16(val, \u0026gen_comm_base-\u003estatus);\n+\t\treturn rc;\n+\t} else {\n+\t\trc = 0;\n+\t}\n+\n+\tsspcc-\u003elast_cmd = cmd;\n+\n+\t/*\n+\t * If Minimum Request Turnaround Time is non-zero, need to record the\n+\t * completion time of both READ and WRITE commands for proper handling\n+\t * of MRTT, so need to check for pcc_mrtt in addition to PCC_CMD_EXEC_RAS2.\n+\t */\n+\tif (cmd == PCC_CMD_EXEC_RAS2 || sspcc-\u003epcc_mrtt) {\n+\t\trc = check_pcc_chan(sspcc);\n+\t\tif (sspcc-\u003epcc_mrtt)\n+\t\t\tsspcc-\u003elast_cmd_cmpl_time = ktime_get();\n+\t}\n+\n+\tif (!pcc_channel-\u003embox-\u003etxdone_irq)\n+\t\tmbox_client_txdone(pcc_channel, rc);\n+\n+\treturn rc;\n+}\n+EXPORT_SYMBOL_FOR_MODULES(ras2_send_pcc_cmd, \"acpi_ras2\");\n+\n+static void ras2_list_pcc_release(struct kref *kref)\n+{\n+\tstruct ras2_sspcc *sspcc =\n+\t\tcontainer_of(kref, struct ras2_sspcc, kref);\n+\n+\tguard(mutex)(\u0026ras2_pcc_list_lock);\n+\tlist_del(\u0026sspcc-\u003eelem);\n+\tpcc_mbox_free_channel(sspcc-\u003epcc_chan);\n+\tkfree(sspcc);\n+}\n+\n+static void ras2_sspcc_put(struct ras2_sspcc *sspcc)\n+{\n+\tkref_put(\u0026sspcc-\u003ekref,  \u0026ras2_list_pcc_release);\n+}\n+\n+static struct ras2_sspcc *ras2_sspcc_get(int pcc_id)\n+{\n+\tstruct ras2_sspcc *sspcc;\n+\n+\tguard(mutex)(\u0026ras2_pcc_list_lock);\n+\tlist_for_each_entry(sspcc, \u0026ras2_sspcc, elem) {\n+\t\tif (sspcc-\u003epcc_id != pcc_id)\n+\t\t\tcontinue;\n+\n+\t\tif (!kref_get_unless_zero(\u0026sspcc-\u003ekref))\n+\t\t\tbreak;\n+\n+\t\treturn sspcc;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)\n+{\n+\tstruct pcc_mbox_chan *pcc_chan;\n+\tstruct ras2_sspcc *sspcc;\n+\n+\tif (pcc_id \u003c 0)\n+\t\treturn -EINVAL;\n+\n+\tsspcc = ras2_sspcc_get(pcc_id);\n+\tif (sspcc) {\n+\t\tras2_ctx-\u003esspcc\t\t= sspcc;\n+\t\tras2_ctx-\u003ecomm_addr\t= sspcc-\u003ecomm_addr;\n+\t\tras2_ctx-\u003edev\t\t=\n+\t\t\tsspcc-\u003epcc_chan-\u003emchan-\u003embox-\u003edev;\n+\t\tras2_ctx-\u003epcc_lock\t= \u0026sspcc-\u003epcc_lock;\n+\t\treturn 0;\n+\t}\n+\n+\tsspcc = kzalloc(sizeof(*sspcc), GFP_KERNEL);\n+\tif (!sspcc)\n+\t\treturn -ENOMEM;\n+\n+\tpcc_chan = pcc_mbox_request_channel(\u0026sspcc-\u003embox_client, pcc_id);\n+\tif (IS_ERR(pcc_chan)) {\n+\t\tkfree(sspcc);\n+\t\treturn PTR_ERR(pcc_chan);\n+\t}\n+\n+\tif (!pcc_chan-\u003eshmem) {\n+\t\tpcc_mbox_free_channel(pcc_chan);\n+\t\tkfree(sspcc);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tsspcc-\u003epcc_id\t\t= pcc_id;\n+\tsspcc-\u003epcc_chan\t\t= pcc_chan;\n+\tsspcc-\u003ecomm_addr\t= pcc_chan-\u003eshmem;\n+\tif (pcc_chan-\u003elatency)\n+\t\tsspcc-\u003edeadline_us = PCC_NUM_RETRIES * pcc_chan-\u003elatency;\n+\telse\n+\t\tsspcc-\u003edeadline_us = PCC_NUM_RETRIES * PCC_CHNL_DEFAULT_LATENCY;\n+\tsspcc-\u003epcc_mrtt\t\t= pcc_chan-\u003emin_turnaround_time;\n+\tsspcc-\u003epcc_mpar\t\t= pcc_chan-\u003emax_access_rate;\n+\tsspcc-\u003embox_client.knows_txdone\t= true;\n+\n+\tmutex_init(\u0026sspcc-\u003epcc_lock);\n+\tkref_init(\u0026sspcc-\u003ekref);\n+\n+\tmutex_lock(\u0026ras2_pcc_list_lock);\n+\tlist_add(\u0026sspcc-\u003eelem, \u0026ras2_sspcc);\n+\tmutex_unlock(\u0026ras2_pcc_list_lock);\n+\n+\tras2_ctx-\u003esspcc\t\t= sspcc;\n+\tras2_ctx-\u003ecomm_addr\t= sspcc-\u003ecomm_addr;\n+\tras2_ctx-\u003edev\t\t= pcc_chan-\u003emchan-\u003embox-\u003edev;\n+\tras2_ctx-\u003epcc_lock\t= \u0026sspcc-\u003epcc_lock;\n+\n+\treturn 0;\n+}\n+\n+static DEFINE_IDA(ras2_ida);\n+static void ras2_release(struct device *device)\n+{\n+\tstruct auxiliary_device *auxdev = to_auxiliary_dev(device);\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\n+\tida_free(\u0026ras2_ida, auxdev-\u003eid);\n+\tras2_sspcc_put(ras2_ctx-\u003esspcc);\n+\tkfree(ras2_ctx);\n+}\n+\n+static struct ras2_mem_ctx *add_aux_device(char *name, int channel, u32 pxm_inst)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx;\n+\tu32 comp_nid;\n+\tint id, rc;\n+\n+\tcomp_nid = pxm_to_node(pxm_inst);\n+\tif (comp_nid == NUMA_NO_NODE) {\n+\t\tpr_debug(\"Invalid NUMA node, channel=%d pxm_inst=%d\\n\", channel, pxm_inst);\n+\t\treturn ERR_PTR(-ENXIO);\n+\t}\n+\n+\tras2_ctx = kzalloc(sizeof(*ras2_ctx), GFP_KERNEL);\n+\tif (!ras2_ctx)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tras2_ctx-\u003esys_comp_nid = comp_nid;\n+\n+\trc = register_pcc_channel(ras2_ctx, channel);\n+\tif (rc \u003c 0) {\n+\t\tpr_debug(\"Failed to register PCC channel=%d pxm_inst=%d rc=%d\\n\", channel,\n+\t\t\t pxm_inst, rc);\n+\t\tgoto ctx_free;\n+\t}\n+\n+\tid = ida_alloc(\u0026ras2_ida, GFP_KERNEL);\n+\tif (id \u003c 0) {\n+\t\trc = id;\n+\t\tgoto pcc_free;\n+\t}\n+\n+\tras2_ctx-\u003eadev.id\t\t= id;\n+\tras2_ctx-\u003eadev.name\t\t= name;\n+\tras2_ctx-\u003eadev.dev.release\t= ras2_release;\n+\tras2_ctx-\u003eadev.dev.parent\t= ras2_ctx-\u003edev;\n+\n+\trc = auxiliary_device_init(\u0026ras2_ctx-\u003eadev);\n+\tif (rc)\n+\t\tgoto ida_free;\n+\n+\trc = auxiliary_device_add(\u0026ras2_ctx-\u003eadev);\n+\tif (rc) {\n+\t\tauxiliary_device_uninit(\u0026ras2_ctx-\u003eadev);\n+\t\treturn ERR_PTR(rc);\n+\t}\n+\n+\treturn ras2_ctx;\n+\n+ida_free:\n+\tida_free(\u0026ras2_ida, id);\n+pcc_free:\n+\tras2_sspcc_put(ras2_ctx-\u003esspcc);\n+ctx_free:\n+\tkfree(ras2_ctx);\n+\n+\treturn ERR_PTR(rc);\n+}\n+\n+static void remove_aux_device(struct ras2_mem_ctx *ras2_ctx)\n+{\n+\tif (!ras2_ctx)\n+\t\treturn;\n+\n+\tauxiliary_device_delete(\u0026ras2_ctx-\u003eadev);\n+\tauxiliary_device_uninit(\u0026ras2_ctx-\u003eadev);\n+}\n+\n+static int parse_ras2_table(struct acpi_table_ras2 *ras2_tab)\n+{\n+\tstruct acpi_ras2_pcc_desc *pcc_desc_list;\n+\tstruct ras2_mem_ctx **pctx_list;\n+\tstruct ras2_mem_ctx *ras2_ctx;\n+\tu16 tot_tbl_len;\n+\tu16 i;\n+\n+\tif (ras2_tab-\u003eheader.length \u003c sizeof(*ras2_tab)) {\n+\t\tpr_warn(FW_WARN \"ACPI RAS2 table present but broken (too short, size=%u)\\n\",\n+\t\t\tras2_tab-\u003eheader.length);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (!ras2_tab-\u003enum_pcc_descs || ras2_tab-\u003enum_pcc_descs \u003e RAS2_MAX_NUM_PCC_DESCS) {\n+\t\tpr_warn(FW_WARN \"No/Invalid number of PCC descs(%d) in ACPI RAS2 table\\n\",\n+\t\t\tras2_tab-\u003enum_pcc_descs);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\ttot_tbl_len = sizeof(*ras2_tab) + ras2_tab-\u003enum_pcc_descs * sizeof(*pcc_desc_list);\n+\tif (ras2_tab-\u003eheader.length \u003c tot_tbl_len) {\n+\t\tpr_warn(FW_WARN \"RAS2 table is not large enough to contain PCC descs=%d size=%u)\\n\",\n+\t\t\tras2_tab-\u003enum_pcc_descs, ras2_tab-\u003eheader.length);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpctx_list = kcalloc(ras2_tab-\u003enum_pcc_descs, sizeof(*pctx_list), GFP_KERNEL);\n+\tif (!pctx_list)\n+\t\treturn -ENOMEM;\n+\n+\tpcc_desc_list = (struct acpi_ras2_pcc_desc *)(ras2_tab + 1);\n+\tfor (i = 0; i \u003c ras2_tab-\u003enum_pcc_descs; i++, pcc_desc_list++) {\n+\t\tif (pcc_desc_list-\u003efeature_type != RAS2_FEAT_TYPE_MEMORY)\n+\t\t\tcontinue;\n+\n+\t\tras2_ctx = add_aux_device(RAS2_MEM_DEV_ID_NAME, pcc_desc_list-\u003echannel_id,\n+\t\t\t\t\t  pcc_desc_list-\u003einstance);\n+\t\t/* Invalid NUMA node, continue parsing next node */\n+\t\tif (PTR_ERR(ras2_ctx) == -ENXIO)\n+\t\t\tcontinue;\n+\n+\t\tif (IS_ERR(ras2_ctx)) {\n+\t\t\tpr_warn(\"Failed to add RAS2 auxiliary device rc=%ld\\n\", PTR_ERR(ras2_ctx));\n+\t\t\tfor (; i \u003e 0; i--) {\n+\t\t\t\tif (pctx_list[i - 1])\n+\t\t\t\t\tremove_aux_device(pctx_list[i - 1]);\n+\t\t\t}\n+\t\t\tkfree(pctx_list);\n+\t\t\treturn PTR_ERR(ras2_ctx);\n+\t\t}\n+\t\tpctx_list[i] = ras2_ctx;\n+\t}\n+\tkfree(pctx_list);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * acpi_ras2_init - RAS2 driver initialization function.\n+ *\n+ * Extracts the ACPI RAS2 table and retrieves ID for the PCC channel subspace\n+ * for communicating with the ACPI compliant HW platform. Driver adds an\n+ * auxiliary device, which binds to the memory ACPI RAS2 driver, for each RAS2\n+ * memory feature.\n+ *\n+ * Returns: none.\n+ */\n+void __init acpi_ras2_init(void)\n+{\n+\tstruct acpi_table_ras2 *ras2_tab;\n+\tacpi_status status;\n+\n+\tstatus = acpi_get_table(ACPI_SIG_RAS2, 0, (struct acpi_table_header **)\u0026ras2_tab);\n+\tif (ACPI_FAILURE(status)) {\n+\t\tpr_debug(\"Failed to get table, %s\\n\", acpi_format_exception(status));\n+\t\treturn;\n+\t}\n+\n+\tif (parse_ras2_table(ras2_tab))\n+\t\tpr_debug(\"Failed to parse RAS2 table\\n\");\n+\n+\tacpi_put_table((struct acpi_table_header *)ras2_tab);\n+}\ndiff --git a/drivers/edac/scrub.c b/drivers/edac/scrub.c\nindex f9d02af2fc3a20..f3b9a2f049500e 100644\n--- a/drivers/edac/scrub.c\n+++ b/drivers/edac/scrub.c\n@@ -14,6 +14,7 @@ enum edac_scrub_attributes {\n \tSCRUB_ADDRESS,\n \tSCRUB_SIZE,\n \tSCRUB_ENABLE_BACKGROUND,\n+\tSCRUB_ENABLE_DEMAND,\n \tSCRUB_MIN_CYCLE_DURATION,\n \tSCRUB_MAX_CYCLE_DURATION,\n \tSCRUB_CUR_CYCLE_DURATION,\n@@ -55,6 +56,7 @@ static ssize_t attrib##_show(struct device *ras_feat_dev,\t\t\t\\\n EDAC_SCRUB_ATTR_SHOW(addr, read_addr, u64, \"0x%llx\\n\")\n EDAC_SCRUB_ATTR_SHOW(size, read_size, u64, \"0x%llx\\n\")\n EDAC_SCRUB_ATTR_SHOW(enable_background, get_enabled_bg, bool, \"%u\\n\")\n+EDAC_SCRUB_ATTR_SHOW(enable_demand, get_enabled_od, bool, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(min_cycle_duration, get_min_cycle, u32, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(max_cycle_duration, get_max_cycle, u32, \"%u\\n\")\n EDAC_SCRUB_ATTR_SHOW(current_cycle_duration, get_cycle_duration, u32, \"%u\\n\")\n@@ -84,6 +86,7 @@ static ssize_t attrib##_store(struct device *ras_feat_dev,\t\t\t\\\n EDAC_SCRUB_ATTR_STORE(addr, write_addr, u64, kstrtou64)\n EDAC_SCRUB_ATTR_STORE(size, write_size, u64, kstrtou64)\n EDAC_SCRUB_ATTR_STORE(enable_background, set_enabled_bg, unsigned long, kstrtoul)\n+EDAC_SCRUB_ATTR_STORE(enable_demand, set_enabled_od, unsigned long, kstrtoul)\n EDAC_SCRUB_ATTR_STORE(current_cycle_duration, set_cycle_duration, unsigned long, kstrtoul)\n \n static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)\n@@ -119,6 +122,14 @@ static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int\n \t\t\t\treturn 0444;\n \t\t}\n \t\tbreak;\n+\tcase SCRUB_ENABLE_DEMAND:\n+\t\tif (ops-\u003eget_enabled_od) {\n+\t\t\tif (ops-\u003eset_enabled_od)\n+\t\t\t\treturn a-\u003emode;\n+\t\t\telse\n+\t\t\t\treturn 0444;\n+\t\t}\n+\t\tbreak;\n \tcase SCRUB_MIN_CYCLE_DURATION:\n \t\tif (ops-\u003eget_min_cycle)\n \t\t\treturn a-\u003emode;\n@@ -164,6 +175,7 @@ static int scrub_create_desc(struct device *scrub_dev,\n \t\t[SCRUB_ADDRESS] = EDAC_SCRUB_ATTR_RW(addr, instance),\n \t\t[SCRUB_SIZE] = EDAC_SCRUB_ATTR_RW(size, instance),\n \t\t[SCRUB_ENABLE_BACKGROUND] = EDAC_SCRUB_ATTR_RW(enable_background, instance),\n+\t\t[SCRUB_ENABLE_DEMAND] = EDAC_SCRUB_ATTR_RW(enable_demand, instance),\n \t\t[SCRUB_MIN_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(min_cycle_duration, instance),\n \t\t[SCRUB_MAX_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(max_cycle_duration, instance),\n \t\t[SCRUB_CUR_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RW(current_cycle_duration, instance)\ndiff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig\nindex fc4f4bb94a4c65..a1e6aed8bcc859 100644\n--- a/drivers/ras/Kconfig\n+++ b/drivers/ras/Kconfig\n@@ -46,4 +46,17 @@ config RAS_FMPM\n \t  Memory will be retired during boot time and run time depending on\n \t  platform-specific policies.\n \n+config MEM_ACPI_RAS2\n+\ttristate \"Memory ACPI RAS2 driver\"\n+\tdepends on ACPI_RAS2\n+\tdepends on EDAC\n+\tdepends on EDAC_SCRUB\n+\tselect NUMA_KEEP_MEMINFO\n+\thelp\n+\t  The driver binds to the auxiliary device added by the ACPI RAS2\n+\t  feature table parser. The driver uses a PCC channel subspace to\n+\t  communicate with the ACPI-compliant platform and provides\n+\t  control of the HW-based memory scrubber parameters to the user\n+\t  through the EDAC scrub interface.\n+\n endif\ndiff --git a/drivers/ras/Makefile b/drivers/ras/Makefile\nindex 11f95d59d3972d..a0e6e903d6b09b 100644\n--- a/drivers/ras/Makefile\n+++ b/drivers/ras/Makefile\n@@ -2,6 +2,7 @@\n obj-$(CONFIG_RAS)\t+= ras.o\n obj-$(CONFIG_DEBUG_FS)\t+= debugfs.o\n obj-$(CONFIG_RAS_CEC)\t+= cec.o\n+obj-$(CONFIG_MEM_ACPI_RAS2)\t+= acpi_ras2.o\n \n obj-$(CONFIG_RAS_FMPM)\t+= amd/fmpm.o\n obj-y\t\t\t+= amd/atl/\ndiff --git a/drivers/ras/acpi_ras2.c b/drivers/ras/acpi_ras2.c\nnew file mode 100644\nindex 00000000000000..268b1c38ddf905\n--- /dev/null\n+++ b/drivers/ras/acpi_ras2.c\n@@ -0,0 +1,603 @@\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\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\t\tif (!ras2_ctx-\u003ereenable_bg_scrub)\n+\t\t\tgoto exit;\n+\n+\t\t/*\n+\t\t * If ras2_get_demand_scrub_running() fails here, re-enabling background\n+\t\t * scrubbing immediately may not be possible or correct. In that case,\n+\t\t * the admin or firmware may need to re-enable background scrubbing\n+\t\t * after demand scrubbing has finished.\n+\t\t */\n+\t\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\t\tif (ret)\n+\t\t\tgoto exit;\n+\n+\t\tif (!running) {\n+\t\t\tret = __ras2_hw_scrub_set_enabled_bg(ras2_ctx-\u003edev, ras2_ctx, true);\n+\t\t\tif (ret)\n+\t\t\t\tdev_err(ras2_ctx-\u003edev,\n+\t\t\t\t\t\"Failed to enable background scrub ret=%d\\n\", ret);\n+\n+\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\tmsleep(1000);\n+\t}\n+\n+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+exit:\n+\tif (ras2_ctx-\u003edriver_active)\n+\t\tras2_ctx-\u003ethread = NULL;\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_min_scrub_cycle(struct device *dev, void *drv_data, u32 *min)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*min = ras2_ctx-\u003emin_scrub_cycle * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_max_scrub_cycle(struct device *dev, void *drv_data, u32 *max)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*max = ras2_ctx-\u003emax_scrub_cycle * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_cycle_read(struct device *dev, void *drv_data, u32 *scrub_cycle_secs)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*scrub_cycle_secs = ras2_ctx-\u003escrub_cycle_hrs * RAS2_HOUR_IN_SECS;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_cycle_write(struct device *dev, void *drv_data, u32 scrub_cycle_secs)\n+{\n+\tu32 scrub_cycle_hrs = scrub_cycle_secs / RAS2_HOUR_IN_SECS;\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tif (scrub_cycle_hrs \u003c ras2_ctx-\u003emin_scrub_cycle ||\n+\t    scrub_cycle_hrs \u003e ras2_ctx-\u003emax_scrub_cycle)\n+\t\treturn -EINVAL;\n+\n+\tras2_ctx-\u003eset_scrub_cycle = scrub_cycle_hrs;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_addr(struct device *dev, void *drv_data, u64 *base)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t/*\n+\t * When BG scrubbing is enabled the actual address range is not valid.\n+\t * Return -EBUSY now unless find out a method to retrieve actual full PA range.\n+\t */\n+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*base = ras2_ctx-\u003ebase;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_read_size(struct device *dev, void *drv_data, u64 *size)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tif (ras2_ctx-\u003ebg_scrub)\n+\t\treturn -EBUSY;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*size = ras2_ctx-\u003esize;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_write_addr(struct device *dev, void *drv_data, u64 base)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tras2_ctx-\u003ebase = base;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_write_size(struct device *dev, void *drv_data, u64 size)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tif (!size)\n+\t\treturn -EINVAL;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (running)\n+\t\treturn -EBUSY;\n+\n+\tras2_ctx-\u003esize = size;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_get_enabled_bg(struct device *dev, void *drv_data, bool *enabled)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\t*enabled = ras2_ctx-\u003ebg_scrub;\n+\n+\treturn 0;\n+}\n+\n+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tu32 scrub_params_in;\n+\tbool running;\n+\tint ret;\n+\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tif (enable) {\n+\t\tif (ras2_ctx-\u003ebg_scrub || running)\n+\t\t\treturn -EBUSY;\n+\n+\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\t\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\t\tscrub_params_in \u0026= ~RAS2_PS_SC_HRS_IN_MASK;\n+\t\tscrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx-\u003eset_scrub_cycle);\n+\t\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\t\tiowrite16(RAS2_START_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\t} else {\n+\t\tif (!ras2_ctx-\u003ebg_scrub)\n+\t\t\treturn -EPERM;\n+\n+\t\tiowrite16(RAS2_STOP_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\t}\n+\n+\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\tscrub_params_in \u0026= ~RAS2_PS_EN_BACKGROUND;\n+\tscrub_params_in |= FIELD_PREP(RAS2_PS_EN_BACKGROUND, enable);\n+\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to %s background scrubbing\\n\",\n+\t\t\tstr_enable_disable(enable));\n+\t\treturn ret;\n+\t}\n+\n+\tras2_ctx-\u003ebg_scrub = enable;\n+\tif (enable) {\n+\t\tras2_ctx-\u003ereenable_bg_scrub = false;\n+\t\t/* Update the cache to account for rounding of supplied parameters and similar */\n+\t\treturn ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\n+\treturn __ras2_hw_scrub_set_enabled_bg(dev, drv_data, enable);\n+}\n+\n+static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tbool running;\n+\tint ret;\n+\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*enabled = running;\n+\n+\treturn 0;\n+}\n+\n+static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = drv_data;\n+\tstruct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx-\u003ecomm_addr);\n+\tu32 scrub_params_in;\n+\tbool running;\n+\tint ret;\n+\n+\tif (!enable)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_get_demand_scrub_running(ras2_ctx, \u0026running);\n+\tif (ret)\n+\t\tgoto exit;\n+\n+\tif (running) {\n+\t\tret = -EBUSY;\n+\t\tgoto exit;\n+\t}\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\tgoto exit;\n+\n+\t\tras2_ctx-\u003ereenable_bg_scrub = true;\n+\t}\n+\n+\t/*\n+\t * The validity checks for the address range to scrub need to be updated\n+\t * with (base + size) \u003e (mem_base + mem_size) check in the future once a\n+\t * proper method for determining the NUMA node memory range is available.\n+\t */\n+\tif (!ras2_ctx-\u003esize || ras2_ctx-\u003ebase \u003c ras2_ctx-\u003emem_base) {\n+\t\tdev_err(dev, \"%s: Invalid address range, base=0x%llx size=0x%llx\\n\",\n+\t\t\t__func__, ras2_ctx-\u003ebase, ras2_ctx-\u003esize);\n+\t\tret = -ERANGE;\n+\t\tgoto enable_bg_scrub;\n+\t}\n+\n+\tiowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, \u0026ps_sm-\u003ecommon.set_caps[0]);\n+\tscrub_params_in = ioread32(\u0026ps_sm-\u003eparams.scrub_params_in);\n+\tscrub_params_in \u0026= ~RAS2_PS_SC_HRS_IN_MASK;\n+\tscrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx-\u003eset_scrub_cycle);\n+\tscrub_params_in \u0026= ~RAS2_PS_EN_BACKGROUND;\n+\tiowrite32(scrub_params_in, \u0026ps_sm-\u003eparams.scrub_params_in);\n+\tiowrite64(ras2_ctx-\u003ebase, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\tiowrite64(ras2_ctx-\u003esize, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\tiowrite16(RAS2_START_PATROL_SCRUBBER, \u0026ps_sm-\u003eparams.command);\n+\n+\tret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to start demand scrubbing rc(%d)\\n\", ret);\n+\t\tif (ret != -EBUSY) {\n+\t\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[0]);\n+\t\t\tiowrite64(0, \u0026ps_sm-\u003eparams.req_addr_range[1]);\n+\t\t\tras2_ctx-\u003eod_scrub = false;\n+\t\t\tras2_ctx-\u003ebase = 0;\n+\t\t\tras2_ctx-\u003esize = 0;\n+\t\t}\n+\t\tgoto enable_bg_scrub;\n+\t}\n+\n+\tras2_ctx-\u003eod_scrub = enable;\n+\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\n+\tif (ras2_ctx-\u003ereenable_bg_scrub \u0026\u0026 ras2_ctx-\u003edriver_active \u0026\u0026 !ras2_ctx-\u003ethread) {\n+\t\t/*\n+\t\t * If kthread_run() is called before sending the command for demand scrubbing,\n+\t\t * the thread may determine that demand scrubbing is not running, attempt to\n+\t\t * restart background scrubbing, and then exit. This can occur because the\n+\t\t * thread function may start running before demand scrubbing starts.\n+\t\t */\n+\t\tras2_ctx-\u003ethread = kthread_run(ras2_scrub_monitor_thread, ras2_ctx,\n+\t\t\t\t\t       \"ras2_scrub_nid%d\", ras2_ctx-\u003esys_comp_nid);\n+\t\tif (IS_ERR(ras2_ctx-\u003ethread)) {\n+\t\t\tret = PTR_ERR(ras2_ctx-\u003ethread);\n+\t\t\tras2_ctx-\u003ethread = NULL;\n+\t\t\t/*\n+\t\t\t * If kthread_run() fails and the demand scrubbing has started running,\n+\t\t\t * re-enabling background scrub will fail. Thus admin/firmware may need\n+\t\t\t * to re-enable background scrub after demand scrubbing has finished.\n+\t\t\t */\n+\t\t\tgoto enable_bg_scrub;\n+\t\t}\n+\t}\n+\n+\tgoto exit;\n+\n+enable_bg_scrub:\n+\tif (ras2_ctx-\u003ereenable_bg_scrub) {\n+\t\tras2_ctx-\u003ereenable_bg_scrub = false;\n+\t\t__ras2_hw_scrub_set_enabled_bg(dev, drv_data, true);\n+\t}\n+\n+exit:\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\treturn ret;\n+}\n+\n+static const struct edac_scrub_ops ras2_scrub_ops = {\n+\t.read_addr = ras2_hw_scrub_read_addr,\n+\t.read_size = ras2_hw_scrub_read_size,\n+\t.write_addr = ras2_hw_scrub_write_addr,\n+\t.write_size = ras2_hw_scrub_write_size,\n+\t.get_enabled_bg = ras2_hw_scrub_get_enabled_bg,\n+\t.set_enabled_bg = ras2_hw_scrub_set_enabled_bg,\n+\t.get_enabled_od = ras2_hw_scrub_get_enabled_od,\n+\t.set_enabled_od = ras2_hw_scrub_set_enabled_od,\n+\t.get_min_cycle = ras2_hw_scrub_read_min_scrub_cycle,\n+\t.get_max_cycle = ras2_hw_scrub_read_max_scrub_cycle,\n+\t.get_cycle_duration = ras2_hw_scrub_cycle_read,\n+\t.set_cycle_duration = ras2_hw_scrub_cycle_write,\n+};\n+\n+static void ras2_mem_drv_remove(struct auxiliary_device *auxdev)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\n+\tif (!ras2_ctx)\n+\t\treturn;\n+\n+\tmutex_lock(ras2_ctx-\u003epcc_lock);\n+\tras2_ctx-\u003edriver_active = false;\n+\tif (ras2_ctx-\u003ethread) {\n+\t\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+\t\tkthread_stop(ras2_ctx-\u003ethread);\n+\t\tras2_ctx-\u003ethread = NULL;\n+\t\treturn;\n+\t}\n+\tmutex_unlock(ras2_ctx-\u003epcc_lock);\n+}\n+\n+static int ras2_mem_drv_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)\n+{\n+\tstruct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);\n+\tstruct edac_dev_feature ras_features;\n+\tchar scrub_name[RAS2_SCRUB_NAME_LEN];\n+\tunsigned long start_pfn, num_spanned_pages;\n+\tint ret;\n+\n+\tif (!ras2_is_patrol_scrub_support(ras2_ctx))\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/*\n+\t * Retrieve the PA range of the NUMA domain and use it as the\n+\t * 'Requested Address Range', when send RAS2 command GET_PATROL_PARAMETERS\n+\t * to get parameters that apply to all addresses in the NUMA domain\n+\t * as well as when send command START_PATROL_SCRUBBER to start the demand\n+\t * scrubbing. The validity check for sys_comp_nid against NUMA_NO_NODE is\n+\t * already performed in add_aux_device() in /acpi/ras2.c\n+\t */\n+\tstart_pfn = node_start_pfn(ras2_ctx-\u003esys_comp_nid);\n+\tnum_spanned_pages = node_spanned_pages(ras2_ctx-\u003esys_comp_nid);\n+\tif (!num_spanned_pages) {\n+\t\tpr_debug(\"Failed to find PA range of NUMA node(%u)\\n\", ras2_ctx-\u003esys_comp_nid);\n+\t\treturn -EPERM;\n+\t}\n+\n+\tras2_ctx-\u003emem_base = __pfn_to_phys(start_pfn);\n+\tras2_ctx-\u003emem_size = (u64)num_spanned_pages * PAGE_SIZE;\n+\tguard(mutex)(ras2_ctx-\u003epcc_lock);\n+\tret = ras2_update_patrol_scrub_params_cache(ras2_ctx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Initialize set_scrub_cycle */\n+\tif (ras2_ctx-\u003escrub_cycle_hrs \u003e ras2_ctx-\u003emin_scrub_cycle)\n+\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003escrub_cycle_hrs;\n+\telse\n+\t\tras2_ctx-\u003eset_scrub_cycle = ras2_ctx-\u003emin_scrub_cycle;\n+\n+\tsprintf(scrub_name, \"acpi_ras_mem%d\", auxdev-\u003eid);\n+\n+\tras_features.ft_type\t= RAS_FEAT_SCRUB;\n+\tras_features.instance\t= 0;\n+\tras_features.scrub_ops\t= \u0026ras2_scrub_ops;\n+\tras_features.ctx\t= ras2_ctx;\n+\n+\tras2_ctx-\u003edriver_active = true;\n+\n+\treturn edac_dev_register(\u0026auxdev-\u003edev, scrub_name, NULL, 1, \u0026ras_features);\n+}\n+\n+static const struct auxiliary_device_id ras2_mem_dev_id_table[] = {\n+\t{ .name = RAS2_AUX_DEV_NAME \".\" RAS2_MEM_DEV_ID_NAME, },\n+\t{ }\n+};\n+\n+MODULE_DEVICE_TABLE(auxiliary, ras2_mem_dev_id_table);\n+\n+static struct auxiliary_driver ras2_mem_driver = {\n+\t.name = RAS2_MEM_DEV_ID_NAME,\n+\t.probe = ras2_mem_drv_probe,\n+\t.remove = ras2_mem_drv_remove,\n+\t.id_table = ras2_mem_dev_id_table,\n+};\n+module_auxiliary_driver(ras2_mem_driver);\n+\n+MODULE_IMPORT_NS(\"ACPI_RAS2\");\n+MODULE_DESCRIPTION(\"ACPI RAS2 memory driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/include/acpi/ras2.h b/include/acpi/ras2.h\nnew file mode 100644\nindex 00000000000000..ac75b0307fa9cc\n--- /dev/null\n+++ b/include/acpi/ras2.h\n@@ -0,0 +1,86 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * ACPI RAS2 (RAS Feature Table) methods.\n+ *\n+ * Copyright (c) 2024-2026 HiSilicon Limited\n+ */\n+\n+#ifndef _ACPI_RAS2_H\n+#define _ACPI_RAS2_H\n+\n+#include \u003clinux/acpi.h\u003e\n+#include \u003clinux/auxiliary_bus.h\u003e\n+#include \u003clinux/mailbox_client.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct device;\n+\n+/*\n+ * ACPI spec 6.5 Table 5.82: PCC command codes used by\n+ * RAS2 platform communication channel.\n+ */\n+#define PCC_CMD_EXEC_RAS2 0x01\n+\n+#define RAS2_AUX_DEV_NAME \"ras2\"\n+#define RAS2_MEM_DEV_ID_NAME \"acpi_ras2_mem\"\n+\n+/**\n+ * struct ras2_mem_ctx - Context for RAS2 memory features\n+ * @adev:\t\tAuxiliary device object\n+ * @comm_addr:\t\tPointer to RAS2 PCC shared memory region\n+ * @dev:\t\tPointer to device backing struct mbox_controller for PCC\n+ * @sspcc:\t\tPointer to local data structure for PCC communication\n+ * @pcc_lock:\t\tPointer to PCC lock to provide mutually exclusive access\n+ *\t\t\tto PCC channel subspace\n+ * @sys_comp_nid:\tNode ID of the system component that the RAS feature\n+ *\t\t\tis associated with. See ACPI spec 6.5 Table 5.80: RAS2\n+ *\t\t\tPlatform Communication Channel Descriptor format,\n+ *\t\t\tField: Instance\n+ * @mem_base:\t\tBase of the lowest physical continuous memory range\n+ *\t\t\tof the memory associated with the NUMA domain\n+ * @mem_size\t\tSize of the lowest physical continuous memory range\n+ *\t\t\tof the memory associated with the NUMA domain\n+ * @base:\t\tBase address of the memory region to scrub\n+ * @size:\t\tSize of the memory region to scrub\n+ * @scrub_cycle_hrs:\tCurrent scrub rate in hours\n+ * @set_scrub_cycle:\tScrub rate to set in hours\n+ * @min_scrub_cycle:\tMinimum scrub rate supported\n+ * @max_scrub_cycle:\tMaximum scrub rate supported\n+ * @od_scrub:\t\tStatus of demand scrubbing (memory region)\n+ * @bg_scrub:\t\tStatus of background patrol scrubbing\n+ * @reenable_bg_scrub:\tFlag indicates restart background scrubbing after demand\n+ *\t\t\tscrubbing is finished\n+ * @thread:\t\tDemand scrub monitor kthread\n+ * @driver_active:\tFlag indicates RAS2 memory driver is active/removed\n+ */\n+struct ras2_mem_ctx {\n+\tstruct auxiliary_device\t\tadev;\n+\tstruct acpi_ras2_shmem __iomem\t*comm_addr;\n+\tstruct device\t\t\t*dev;\n+\tvoid\t\t\t\t*sspcc;\n+\tstruct mutex\t\t\t*pcc_lock;\n+\tu32\t\t\t\tsys_comp_nid;\n+\tu64\t\t\t\tmem_base;\n+\tu64\t\t\t\tmem_size;\n+\tu64\t\t\t\tbase;\n+\tu64\t\t\t\tsize;\n+\tu8\t\t\t\tscrub_cycle_hrs;\n+\tu8\t\t\t\tset_scrub_cycle;\n+\tu8\t\t\t\tmin_scrub_cycle;\n+\tu8\t\t\t\tmax_scrub_cycle;\n+\tbool\t\t\t\tod_scrub;\n+\tbool\t\t\t\tbg_scrub;\n+\tbool\t\t\t\treenable_bg_scrub;\n+\tstruct task_struct\t\t*thread;\n+\tbool\t\t\t\tdriver_active;\n+};\n+\n+#ifdef CONFIG_ACPI_RAS2\n+void __init acpi_ras2_init(void);\n+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd);\n+#else\n+static inline void acpi_ras2_init(void) { }\n+#endif\n+\n+#endif /* _ACPI_RAS2_H */\ndiff --git a/include/linux/edac.h b/include/linux/edac.h\nindex e6b4e51130e5ff..be4f6e52ea38a0 100644\n--- a/include/linux/edac.h\n+++ b/include/linux/edac.h\n@@ -684,6 +684,8 @@ enum edac_dev_feat {\n  * @write_size: set offset of the scrubbing range.\n  * @get_enabled_bg: check if currently performing background scrub.\n  * @set_enabled_bg: start or stop a bg-scrub.\n+ * @get_enabled_od: check if currently performing demand scrub.\n+ * @set_enabled_od: start or stop a demand-scrub.\n  * @get_min_cycle: get minimum supported scrub cycle duration in seconds.\n  * @get_max_cycle: get maximum supported scrub cycle duration in seconds.\n  * @get_cycle_duration: get current scrub cycle duration in seconds.\n@@ -696,6 +698,8 @@ struct edac_scrub_ops {\n \tint (*write_size)(struct device *dev, void *drv_data, u64 size);\n \tint (*get_enabled_bg)(struct device *dev, void *drv_data, bool *enable);\n \tint (*set_enabled_bg)(struct device *dev, void *drv_data, bool enable);\n+\tint (*get_enabled_od)(struct device *dev, void *drv_data, bool *enable);\n+\tint (*set_enabled_od)(struct device *dev, void *drv_data, bool enable);\n \tint (*get_min_cycle)(struct device *dev, void *drv_data,  u32 *min);\n \tint (*get_max_cycle)(struct device *dev, void *drv_data,  u32 *max);\n \tint (*get_cycle_duration)(struct device *dev, void *drv_data, u32 *cycle);\n"
}

2/1 2026/07/10 18:47 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "ACPI_RAS2",
    "MEM_ACPI_RAS2",
    "EDAC_SCRUB",
    "PCC"
  ],
  "FocusSymbols": [
    "parse_ras2_table",
    "ras2_send_pcc_cmd",
    "ras2_mem_drv_probe",
    "ras2_update_patrol_scrub_params_cache",
    "ras2_get_demand_scrub_running",
    "ras2_scrub_monitor_thread",
    "ras2_hw_scrub_cycle_write",
    "ras2_hw_scrub_write_addr",
    "ras2_hw_scrub_write_size",
    "__ras2_hw_scrub_set_enabled_bg",
    "ras2_hw_scrub_set_enabled_od"
  ],
  "Reasoning": "The patch introduces a new ACPI RAS2 driver that parses the RAS2 ACPI table and registers auxiliary devices for memory scrubbing. It also adds a driver that binds to these auxiliary devices and exposes EDAC scrub sysfs interfaces to control hardware-based memory scrubbing via PCC (Platform Communication Channel). Since syzkaller can inject ACPI tables (including RAS2 and PCCT) and interact with sysfs, this code is reachable and functional in virtualized environments.",
  "WorthFuzzing": true
}

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 3becb4f2b72343bb754f14c619db35c7a4f38305
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 10 18:47:08 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..b66700e0e7d352
--- /dev/null
+++ b/drivers/acpi/ras2.c
@@ -0,0 +1,502 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI RAS2 feature table driver.
+ *
+ * Copyright (c) 2024-2026 HiSilicon Limited.
+ *
+ * Support for RAS2 table - ACPI 6.5 Specification, section 5.2.21, which
+ * provides interfaces for platform RAS features, e.g., for HW-based memory
+ * scrubbing, and logical to physical address translation service. RAS2 uses
+ * PCC channel subspace for communicating with the ACPI compliant HW platform.
+ */
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ACPI RAS2: " fmt
+
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/iopoll.h>
+#include <linux/ktime.h>
+#include <acpi/pcc.h>
+#include <acpi/ras2.h>
+
+/**
+ * struct ras2_sspcc - Data structure for PCC communication
+ * @mbox_client:	struct mbox_client object
+ * @pcc_chan:		Pointer to struct pcc_mbox_chan
+ * @comm_addr:		Pointer to RAS2 PCC shared memory region
+ * @elem:		List for registered RAS2 PCC channel subspaces
+ * @pcc_lock:		PCC lock to provide mutually exclusive access
+ *			to PCC channel subspace
+ * @deadline_us:	Poll PCC status register timeout in microsecs
+ *			for PCC command completion
+ * @pcc_mpar:		Maximum Periodic Access Rate (MPAR) for PCC channel
+ * @pcc_mrtt:		Minimum Request Turnaround Time (MRTT) in microsecs
+ *			OS must wait after completion of a PCC command before
+ *			issuing next command
+ * @last_cmd_cmpl_time: Completion time of last PCC command
+ * @last_mpar_reset:	Time of last MPAR count reset
+ * @mpar_count:		MPAR count
+ * @pcc_id:		Identifier of the RAS2 platform communication channel
+ * @last_cmd:		Last PCC command
+ * @kref:		kref object
+ */
+struct ras2_sspcc {
+	struct mbox_client		mbox_client;
+	struct pcc_mbox_chan		*pcc_chan;
+	struct acpi_ras2_shmem __iomem	*comm_addr;
+	struct list_head		elem;
+	struct mutex			pcc_lock;
+	u64				deadline_us;
+	unsigned int			pcc_mpar;
+	unsigned int			pcc_mrtt;
+	ktime_t				last_cmd_cmpl_time;
+	ktime_t				last_mpar_reset;
+	int				mpar_count;
+	int				pcc_id;
+	u16				last_cmd;
+	struct kref			kref;
+};
+
+/*
+ * Arbitrary retries for PCC commands because the remote processor could be
+ * much slower to reply. Keep it high enough to cover emulators where the
+ * processors run painfully slow.
+ */
+#define PCC_NUM_RETRIES 600ULL
+#define PCC_CHNL_DEFAULT_LATENCY 1000
+#define PCC_MIN_POLL_USECS 3
+
+#define RAS2_MAX_NUM_PCC_DESCS 100
+#define RAS2_FEAT_TYPE_MEMORY 0x00
+
+/* Static variables for the RAS2 PCC subspaces */
+static DEFINE_MUTEX(ras2_pcc_list_lock);
+static LIST_HEAD(ras2_sspcc);
+
+static int check_pcc_chan(struct ras2_sspcc *sspcc)
+{
+	struct acpi_ras2_shmem __iomem *gen_comm_base = sspcc->comm_addr;
+	u32 cap_status;
+	u16 status;
+	int rc;
+
+	/*
+	 * As per ACPI spec, the PCC space will be initialized by the
+	 * platform and should have set the command completion bit when
+	 * PCC can be used by OSPM.
+	 *
+	 * Poll PCC status register every PCC_MIN_POLL_USECS for maximum of
+	 * PCC_NUM_RETRIES * PCC channel latency until PCC command complete
+	 * bit is set.
+	 */
+	rc = readw_relaxed_poll_timeout(&gen_comm_base->status, status,
+					status & PCC_STATUS_CMD_COMPLETE,
+					PCC_MIN_POLL_USECS, sspcc->deadline_us);
+	if (rc) {
+		pr_warn("PCC ID: 0x%x: PCC check channel timeout for last command: 0x%x rc=%d\n",
+			sspcc->pcc_id, sspcc->last_cmd, rc);
+
+		return rc;
+	}
+
+	if (status & PCC_STATUS_ERROR) {
+		pr_warn("PCC ID: 0x%x: Error in executing last command: 0x%x\n",
+			sspcc->pcc_id, sspcc->last_cmd);
+		status &= ~PCC_STATUS_ERROR;
+		iowrite16(status, &gen_comm_base->status);
+		iowrite32(0x0, &gen_comm_base->set_caps_status);
+		return -EIO;
+	}
+
+	/* Ensure get updated PCC status */
+	rmb();
+	cap_status = ioread32(&gen_comm_base->set_caps_status);
+	switch (cap_status) {
+	case ACPI_RAS2_NOT_VALID:
+	case ACPI_RAS2_NOT_SUPPORTED:
+		rc = -EPERM;
+		break;
+	case ACPI_RAS2_BUSY:
+		rc = -EBUSY;
+		break;
+	case ACPI_RAS2_FAILED:
+	case ACPI_RAS2_ABORTED:
+	case ACPI_RAS2_INVALID_DATA:
+		rc = -EINVAL;
+		break;
+	default:
+		rc = 0;
+	}
+
+	iowrite32(0x0, &gen_comm_base->set_caps_status);
+
+	return rc;
+}
+
+/**
+ * ras2_send_pcc_cmd() - Send RAS2 command via PCC channel
+ * @ras2_ctx:	pointer to the RAS2 context structure
+ * @cmd:	RAS2 command to send
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd)
+{
+	struct acpi_ras2_shmem __iomem *gen_comm_base;
+	struct mbox_chan *pcc_channel;
+	struct ras2_sspcc *sspcc;
+	s64 time_delta;
+	u16 val;
+	int rc;
+
+	if (!ras2_ctx)
+		return -EINVAL;
+
+	lockdep_assert_held(ras2_ctx->pcc_lock);
+	sspcc = ras2_ctx->sspcc;
+	gen_comm_base = sspcc->comm_addr;
+
+	rc = check_pcc_chan(sspcc);
+	if (rc < 0)
+		return rc;
+
+	pcc_channel = sspcc->pcc_chan->mchan;
+
+	/*
+	 * Handle the Minimum Request Turnaround Time (MRTT): the minimum
+	 * amount of time that OSPM must wait after the completion of
+	 * a command before issuing the next command, in microseconds.
+	 */
+	if (sspcc->pcc_mrtt) {
+		time_delta = ktime_us_delta(ktime_get(), sspcc->last_cmd_cmpl_time);
+		if (sspcc->pcc_mrtt > time_delta)
+			fsleep(sspcc->pcc_mrtt - time_delta);
+	}
+
+	/*
+	 * Handle the non-zero Maximum Periodic Access Rate (MPAR): the
+	 * maximum number of periodic requests that the subspace channel can
+	 * support, reported in commands per minute. 0 indicates no
+	 * limitation.
+	 *
+	 * This parameter should be ideally zero or large enough so that it
+	 * can handle maximum number of requests that all the cores in the
+	 * system can collectively generate. If it is not, follow the spec and
+	 * just not send the request to the platform after hitting the MPAR
+	 * limit in any 60s window.
+	 */
+	if (sspcc->pcc_mpar) {
+		if (!sspcc->mpar_count) {
+			time_delta = ktime_ms_delta(ktime_get(), sspcc->last_mpar_reset);
+			if ((time_delta < 60 * MSEC_PER_SEC) && sspcc->last_mpar_reset) {
+				dev_dbg(ras2_ctx->dev,
+					"PCC command 0x%x not sent due to MPAR limit", cmd);
+				return -EIO;
+			}
+			sspcc->last_mpar_reset = ktime_get();
+			sspcc->mpar_count = sspcc->pcc_mpar;
+		}
+		sspcc->mpar_count--;
+	}
+
+	/* Write to the shared comm region */
+	iowrite16(cmd, &gen_comm_base->command);
+
+	/* Flip CMD COMPLETE bit */
+	iowrite16(0, &gen_comm_base->status);
+
+	/* Ring doorbell */
+	rc = mbox_send_message(pcc_channel, &cmd);
+	/*
+	 * mbox_send_message() returns a non-negative integer for successful submission
+	 * and a negative value on failure.
+	 */
+	if (rc < 0) {
+		dev_warn(ras2_ctx->dev,
+			 "Error sending PCC mbox message command: 0x%x, rc:%d\n", cmd, rc);
+		/* Restore CMD COMPLETE bit on error */
+		val = ioread16(&gen_comm_base->status);
+		val |= PCC_STATUS_CMD_COMPLETE;
+		iowrite16(val, &gen_comm_base->status);
+		return rc;
+	} else {
+		rc = 0;
+	}
+
+	sspcc->last_cmd = cmd;
+
+	/*
+	 * If Minimum Request Turnaround Time is non-zero, need to record the
+	 * completion time of both READ and WRITE commands for proper handling
+	 * of MRTT, so need to check for pcc_mrtt in addition to PCC_CMD_EXEC_RAS2.
+	 */
+	if (cmd == PCC_CMD_EXEC_RAS2 || sspcc->pcc_mrtt) {
+		rc = check_pcc_chan(sspcc);
+		if (sspcc->pcc_mrtt)
+			sspcc->last_cmd_cmpl_time = ktime_get();
+	}
+
+	if (!pcc_channel->mbox->txdone_irq)
+		mbox_client_txdone(pcc_channel, rc);
+
+	return rc;
+}
+EXPORT_SYMBOL_FOR_MODULES(ras2_send_pcc_cmd, "acpi_ras2");
+
+static void ras2_list_pcc_release(struct kref *kref)
+{
+	struct ras2_sspcc *sspcc =
+		container_of(kref, struct ras2_sspcc, kref);
+
+	guard(mutex)(&ras2_pcc_list_lock);
+	list_del(&sspcc->elem);
+	pcc_mbox_free_channel(sspcc->pcc_chan);
+	kfree(sspcc);
+}
+
+static void ras2_sspcc_put(struct ras2_sspcc *sspcc)
+{
+	kref_put(&sspcc->kref,  &ras2_list_pcc_release);
+}
+
+static struct ras2_sspcc *ras2_sspcc_get(int pcc_id)
+{
+	struct ras2_sspcc *sspcc;
+
+	guard(mutex)(&ras2_pcc_list_lock);
+	list_for_each_entry(sspcc, &ras2_sspcc, elem) {
+		if (sspcc->pcc_id != pcc_id)
+			continue;
+
+		if (!kref_get_unless_zero(&sspcc->kref))
+			break;
+
+		return sspcc;
+	}
+
+	return NULL;
+}
+
+static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)
+{
+	struct pcc_mbox_chan *pcc_chan;
+	struct ras2_sspcc *sspcc;
+
+	if (pcc_id < 0)
+		return -EINVAL;
+
+	sspcc = ras2_sspcc_get(pcc_id);
+	if (sspcc) {
+		ras2_ctx->sspcc		= sspcc;
+		ras2_ctx->comm_addr	= sspcc->comm_addr;
+		ras2_ctx->dev		=
+			sspcc->pcc_chan->mchan->mbox->dev;
+		ras2_ctx->pcc_lock	= &sspcc->pcc_lock;
+		return 0;
+	}
+
+	sspcc = kzalloc(sizeof(*sspcc), GFP_KERNEL);
+	if (!sspcc)
+		return -ENOMEM;
+
+	pcc_chan = pcc_mbox_request_channel(&sspcc->mbox_client, pcc_id);
+	if (IS_ERR(pcc_chan)) {
+		kfree(sspcc);
+		return PTR_ERR(pcc_chan);
+	}
+
+	if (!pcc_chan->shmem) {
+		pcc_mbox_free_channel(pcc_chan);
+		kfree(sspcc);
+		return -EINVAL;
+	}
+
+	sspcc->pcc_id		= pcc_id;
+	sspcc->pcc_chan		= pcc_chan;
+	sspcc->comm_addr	= pcc_chan->shmem;
+	if (pcc_chan->latency)
+		sspcc->deadline_us = PCC_NUM_RETRIES * pcc_chan->latency;
+	else
+		sspcc->deadline_us = PCC_NUM_RETRIES * PCC_CHNL_DEFAULT_LATENCY;
+	sspcc->pcc_mrtt		= pcc_chan->min_turnaround_time;
+	sspcc->pcc_mpar		= pcc_chan->max_access_rate;
+	sspcc->mbox_client.knows_txdone	= true;
+
+	mutex_init(&sspcc->pcc_lock);
+	kref_init(&sspcc->kref);
+
+	mutex_lock(&ras2_pcc_list_lock);
+	list_add(&sspcc->elem, &ras2_sspcc);
+	mutex_unlock(&ras2_pcc_list_lock);
+
+	ras2_ctx->sspcc		= sspcc;
+	ras2_ctx->comm_addr	= sspcc->comm_addr;
+	ras2_ctx->dev		= pcc_chan->mchan->mbox->dev;
+	ras2_ctx->pcc_lock	= &sspcc->pcc_lock;
+
+	return 0;
+}
+
+static DEFINE_IDA(ras2_ida);
+static void ras2_release(struct device *device)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(device);
+	struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+
+	ida_free(&ras2_ida, auxdev->id);
+	ras2_sspcc_put(ras2_ctx->sspcc);
+	kfree(ras2_ctx);
+}
+
+static struct ras2_mem_ctx *add_aux_device(char *name, int channel, u32 pxm_inst)
+{
+	struct ras2_mem_ctx *ras2_ctx;
+	u32 comp_nid;
+	int id, rc;
+
+	comp_nid = pxm_to_node(pxm_inst);
+	if (comp_nid == NUMA_NO_NODE) {
+		pr_debug("Invalid NUMA node, channel=%d pxm_inst=%d\n", channel, pxm_inst);
+		return ERR_PTR(-ENXIO);
+	}
+
+	ras2_ctx = kzalloc(sizeof(*ras2_ctx), GFP_KERNEL);
+	if (!ras2_ctx)
+		return ERR_PTR(-ENOMEM);
+
+	ras2_ctx->sys_comp_nid = comp_nid;
+
+	rc = register_pcc_channel(ras2_ctx, channel);
+	if (rc < 0) {
+		pr_debug("Failed to register PCC channel=%d pxm_inst=%d rc=%d\n", channel,
+			 pxm_inst, rc);
+		goto ctx_free;
+	}
+
+	id = ida_alloc(&ras2_ida, GFP_KERNEL);
+	if (id < 0) {
+		rc = id;
+		goto pcc_free;
+	}
+
+	ras2_ctx->adev.id		= id;
+	ras2_ctx->adev.name		= name;
+	ras2_ctx->adev.dev.release	= ras2_release;
+	ras2_ctx->adev.dev.parent	= ras2_ctx->dev;
+
+	rc = auxiliary_device_init(&ras2_ctx->adev);
+	if (rc)
+		goto ida_free;
+
+	rc = auxiliary_device_add(&ras2_ctx->adev);
+	if (rc) {
+		auxiliary_device_uninit(&ras2_ctx->adev);
+		return ERR_PTR(rc);
+	}
+
+	return ras2_ctx;
+
+ida_free:
+	ida_free(&ras2_ida, id);
+pcc_free:
+	ras2_sspcc_put(ras2_ctx->sspcc);
+ctx_free:
+	kfree(ras2_ctx);
+
+	return ERR_PTR(rc);
+}
+
+static void remove_aux_device(struct ras2_mem_ctx *ras2_ctx)
+{
+	if (!ras2_ctx)
+		return;
+
+	auxiliary_device_delete(&ras2_ctx->adev);
+	auxiliary_device_uninit(&ras2_ctx->adev);
+}
+
+static int parse_ras2_table(struct acpi_table_ras2 *ras2_tab)
+{
+	struct acpi_ras2_pcc_desc *pcc_desc_list;
+	struct ras2_mem_ctx **pctx_list;
+	struct ras2_mem_ctx *ras2_ctx;
+	u16 tot_tbl_len;
+	u16 i;
+
+	if (ras2_tab->header.length < sizeof(*ras2_tab)) {
+		pr_warn(FW_WARN "ACPI RAS2 table present but broken (too short, size=%u)\n",
+			ras2_tab->header.length);
+		return -EINVAL;
+	}
+
+	if (!ras2_tab->num_pcc_descs || ras2_tab->num_pcc_descs > RAS2_MAX_NUM_PCC_DESCS) {
+		pr_warn(FW_WARN "No/Invalid number of PCC descs(%d) in ACPI RAS2 table\n",
+			ras2_tab->num_pcc_descs);
+		return -EINVAL;
+	}
+
+	tot_tbl_len = sizeof(*ras2_tab) + ras2_tab->num_pcc_descs * sizeof(*pcc_desc_list);
+	if (ras2_tab->header.length < tot_tbl_len) {
+		pr_warn(FW_WARN "RAS2 table is not large enough to contain PCC descs=%d size=%u)\n",
+			ras2_tab->num_pcc_descs, ras2_tab->header.length);
+		return -EINVAL;
+	}
+
+	pctx_list = kcalloc(ras2_tab->num_pcc_descs, sizeof(*pctx_list), GFP_KERNEL);
+	if (!pctx_list)
+		return -ENOMEM;
+
+	pcc_desc_list = (struct acpi_ras2_pcc_desc *)(ras2_tab + 1);
+	for (i = 0; i < ras2_tab->num_pcc_descs; i++, pcc_desc_list++) {
+		if (pcc_desc_list->feature_type != RAS2_FEAT_TYPE_MEMORY)
+			continue;
+
+		ras2_ctx = add_aux_device(RAS2_MEM_DEV_ID_NAME, pcc_desc_list->channel_id,
+					  pcc_desc_list->instance);
+		/* Invalid NUMA node, continue parsing next node */
+		if (PTR_ERR(ras2_ctx) == -ENXIO)
+			continue;
+
+		if (IS_ERR(ras2_ctx)) {
+			pr_warn("Failed to add RAS2 auxiliary device rc=%ld\n", PTR_ERR(ras2_ctx));
+			for (; i > 0; i--) {
+				if (pctx_list[i - 1])
+					remove_aux_device(pctx_list[i - 1]);
+			}
+			kfree(pctx_list);
+			return PTR_ERR(ras2_ctx);
+		}
+		pctx_list[i] = ras2_ctx;
+	}
+	kfree(pctx_list);
+
+	return 0;
+}
+
+/**
+ * acpi_ras2_init - RAS2 driver initialization function.
+ *
+ * Extracts the ACPI RAS2 table and retrieves ID for the PCC channel subspace
+ * for communicating with the ACPI compliant HW platform. Driver adds an
+ * auxiliary device, which binds to the memory ACPI RAS2 driver, for each RAS2
+ * memory feature.
+ *
+ * Returns: none.
+ */
+void __init acpi_ras2_init(void)
+{
+	struct acpi_table_ras2 *ras2_tab;
+	acpi_status status;
+
+	status = acpi_get_table(ACPI_SIG_RAS2, 0, (struct acpi_table_header **)&ras2_tab);
+	if (ACPI_FAILURE(status)) {
+		pr_debug("Failed to get table, %s\n", acpi_format_exception(status));
+		return;
+	}
+
+	if (parse_ras2_table(ras2_tab))
+		pr_debug("Failed to parse RAS2 table\n");
+
+	acpi_put_table((struct acpi_table_header *)ras2_tab);
+}
diff --git a/drivers/edac/scrub.c b/drivers/edac/scrub.c
index f9d02af2fc3a20..f3b9a2f049500e 100644
--- a/drivers/edac/scrub.c
+++ b/drivers/edac/scrub.c
@@ -14,6 +14,7 @@ enum edac_scrub_attributes {
 	SCRUB_ADDRESS,
 	SCRUB_SIZE,
 	SCRUB_ENABLE_BACKGROUND,
+	SCRUB_ENABLE_DEMAND,
 	SCRUB_MIN_CYCLE_DURATION,
 	SCRUB_MAX_CYCLE_DURATION,
 	SCRUB_CUR_CYCLE_DURATION,
@@ -55,6 +56,7 @@ static ssize_t attrib##_show(struct device *ras_feat_dev,			\
 EDAC_SCRUB_ATTR_SHOW(addr, read_addr, u64, "0x%llx\n")
 EDAC_SCRUB_ATTR_SHOW(size, read_size, u64, "0x%llx\n")
 EDAC_SCRUB_ATTR_SHOW(enable_background, get_enabled_bg, bool, "%u\n")
+EDAC_SCRUB_ATTR_SHOW(enable_demand, get_enabled_od, bool, "%u\n")
 EDAC_SCRUB_ATTR_SHOW(min_cycle_duration, get_min_cycle, u32, "%u\n")
 EDAC_SCRUB_ATTR_SHOW(max_cycle_duration, get_max_cycle, u32, "%u\n")
 EDAC_SCRUB_ATTR_SHOW(current_cycle_duration, get_cycle_duration, u32, "%u\n")
@@ -84,6 +86,7 @@ static ssize_t attrib##_store(struct device *ras_feat_dev,			\
 EDAC_SCRUB_ATTR_STORE(addr, write_addr, u64, kstrtou64)
 EDAC_SCRUB_ATTR_STORE(size, write_size, u64, kstrtou64)
 EDAC_SCRUB_ATTR_STORE(enable_background, set_enabled_bg, unsigned long, kstrtoul)
+EDAC_SCRUB_ATTR_STORE(enable_demand, set_enabled_od, unsigned long, kstrtoul)
 EDAC_SCRUB_ATTR_STORE(current_cycle_duration, set_cycle_duration, unsigned long, kstrtoul)
 
 static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)
@@ -119,6 +122,14 @@ static umode_t scrub_attr_visible(struct kobject *kobj, struct attribute *a, int
 				return 0444;
 		}
 		break;
+	case SCRUB_ENABLE_DEMAND:
+		if (ops->get_enabled_od) {
+			if (ops->set_enabled_od)
+				return a->mode;
+			else
+				return 0444;
+		}
+		break;
 	case SCRUB_MIN_CYCLE_DURATION:
 		if (ops->get_min_cycle)
 			return a->mode;
@@ -164,6 +175,7 @@ static int scrub_create_desc(struct device *scrub_dev,
 		[SCRUB_ADDRESS] = EDAC_SCRUB_ATTR_RW(addr, instance),
 		[SCRUB_SIZE] = EDAC_SCRUB_ATTR_RW(size, instance),
 		[SCRUB_ENABLE_BACKGROUND] = EDAC_SCRUB_ATTR_RW(enable_background, instance),
+		[SCRUB_ENABLE_DEMAND] = EDAC_SCRUB_ATTR_RW(enable_demand, instance),
 		[SCRUB_MIN_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(min_cycle_duration, instance),
 		[SCRUB_MAX_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RO(max_cycle_duration, instance),
 		[SCRUB_CUR_CYCLE_DURATION] = EDAC_SCRUB_ATTR_RW(current_cycle_duration, instance)
diff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig
index fc4f4bb94a4c65..a1e6aed8bcc859 100644
--- a/drivers/ras/Kconfig
+++ b/drivers/ras/Kconfig
@@ -46,4 +46,17 @@ config RAS_FMPM
 	  Memory will be retired during boot time and run time depending on
 	  platform-specific policies.
 
+config MEM_ACPI_RAS2
+	tristate "Memory ACPI RAS2 driver"
+	depends on ACPI_RAS2
+	depends on EDAC
+	depends on EDAC_SCRUB
+	select NUMA_KEEP_MEMINFO
+	help
+	  The driver binds to the auxiliary device added by the ACPI RAS2
+	  feature table parser. The driver uses a PCC channel subspace to
+	  communicate with the ACPI-compliant platform and provides
+	  control of the HW-based memory scrubber parameters to the user
+	  through the EDAC scrub interface.
+
 endif
diff --git a/drivers/ras/Makefile b/drivers/ras/Makefile
index 11f95d59d3972d..a0e6e903d6b09b 100644
--- a/drivers/ras/Makefile
+++ b/drivers/ras/Makefile
@@ -2,6 +2,7 @@
 obj-$(CONFIG_RAS)	+= ras.o
 obj-$(CONFIG_DEBUG_FS)	+= debugfs.o
 obj-$(CONFIG_RAS_CEC)	+= cec.o
+obj-$(CONFIG_MEM_ACPI_RAS2)	+= acpi_ras2.o
 
 obj-$(CONFIG_RAS_FMPM)	+= amd/fmpm.o
 obj-y			+= amd/atl/
diff --git a/drivers/ras/acpi_ras2.c b/drivers/ras/acpi_ras2.c
new file mode 100644
index 00000000000000..268b1c38ddf905
--- /dev/null
+++ b/drivers/ras/acpi_ras2.c
@@ -0,0 +1,603 @@
+// 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()) {
+		mutex_lock(ras2_ctx->pcc_lock);
+		if (!ras2_ctx->reenable_bg_scrub)
+			goto exit;
+
+		/*
+		 * If ras2_get_demand_scrub_running() fails here, re-enabling background
+		 * scrubbing immediately may not be possible or correct. In that case,
+		 * the admin or firmware may need to re-enable background scrubbing
+		 * after demand scrubbing has finished.
+		 */
+		ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+		if (ret)
+			goto exit;
+
+		if (!running) {
+			ret = __ras2_hw_scrub_set_enabled_bg(ras2_ctx->dev, ras2_ctx, true);
+			if (ret)
+				dev_err(ras2_ctx->dev,
+					"Failed to enable background scrub ret=%d\n", ret);
+
+			goto exit;
+		}
+
+		mutex_unlock(ras2_ctx->pcc_lock);
+		msleep(1000);
+	}
+
+	mutex_lock(ras2_ctx->pcc_lock);
+exit:
+	if (ras2_ctx->driver_active)
+		ras2_ctx->thread = NULL;
+	mutex_unlock(ras2_ctx->pcc_lock);
+
+	return 0;
+}
+
+static int ras2_hw_scrub_read_min_scrub_cycle(struct device *dev, void *drv_data, u32 *min)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	*min = ras2_ctx->min_scrub_cycle * RAS2_HOUR_IN_SECS;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_read_max_scrub_cycle(struct device *dev, void *drv_data, u32 *max)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	*max = ras2_ctx->max_scrub_cycle * RAS2_HOUR_IN_SECS;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_cycle_read(struct device *dev, void *drv_data, u32 *scrub_cycle_secs)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	*scrub_cycle_secs = ras2_ctx->scrub_cycle_hrs * RAS2_HOUR_IN_SECS;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_cycle_write(struct device *dev, void *drv_data, u32 scrub_cycle_secs)
+{
+	u32 scrub_cycle_hrs = scrub_cycle_secs / RAS2_HOUR_IN_SECS;
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	bool running;
+	int ret;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	if (ras2_ctx->bg_scrub)
+		return -EBUSY;
+
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		return ret;
+
+	if (running)
+		return -EBUSY;
+
+	if (scrub_cycle_hrs < ras2_ctx->min_scrub_cycle ||
+	    scrub_cycle_hrs > ras2_ctx->max_scrub_cycle)
+		return -EINVAL;
+
+	ras2_ctx->set_scrub_cycle = scrub_cycle_hrs;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_read_addr(struct device *dev, void *drv_data, u64 *base)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	int ret;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	/*
+	 * When BG scrubbing is enabled the actual address range is not valid.
+	 * Return -EBUSY now unless find out a method to retrieve actual full PA range.
+	 */
+	if (ras2_ctx->bg_scrub)
+		return -EBUSY;
+
+	ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+	if (ret)
+		return ret;
+
+	*base = ras2_ctx->base;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_read_size(struct device *dev, void *drv_data, u64 *size)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	int ret;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	if (ras2_ctx->bg_scrub)
+		return -EBUSY;
+
+	ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+	if (ret)
+		return ret;
+
+	*size = ras2_ctx->size;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_write_addr(struct device *dev, void *drv_data, u64 base)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	bool running;
+	int ret;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		return ret;
+
+	if (running)
+		return -EBUSY;
+
+	ras2_ctx->base = base;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_write_size(struct device *dev, void *drv_data, u64 size)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	bool running;
+	int ret;
+
+	if (!size)
+		return -EINVAL;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		return ret;
+
+	if (running)
+		return -EBUSY;
+
+	ras2_ctx->size = size;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_get_enabled_bg(struct device *dev, void *drv_data, bool *enabled)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	*enabled = ras2_ctx->bg_scrub;
+
+	return 0;
+}
+
+static int __ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	struct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+	u32 scrub_params_in;
+	bool running;
+	int ret;
+
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		return ret;
+
+	iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+	if (enable) {
+		if (ras2_ctx->bg_scrub || running)
+			return -EBUSY;
+
+		iowrite64(0, &ps_sm->params.req_addr_range[0]);
+		iowrite64(0, &ps_sm->params.req_addr_range[1]);
+		scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+		scrub_params_in &= ~RAS2_PS_SC_HRS_IN_MASK;
+		scrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx->set_scrub_cycle);
+		iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+		iowrite16(RAS2_START_PATROL_SCRUBBER, &ps_sm->params.command);
+	} else {
+		if (!ras2_ctx->bg_scrub)
+			return -EPERM;
+
+		iowrite16(RAS2_STOP_PATROL_SCRUBBER, &ps_sm->params.command);
+	}
+
+	scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+	scrub_params_in &= ~RAS2_PS_EN_BACKGROUND;
+	scrub_params_in |= FIELD_PREP(RAS2_PS_EN_BACKGROUND, enable);
+	iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+	ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+	if (ret) {
+		dev_err(dev, "Failed to %s background scrubbing\n",
+			str_enable_disable(enable));
+		return ret;
+	}
+
+	ras2_ctx->bg_scrub = enable;
+	if (enable) {
+		ras2_ctx->reenable_bg_scrub = false;
+		/* Update the cache to account for rounding of supplied parameters and similar */
+		return ras2_update_patrol_scrub_params_cache(ras2_ctx);
+	}
+
+	return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+
+	return __ras2_hw_scrub_set_enabled_bg(dev, drv_data, enable);
+}
+
+static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	bool running;
+	int ret;
+
+	guard(mutex)(ras2_ctx->pcc_lock);
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		return ret;
+
+	*enabled = running;
+
+	return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable)
+{
+	struct ras2_mem_ctx *ras2_ctx = drv_data;
+	struct acpi_ras2_ps_shared_mem __iomem *ps_sm = TO_ACPI_RAS2_PS_SHMEM(ras2_ctx->comm_addr);
+	u32 scrub_params_in;
+	bool running;
+	int ret;
+
+	if (!enable)
+		return -EOPNOTSUPP;
+
+	mutex_lock(ras2_ctx->pcc_lock);
+	ret = ras2_get_demand_scrub_running(ras2_ctx, &running);
+	if (ret)
+		goto exit;
+
+	if (running) {
+		ret = -EBUSY;
+		goto exit;
+	}
+
+	/* 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)
+			goto exit;
+
+		ras2_ctx->reenable_bg_scrub = true;
+	}
+
+	/*
+	 * The validity checks for the address range to scrub need to be updated
+	 * with (base + size) > (mem_base + mem_size) check in the future once a
+	 * proper method for determining the NUMA node memory range is available.
+	 */
+	if (!ras2_ctx->size || ras2_ctx->base < ras2_ctx->mem_base) {
+		dev_err(dev, "%s: Invalid address range, base=0x%llx size=0x%llx\n",
+			__func__, ras2_ctx->base, ras2_ctx->size);
+		ret = -ERANGE;
+		goto enable_bg_scrub;
+	}
+
+	iowrite8(RAS2_SUPPORT_HW_PARTOL_SCRUB, &ps_sm->common.set_caps[0]);
+	scrub_params_in = ioread32(&ps_sm->params.scrub_params_in);
+	scrub_params_in &= ~RAS2_PS_SC_HRS_IN_MASK;
+	scrub_params_in |= FIELD_PREP(RAS2_PS_SC_HRS_IN_MASK, ras2_ctx->set_scrub_cycle);
+	scrub_params_in &= ~RAS2_PS_EN_BACKGROUND;
+	iowrite32(scrub_params_in, &ps_sm->params.scrub_params_in);
+	iowrite64(ras2_ctx->base, &ps_sm->params.req_addr_range[0]);
+	iowrite64(ras2_ctx->size, &ps_sm->params.req_addr_range[1]);
+	iowrite16(RAS2_START_PATROL_SCRUBBER, &ps_sm->params.command);
+
+	ret = ras2_send_pcc_cmd(ras2_ctx, PCC_CMD_EXEC_RAS2);
+	if (ret) {
+		dev_err(dev, "Failed to start demand scrubbing rc(%d)\n", ret);
+		if (ret != -EBUSY) {
+			iowrite64(0, &ps_sm->params.req_addr_range[0]);
+			iowrite64(0, &ps_sm->params.req_addr_range[1]);
+			ras2_ctx->od_scrub = false;
+			ras2_ctx->base = 0;
+			ras2_ctx->size = 0;
+		}
+		goto enable_bg_scrub;
+	}
+
+	ras2_ctx->od_scrub = enable;
+
+	ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+
+	if (ras2_ctx->reenable_bg_scrub && ras2_ctx->driver_active && !ras2_ctx->thread) {
+		/*
+		 * If kthread_run() is called before sending the command for demand scrubbing,
+		 * the thread may determine that demand scrubbing is not running, attempt to
+		 * restart background scrubbing, and then exit. This can occur because the
+		 * thread function may start running before demand scrubbing starts.
+		 */
+		ras2_ctx->thread = kthread_run(ras2_scrub_monitor_thread, ras2_ctx,
+					       "ras2_scrub_nid%d", ras2_ctx->sys_comp_nid);
+		if (IS_ERR(ras2_ctx->thread)) {
+			ret = PTR_ERR(ras2_ctx->thread);
+			ras2_ctx->thread = NULL;
+			/*
+			 * If kthread_run() fails and the demand scrubbing has started running,
+			 * re-enabling background scrub will fail. Thus admin/firmware may need
+			 * to re-enable background scrub after demand scrubbing has finished.
+			 */
+			goto enable_bg_scrub;
+		}
+	}
+
+	goto exit;
+
+enable_bg_scrub:
+	if (ras2_ctx->reenable_bg_scrub) {
+		ras2_ctx->reenable_bg_scrub = false;
+		__ras2_hw_scrub_set_enabled_bg(dev, drv_data, true);
+	}
+
+exit:
+	mutex_unlock(ras2_ctx->pcc_lock);
+	return ret;
+}
+
+static const struct edac_scrub_ops ras2_scrub_ops = {
+	.read_addr = ras2_hw_scrub_read_addr,
+	.read_size = ras2_hw_scrub_read_size,
+	.write_addr = ras2_hw_scrub_write_addr,
+	.write_size = ras2_hw_scrub_write_size,
+	.get_enabled_bg = ras2_hw_scrub_get_enabled_bg,
+	.set_enabled_bg = ras2_hw_scrub_set_enabled_bg,
+	.get_enabled_od = ras2_hw_scrub_get_enabled_od,
+	.set_enabled_od = ras2_hw_scrub_set_enabled_od,
+	.get_min_cycle = ras2_hw_scrub_read_min_scrub_cycle,
+	.get_max_cycle = ras2_hw_scrub_read_max_scrub_cycle,
+	.get_cycle_duration = ras2_hw_scrub_cycle_read,
+	.set_cycle_duration = ras2_hw_scrub_cycle_write,
+};
+
+static void ras2_mem_drv_remove(struct auxiliary_device *auxdev)
+{
+	struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+
+	if (!ras2_ctx)
+		return;
+
+	mutex_lock(ras2_ctx->pcc_lock);
+	ras2_ctx->driver_active = false;
+	if (ras2_ctx->thread) {
+		mutex_unlock(ras2_ctx->pcc_lock);
+		kthread_stop(ras2_ctx->thread);
+		ras2_ctx->thread = NULL;
+		return;
+	}
+	mutex_unlock(ras2_ctx->pcc_lock);
+}
+
+static int ras2_mem_drv_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+	struct ras2_mem_ctx *ras2_ctx = container_of(auxdev, struct ras2_mem_ctx, adev);
+	struct edac_dev_feature ras_features;
+	char scrub_name[RAS2_SCRUB_NAME_LEN];
+	unsigned long start_pfn, num_spanned_pages;
+	int ret;
+
+	if (!ras2_is_patrol_scrub_support(ras2_ctx))
+		return -EOPNOTSUPP;
+
+	/*
+	 * Retrieve the PA range of the NUMA domain and use it as the
+	 * 'Requested Address Range', when send RAS2 command GET_PATROL_PARAMETERS
+	 * to get parameters that apply to all addresses in the NUMA domain
+	 * as well as when send command START_PATROL_SCRUBBER to start the demand
+	 * scrubbing. The validity check for sys_comp_nid against NUMA_NO_NODE is
+	 * already performed in add_aux_device() in /acpi/ras2.c
+	 */
+	start_pfn = node_start_pfn(ras2_ctx->sys_comp_nid);
+	num_spanned_pages = node_spanned_pages(ras2_ctx->sys_comp_nid);
+	if (!num_spanned_pages) {
+		pr_debug("Failed to find PA range of NUMA node(%u)\n", ras2_ctx->sys_comp_nid);
+		return -EPERM;
+	}
+
+	ras2_ctx->mem_base = __pfn_to_phys(start_pfn);
+	ras2_ctx->mem_size = (u64)num_spanned_pages * PAGE_SIZE;
+	guard(mutex)(ras2_ctx->pcc_lock);
+	ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+	if (ret)
+		return ret;
+
+	/* Initialize set_scrub_cycle */
+	if (ras2_ctx->scrub_cycle_hrs > ras2_ctx->min_scrub_cycle)
+		ras2_ctx->set_scrub_cycle = ras2_ctx->scrub_cycle_hrs;
+	else
+		ras2_ctx->set_scrub_cycle = ras2_ctx->min_scrub_cycle;
+
+	sprintf(scrub_name, "acpi_ras_mem%d", auxdev->id);
+
+	ras_features.ft_type	= RAS_FEAT_SCRUB;
+	ras_features.instance	= 0;
+	ras_features.scrub_ops	= &ras2_scrub_ops;
+	ras_features.ctx	= ras2_ctx;
+
+	ras2_ctx->driver_active = true;
+
+	return edac_dev_register(&auxdev->dev, scrub_name, NULL, 1, &ras_features);
+}
+
+static const struct auxiliary_device_id ras2_mem_dev_id_table[] = {
+	{ .name = RAS2_AUX_DEV_NAME "." RAS2_MEM_DEV_ID_NAME, },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(auxiliary, ras2_mem_dev_id_table);
+
+static struct auxiliary_driver ras2_mem_driver = {
+	.name = RAS2_MEM_DEV_ID_NAME,
+	.probe = ras2_mem_drv_probe,
+	.remove = ras2_mem_drv_remove,
+	.id_table = ras2_mem_dev_id_table,
+};
+module_auxiliary_driver(ras2_mem_driver);
+
+MODULE_IMPORT_NS("ACPI_RAS2");
+MODULE_DESCRIPTION("ACPI RAS2 memory driver");
+MODULE_LICENSE("GPL");
diff --git a/include/acpi/ras2.h b/include/acpi/ras2.h
new file mode 100644
index 00000000000000..ac75b0307fa9cc
--- /dev/null
+++ b/include/acpi/ras2.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ACPI RAS2 (RAS Feature Table) methods.
+ *
+ * Copyright (c) 2024-2026 HiSilicon Limited
+ */
+
+#ifndef _ACPI_RAS2_H
+#define _ACPI_RAS2_H
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+struct device;
+
+/*
+ * ACPI spec 6.5 Table 5.82: PCC command codes used by
+ * RAS2 platform communication channel.
+ */
+#define PCC_CMD_EXEC_RAS2 0x01
+
+#define RAS2_AUX_DEV_NAME "ras2"
+#define RAS2_MEM_DEV_ID_NAME "acpi_ras2_mem"
+
+/**
+ * struct ras2_mem_ctx - Context for RAS2 memory features
+ * @adev:		Auxiliary device object
+ * @comm_addr:		Pointer to RAS2 PCC shared memory region
+ * @dev:		Pointer to device backing struct mbox_controller for PCC
+ * @sspcc:		Pointer to local data structure for PCC communication
+ * @pcc_lock:		Pointer to PCC lock to provide mutually exclusive access
+ *			to PCC channel subspace
+ * @sys_comp_nid:	Node ID of the system component that the RAS feature
+ *			is associated with. See ACPI spec 6.5 Table 5.80: RAS2
+ *			Platform Communication Channel Descriptor format,
+ *			Field: Instance
+ * @mem_base:		Base of the lowest physical continuous memory range
+ *			of the memory associated with the NUMA domain
+ * @mem_size		Size of the lowest physical continuous memory range
+ *			of the memory associated with the NUMA domain
+ * @base:		Base address of the memory region to scrub
+ * @size:		Size of the memory region to scrub
+ * @scrub_cycle_hrs:	Current scrub rate in hours
+ * @set_scrub_cycle:	Scrub rate to set in hours
+ * @min_scrub_cycle:	Minimum scrub rate supported
+ * @max_scrub_cycle:	Maximum scrub rate supported
+ * @od_scrub:		Status of demand scrubbing (memory region)
+ * @bg_scrub:		Status of background patrol scrubbing
+ * @reenable_bg_scrub:	Flag indicates restart background scrubbing after demand
+ *			scrubbing is finished
+ * @thread:		Demand scrub monitor kthread
+ * @driver_active:	Flag indicates RAS2 memory driver is active/removed
+ */
+struct ras2_mem_ctx {
+	struct auxiliary_device		adev;
+	struct acpi_ras2_shmem __iomem	*comm_addr;
+	struct device			*dev;
+	void				*sspcc;
+	struct mutex			*pcc_lock;
+	u32				sys_comp_nid;
+	u64				mem_base;
+	u64				mem_size;
+	u64				base;
+	u64				size;
+	u8				scrub_cycle_hrs;
+	u8				set_scrub_cycle;
+	u8				min_scrub_cycle;
+	u8				max_scrub_cycle;
+	bool				od_scrub;
+	bool				bg_scrub;
+	bool				reenable_bg_scrub;
+	struct task_struct		*thread;
+	bool				driver_active;
+};
+
+#ifdef CONFIG_ACPI_RAS2
+void __init acpi_ras2_init(void);
+int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd);
+#else
+static inline void acpi_ras2_init(void) { }
+#endif
+
+#endif /* _ACPI_RAS2_H */
diff --git a/include/linux/edac.h b/include/linux/edac.h
index e6b4e51130e5ff..be4f6e52ea38a0 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -684,6 +684,8 @@ enum edac_dev_feat {
  * @write_size: set offset of the scrubbing range.
  * @get_enabled_bg: check if currently performing background scrub.
  * @set_enabled_bg: start or stop a bg-scrub.
+ * @get_enabled_od: check if currently performing demand scrub.
+ * @set_enabled_od: start or stop a demand-scrub.
  * @get_min_cycle: get minimum supported scrub cycle duration in seconds.
  * @get_max_cycle: get maximum supported scrub cycle duration in seconds.
  * @get_cycle_duration: get current scrub cycle duration in seconds.
@@ -696,6 +698,8 @@ struct edac_scrub_ops {
 	int (*write_size)(struct device *dev, void *drv_data, u64 size);
 	int (*get_enabled_bg)(struct device *dev, void *drv_data, bool *enable);
 	int (*set_enabled_bg)(struct device *dev, void *drv_data, bool enable);
+	int (*get_enabled_od)(struct device *dev, void *drv_data, bool *enable);
+	int (*set_enabled_od)(struct device *dev, void *drv_data, bool enable);
 	int (*get_min_cycle)(struct device *dev, void *drv_data,  u32 *min);
 	int (*get_max_cycle)(struct device *dev, void *drv_data,  u32 *max);
 	int (*get_cycle_duration)(struct device *dev, void *drv_data, u32 *cycle);

3/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20727
							output: 22
							thoughts: 327
						

Thoughts:
**Fuzzing the ACPI RAS2 Memory Scrubbing Patch**

Okay, so we've got this patch that introduces ACPI RAS2 memory scrubbing. It seems straightforward enough at first glance – a new driver, `drivers/acpi/ras2.c` and `drivers/ras/acpi_ras2.c`, along with a modification to `drivers/edac/scrub.c`. The core of the new functionality relies on the Platform Communication Channel (PCC) for hardware interaction. My immediate question, given my focus, is whether this is worth fuzzing.

The driver interacts with ACPI tables (specifically the `ACPI_SIG_RAS2` signature) and PCC mailboxes. Now, here's where it gets interesting in the context of fuzzing, particularly in virtualized environments like QEMU or Google Compute Engine. We can probably inject ACPI tables using syzkaller, which might let us trigger the initialization. The initialization routine, `acpi_ras2_init`, is called from `acpi_init`, so that’s a promising entry point. It parses the RAS2 table and, if present, registers auxiliary devices. The auxiliary device probe, `ras2_mem_drv_probe`, is where the crucial PCC read operations occur.

But the catch is the PCC mailbox support. Syzkaller can inject ACPI tables using `syz_acpi_inject`, but does it actually have PCC mailbox support? I need to verify if PCC is even reachable. The critical piece here is the function `pcc_mbox_request_channel`. If there's no PCC mailbox available, this function will fail. I'm going to delve into the kernel code for `pcc_mbox_request_channel` and see what the conditions for success or failure are. This is a critical factor in determining if this whole path is viable for fuzzing and will determine how I approach testing this.



4/2 2026/07/10 18:47 tool grepper
0m Args:
{
  "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=281=static int register_pcc_channel(struct ras2_mem_ctx *ras2_ctx, int pcc_id)\n--\ndrivers/acpi/ras2.c-302-\ndrivers/acpi/ras2.c:303:\tpcc_chan = pcc_mbox_request_channel(\u0026sspcc-\u003embox_client, pcc_id);\ndrivers/acpi/ras2.c-304-\tif (IS_ERR(pcc_chan)) {\n--\ndrivers/devfreq/hisi_uncore_freq.c=106=static int hisi_uncore_request_pcc_chan(struct hisi_uncore_freq *uncore)\n--\ndrivers/devfreq/hisi_uncore_freq.c-116-\ndrivers/devfreq/hisi_uncore_freq.c:117:\tpcc_chan = pcc_mbox_request_channel(\u0026uncore-\u003ecl, uncore-\u003echan_id);\ndrivers/devfreq/hisi_uncore_freq.c-118-\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"
}

5/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23030
							output: 22
							thoughts: 704
						

Thoughts:
**Fuzzing the RAS2 Scrub Interfaces**

Okay, so I'm looking at how we can fuzz the RAS2 driver and its interaction with EDAC scrub interfaces. First, I want to confirm that the PCC mailbox driver is accessible via ACPI table injection, specifically PCCT.  If syzkaller injects a PCCT table, the PCC mailbox should initialize channels, and then the RAS2 driver should initialize itself if a RAS2 table is also injected, which means we can reach it in a virtualized environment.

Now, the interesting part is the `edac_dev_register` call in `ras2_mem_drv_probe`. That points directly to the EDAC scrub interfaces being exposed via sysfs. This is great, because syzkaller can directly interact with those sysfs files to trigger the scrub operations. I'm seeing the files are: `/sys/bus/edac/devices/<dev-name>/scrubX/enable_demand`, `/sys/bus/edac/devices/<dev-name>/scrubX/size`, `/sys/bus/edac/devices/<dev-name>/scrubX/addr`, `/sys/bus/edac/devices/<dev-name>/scrubX/current_cycle_duration`, and `/sys/bus/edac/devices/<dev-name>/scrubX/enable_background`.

The key here is understanding the operations. Looking at `ras2_scrub_ops`, I see the function pointers for `read_addr`, `read_size`, `write_addr`, `write_size`, `get_enabled_bg`, `set_enabled_bg`, `get_enabled_od`, `set_enabled_od`, `get_min_cycle`, `get_max_cycle`, `get_cycle_duration`, and `set_cycle_duration`. This is a clear target for fuzzing. We can provide arbitrary values to these files and observe the effects, seeing if we can trigger any memory errors or unexpected behavior.

Now, I need to check the required configurations. I'm thinking we need `CONFIG_ACPI_RAS2`, `CONFIG_MEM_ACPI_RAS2`, and `CONFIG_EDAC_SCRUB`. Let's confirm how these relate... Aha, `MEM_ACPI_RAS2` *depends* on `ACPI_RAS2`, `EDAC`, and `EDAC_SCRUB`. So, ensuring that `MEM_ACPI_RAS2` is enabled will automatically include the others, which is ideal. Let me go check `drivers/ras/Kconfig` to confirm these dependencies.



6/2 2026/07/10 18:47 tool grepper
0m Args:
{
  "Expression": "config MEM_ACPI_RAS2"
}

Results:
{
  "Output": "drivers/ras/Kconfig=37=config RAS_FMPM\n--\ndrivers/ras/Kconfig-48-\ndrivers/ras/Kconfig:49:config MEM_ACPI_RAS2\ndrivers/ras/Kconfig-50-\ttristate \"Memory ACPI RAS2 driver\"\n"
}

7/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24399
							output: 33
							thoughts: 22
						

Thoughts:
Here's my analysis of the provided text, assuming I'm an expert:

**Diving into the RAS Kconfig**

Alright, let's pull up the `drivers/ras/Kconfig` file and zero in on the `MEM_ACPI_RAS2` configuration option. My immediate thought is, what's the context here? RAS, or Reliability, Availability, and Serviceability, is critical, obviously. And `ACPI` almost certainly indicates we're dealing with hardware platform interfaces. It looks like we're interested in error handling or memory configuration specifically related to ACPI-defined features. I'll need to see how `MEM_ACPI_RAS2` relates to the broader RAS infrastructure; perhaps it's an enhancement, a specific implementation, or a fallback mechanism. I'll need to quickly establish the dependencies and associated features. This should be interesting to see how this particular driver configures reliability features based on ACPI.



8/2 2026/07/10 18:47 tool read-file
0m Args:
{
  "File": "drivers/ras/Kconfig",
  "FirstLine": 40,
  "LineCount": 20
}

Results:
{
  "Contents": "  40:\t\tdepends on AMD_ATL \u0026\u0026 ACPI_APEI\n  41:\t\thelp\n  42:\t\t  Support saving and restoring memory error information across reboot\n  43:\t\t  using ACPI ERST as persistent storage. Error information is saved with\n  44:\t\t  the UEFI CPER \"FRU Memory Poison\" section format.\n  45:\t\n  46:\t\t  Memory will be retired during boot time and run time depending on\n  47:\t\t  platform-specific policies.\n  48:\t\n  49:\tconfig MEM_ACPI_RAS2\n  50:\t\ttristate \"Memory ACPI RAS2 driver\"\n  51:\t\tdepends on ACPI_RAS2\n  52:\t\tdepends on EDAC\n  53:\t\tdepends on EDAC_SCRUB\n  54:\t\tselect NUMA_KEEP_MEMINFO\n  55:\t\thelp\n  56:\t\t  The driver binds to the auxiliary device added by the ACPI RAS2\n  57:\t\t  feature table parser. The driver uses a PCC channel subspace to\n  58:\t\t  communicate with the ACPI-compliant platform and provides\n  59:\t\t  control of the HW-based memory scrubber parameters to the user\n"
}

9/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24956
							output: 35
							thoughts: 52
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Connecting the Dots in the Reliability Framework**

Alright, let's see, `MEM_ACPI_RAS2`… that rings a bell. Ah, right, it's definitely tied to `ACPI_RAS2`, and of course, that implies a link to `EDAC` and `EDAC_SCRUB`. Seems like a pretty focused area – memory error correction within the ACPI framework. Before I dive too deep into anything else, I should check the source. That should be a good starting point. Let's get the Kconfig file for ACPI, that should tell me what that `ACPI_RAS2` symbol is all about. Hopefully I can get a better overview of dependencies this way.



10/2 2026/07/10 18:47 tool read-file
0m Args:
{
  "File": "drivers/acpi/Kconfig",
  "FirstLine": 290,
  "LineCount": 20
}

Results:
{
  "Contents": " 290:\t\t  If this option is enabled, this file implements common functionality\n 291:\t\t  to parse CPPC tables as described in the ACPI 5.1+ spec. The\n 292:\t\t  routines implemented are meant to be used by other\n 293:\t\t  drivers to control CPU performance using CPPC semantics.\n 294:\t\t  If your platform does not support CPPC in firmware,\n 295:\t\t  leave this option disabled.\n 296:\t\n 297:\tconfig ACPI_RAS2\n 298:\t\tbool \"ACPI RAS2 driver\"\n 299:\t\tselect AUXILIARY_BUS\n 300:\t\tdepends on MAILBOX\n 301:\t\tdepends on PCC\n 302:\t\thelp\n 303:\t\t  Add support for the RAS2 feature table and provide interfaces for\n 304:\t\t  platform RAS features, such as hardware-based memory scrubbing.\n 305:\t\n 306:\t\t  If unsure, select N.\n 307:\t\n 308:\tconfig ACPI_PROCESSOR\n 309:\t\ttristate \"Processor\"\n"
}

11/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25510
							output: 20
							thoughts: 51
						

Thoughts:
Okay, here's my take on those requirements, tailored for someone who understands this space:

**Digging into the Reliability, Availability, and Serviceability Requirements**

Alright, let's break down this reliability and RAS stuff. I see a few key elements jumping out: `ACPI_RAS2`, `MEM_ACPI_RAS2`, `EDAC_SCRUB`, `PCC`, and `MAILBOX`. These all point to different levels of error detection, correction, and handling within the system. `ACPI_RAS2` and `MEM_ACPI_RAS2` likely address firmware-based and memory-specific RAS capabilities, which are fundamental. `PCC` probably refers to Platform Control Component, suggesting we're dealing with advanced platform management features. And the `MAILBOX` indicates a mechanism for inter-processor or inter-module communication specifically for RAS events.

My first thought is always to understand the scope and capabilities of the existing infrastructure. Therefore, I need to check something quickly: Is `EDAC_SCRUB` actually a configurable option? Understanding the build/config options for a scrub allows us to determine what can be customized and if we can modify the way the EDAC scrub works. Is it just a driver, or a comprehensive mechanism that needs careful consideration in its context? Getting this clarity is key to deciding how we can integrate all these components effectively. It's a foundational question.



12/2 2026/07/10 18:47 tool grepper
0m Args:
{
  "Expression": "config EDAC_SCRUB"
}

Results:
{
  "Output": "drivers/edac/Kconfig=47=config EDAC_GHES\n--\ndrivers/edac/Kconfig-69-\ndrivers/edac/Kconfig:70:config EDAC_SCRUB\ndrivers/edac/Kconfig-71-\tbool \"EDAC scrub feature\"\n"
}

13/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25959
							output: 16
							thoughts: 22
						

Thoughts:
Here's my take on those thoughts:

**Analyzing System Configurations**

Okay, so I'm looking at this, and yep, *EDAC_SCRUB*... that's definitely a configuration setting. Got it. Now, what's next? Let me shift my focus. I need to take a look at *PCC*. That's where I need to go from here, let's explore that setting.



14/2 2026/07/10 18:47 tool grepper
0m Args:
{
  "Expression": "config PCC"
}

Results:
{
  "Output": "drivers/mailbox/Kconfig=114=config ROCKCHIP_MBOX\n--\ndrivers/mailbox/Kconfig-122-\ndrivers/mailbox/Kconfig:123:config PCC\ndrivers/mailbox/Kconfig-124-\tbool \"Platform Communication Channel Driver\"\n--\ndrivers/pcmcia/Kconfig-5-\ndrivers/pcmcia/Kconfig:6:menuconfig PCCARD\ndrivers/pcmcia/Kconfig-7-\ttristate \"PCCard (PCMCIA/CardBus) support\"\n--\ndrivers/pcmcia/Kconfig=213=config ELECTRA_CF\n--\ndrivers/pcmcia/Kconfig-219-\ndrivers/pcmcia/Kconfig:220:config PCCARD_NONSTATIC\ndrivers/pcmcia/Kconfig-221-\tbool\n"
}

15/2 2026/07/10 18:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26293
							output: 21
							thoughts: 964
						

Thoughts:
**Refining the Target for Fuzzing: PCC and RAS2**

Okay, so I'm diving into this `PCC` thing. First things first: confirm that `PCC` is indeed a configuration option, and I see it is in `drivers/mailbox/Kconfig`. Right, so to enable the `PCC` functionality, I'll need to enable these configs: `ACPI_RAS2`, `MEM_ACPI_RAS2`, `EDAC_SCRUB` and `PCC`, and probably `MAILBOX` just to be on the safe side, it looks like it's a core subsystem.

Now, let's zero in on the key functions. Looking at `drivers/acpi/ras2.c`, there are these new functions: `ras2_send_pcc_cmd`, `register_pcc_channel`, `add_aux_device`, `parse_ras2_table`, and `acpi_ras2_init`. Then, in `drivers/ras/acpi_ras2.c`, I see a larger set of interesting routines: `ras2_is_patrol_scrub_support`, `ras2_update_patrol_scrub_params_cache`, `ras2_get_demand_scrub_running`, `ras2_scrub_monitor_thread`, `ras2_hw_scrub_read_min_scrub_cycle`, `ras2_hw_scrub_read_max_scrub_cycle`, `ras2_hw_scrub_cycle_read`, `ras2_hw_scrub_cycle_write`, `ras2_hw_scrub_read_addr`, `ras2_hw_scrub_read_size`, `ras2_hw_scrub_write_addr`, `ras2_hw_scrub_write_size`, `ras2_hw_scrub_get_enabled_bg`, `__ras2_hw_scrub_set_enabled_bg`, `ras2_hw_scrub_set_enabled_bg`, `ras2_hw_scrub_get_enabled_od`, `ras2_hw_scrub_set_enabled_od`, and `ras2_mem_drv_probe`.

Alright, time to refine the attack surface for fuzzing. Initially, I think the critical ones are: `ras2_send_pcc_cmd`, `ras2_update_patrol_scrub_params_cache`, `ras2_get_demand_scrub_running`, `ras2_scrub_monitor_thread`, `ras2_hw_scrub_cycle_write`, `ras2_hw_scrub_write_addr`, `ras2_hw_scrub_write_size`, `__ras2_hw_scrub_set_enabled_bg`, `ras2_hw_scrub_set_enabled_od`, `ras2_mem_drv_probe`, and `parse_ras2_table`.

Hang on... I see `ras2_send_pcc_cmd` is exported. That's interesting. I should check `drivers/edac/scrub.c` to see if there are any new sysfs attributes or anything else to consider. Seems like there's a simple addition of `enable_demand` to the sysfs attributes and a modification of `scrub_attr_visible`, nothing major.

So, let me re-evaluate the target list, after this quick scan of the `edac` side: `ras2_send_pcc_cmd`, `parse_ras2_table`, `ras2_mem_drv_probe`, `ras2_update_patrol_scrub_params_cache`, `ras2_get_demand_scrub_running`, `ras2_scrub_monitor_thread`, `ras2_hw_scrub_cycle_write`, `ras2_hw_scrub_write_addr`, `ras2_hw_scrub_write_size`, `__ras2_hw_scrub_set_enabled_bg`, and `ras2_hw_scrub_set_enabled_od`. These are the functions to focus on.



16/2 2026/07/10 18:48 tool grepper
0m Args:
{
  "Expression": "acpi_ras2_init"
}

Results:
{
  "Output": "drivers/acpi/bus.c=1576=static int __init acpi_init(void)\n--\ndrivers/acpi/bus.c-1613-\tacpi_viot_init();\ndrivers/acpi/bus.c:1614:\tacpi_ras2_init();\ndrivers/acpi/bus.c-1615-\n--\ndrivers/acpi/ras2.c=419=static int parse_ras2_table(struct acpi_table_ras2 *ras2_tab)\n--\ndrivers/acpi/ras2.c-477-/**\ndrivers/acpi/ras2.c:478: * acpi_ras2_init - RAS2 driver initialization function.\ndrivers/acpi/ras2.c-479- *\n--\ndrivers/acpi/ras2.c-486- */\ndrivers/acpi/ras2.c:487:void __init acpi_ras2_init(void)\ndrivers/acpi/ras2.c-488-{\n--\ninclude/acpi/ras2.h=57=struct ras2_mem_ctx {\n--\ninclude/acpi/ras2.h-79-#ifdef CONFIG_ACPI_RAS2\ninclude/acpi/ras2.h:80:void __init acpi_ras2_init(void);\ninclude/acpi/ras2.h-81-int ras2_send_pcc_cmd(struct ras2_mem_ctx *ras2_ctx, u16 cmd);\ninclude/acpi/ras2.h-82-#else\ninclude/acpi/ras2.h:83:static inline void acpi_ras2_init(void) { }\ninclude/acpi/ras2.h-84-#endif\n"
}

17/2 2026/07/10 18:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28637
							output: 303
							thoughts: 294
						

Thoughts:
**My Assessment of the New ACPI RAS2 Driver**

Okay, so I'm looking at this new ACPI RAS2 driver, and immediately my expert brain starts churning. Essentially, it's introducing a way to handle memory scrubbing through ACPI and expose it through sysfs. I can see the potential implications right away. This driver parses the RAS2 ACPI table, which is a key structure. It then registers these auxiliary devices, and a binding driver is included to interact with them, exposing EDAC scrub interfaces for control through the PCC.

The crucial point is that this setup is *reachable* by syzkaller. Syzkaller can inject custom ACPI tables, including RAS2 and PCCT, and it can interact with the sysfs interfaces. This is prime fuzzing territory. I need to focus on these symbols, they're the attack surface: `parse_ras2_table`, `ras2_send_pcc_cmd`, `ras2_mem_drv_probe`, `ras2_update_patrol_scrub_params_cache`, `ras2_get_demand_scrub_running`, `ras2_scrub_monitor_thread`, `ras2_hw_scrub_cycle_write`, `ras2_hw_scrub_write_addr`, `ras2_hw_scrub_write_size`, `__ras2_hw_scrub_set_enabled_bg`, and `ras2_hw_scrub_set_enabled_od`. Each one of these, particularly the *write* operations, needs meticulous scrutiny.

The configurations to look out for are `ACPI_RAS2`, `MEM_ACPI_RAS2`, `EDAC_SCRUB`, and `PCC`. These are the gates and indicators to make sure we're looking at the right environment.

Alright, time to set some expectations and results. This needs to be thoroughly fuzzed.



18/2 2026/07/10 18:48 tool set-results
0m Args:
{
  "EnableConfigs": [
    "ACPI_RAS2",
    "MEM_ACPI_RAS2",
    "EDAC_SCRUB",
    "PCC"
  ],
  "FocusSymbols": [
    "parse_ras2_table",
    "ras2_send_pcc_cmd",
    "ras2_mem_drv_probe",
    "ras2_update_patrol_scrub_params_cache",
    "ras2_get_demand_scrub_running",
    "ras2_scrub_monitor_thread",
    "ras2_hw_scrub_cycle_write",
    "ras2_hw_scrub_write_addr",
    "ras2_hw_scrub_write_size",
    "__ras2_hw_scrub_set_enabled_bg",
    "ras2_hw_scrub_set_enabled_od"
  ],
  "Reasoning": "The patch introduces a new ACPI RAS2 driver that parses the RAS2 ACPI table and registers auxiliary devices for memory scrubbing. It also adds a driver that binds to these auxiliary devices and exposes EDAC scrub sysfs interfaces to control hardware-based memory scrubbing via PCC (Platform Communication Channel). Since syzkaller can inject ACPI tables (including RAS2 and PCCT) and interact with sysfs, this code is reachable and functional in virtualized environments.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "ACPI_RAS2",
    "MEM_ACPI_RAS2",
    "EDAC_SCRUB",
    "PCC"
  ],
  "FocusSymbols": [
    "parse_ras2_table",
    "ras2_send_pcc_cmd",
    "ras2_mem_drv_probe",
    "ras2_update_patrol_scrub_params_cache",
    "ras2_get_demand_scrub_running",
    "ras2_scrub_monitor_thread",
    "ras2_hw_scrub_cycle_write",
    "ras2_hw_scrub_write_addr",
    "ras2_hw_scrub_write_size",
    "__ras2_hw_scrub_set_enabled_bg",
    "ras2_hw_scrub_set_enabled_od"
  ],
  "Reasoning": "The patch introduces a new ACPI RAS2 driver that parses the RAS2 ACPI table and registers auxiliary devices for memory scrubbing. It also adds a driver that binds to these auxiliary devices and exposes EDAC scrub sysfs interfaces to control hardware-based memory scrubbing via PCC (Platform Communication Channel). Since syzkaller can inject ACPI tables (including RAS2 and PCCT) and interact with sysfs, this code is reachable and functional in virtualized environments.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)