AI Trajectory


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

Results:
{
  "EnableConfigs": [
    "DEV_DAX",
    "DEV_DAX_KMEM",
    "MEMORY_HOTPLUG",
    "MEMORY_HOTREMOVE"
  ],
  "FocusSymbols": [
    "dax_kmem_do_hotplug",
    "dax_kmem_do_hotremove",
    "dax_kmem_init_resources",
    "dax_kmem_cleanup_resources",
    "dax_kmem_parse_state",
    "offline_and_remove_memory_ranges",
    "__add_memory_driver_managed",
    "mhp_online_type_to_str"
  ],
  "Reasoning": "The patch introduces a new sysfs attribute to control the memory hotplug state of DAX kmem devices. It adds logic to handle hotplugging and hotremoving memory ranges, including new core memory hotplug functions like `offline_and_remove_memory_ranges` and `__add_memory_driver_managed`. These are reachable via sysfs and device binding/unbinding, making them suitable for fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/07/01 04:28 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 637122e5a22d30d7a50ebbf3d4ad2ea4e2b4e657\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 1 04:28:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/ABI/testing/sysfs-bus-dax b/Documentation/ABI/testing/sysfs-bus-dax\nindex b34266bfae49ae..2dcad1e9dad0d8 100644\n--- a/Documentation/ABI/testing/sysfs-bus-dax\n+++ b/Documentation/ABI/testing/sysfs-bus-dax\n@@ -151,3 +151,29 @@ Description:\n \t\tmemmap_on_memory parameter for memory_hotplug. This is\n \t\ttypically set on the kernel command line -\n \t\tmemory_hotplug.memmap_on_memory set to 'true' or 'force'.\"\n+\n+What:\t\t/sys/bus/dax/devices/daxX.Y/state\n+Date:\t\tJune, 2026\n+KernelVersion:\tv6.21\n+Contact:\tnvdimm@lists.linux.dev\n+Description:\n+\t\t(RW) Controls the state of the memory region.\n+\t\tApplies to all memory blocks associated with the device.\n+\t\tOnly applies to dax_kmem devices.\n+\n+\t\tReading returns the current state; the writable states mirror\n+\t\tthe per-block /sys/devices/system/memory/memoryX/state ABI::\n+\n+\t\t  \"unplugged\": memory blocks are not present\n+\t\t  \"online\": memory is online, zone chosen by the kernel\n+\t\t  \"online_kernel\": memory is online in ZONE_NORMAL\n+\t\t  \"online_movable\": memory is online in ZONE_MOVABLE\n+\n+\t\t\"offline\" (memory blocks are present but offline) may also be\n+\t\treported - this happens when the device is bound while the\n+\t\tauto_online_blocks policy is \"offline\".  It cannot be written,\n+\t\tas it's not useful and creates device destruction races.\n+\n+\t\tA device can only be onlined from the \"unplugged\" state, so a\n+\t\tdevice must be returned to \"unplugged\" before it can be onlined\n+\t\tinto a different state.\ndiff --git a/drivers/base/memory.c b/drivers/base/memory.c\nindex bcfe2d9f4adbd9..5eead3346f1e32 100644\n--- a/drivers/base/memory.c\n+++ b/drivers/base/memory.c\n@@ -46,6 +46,15 @@ int mhp_online_type_from_str(const char *str)\n \t}\n \treturn -EINVAL;\n }\n+EXPORT_SYMBOL_GPL(mhp_online_type_from_str);\n+\n+const char *mhp_online_type_to_str(int online_type)\n+{\n+\tif (online_type \u003c 0 || online_type \u003e= (int)ARRAY_SIZE(online_type_to_str))\n+\t\treturn NULL;\n+\treturn online_type_to_str[online_type];\n+}\n+EXPORT_SYMBOL_GPL(mhp_online_type_to_str);\n \n #define to_memory_block(dev) container_of(dev, struct memory_block, dev)\n \ndiff --git a/drivers/dax/bus.c b/drivers/dax/bus.c\nindex d8aea5fe0af2c5..8d83b5577a8ea0 100644\n--- a/drivers/dax/bus.c\n+++ b/drivers/dax/bus.c\n@@ -1,6 +1,7 @@\n // SPDX-License-Identifier: GPL-2.0\n /* Copyright(c) 2017-2018 Intel Corporation. All rights reserved. */\n #include \u003clinux/memremap.h\u003e\n+#include \u003clinux/memory_hotplug.h\u003e\n #include \u003clinux/device.h\u003e\n #include \u003clinux/mutex.h\u003e\n #include \u003clinux/list.h\u003e\n@@ -394,6 +395,7 @@ static ssize_t create_store(struct device *dev, struct device_attribute *attr,\n \t\t\t.size = 0,\n \t\t\t.id = -1,\n \t\t\t.memmap_on_memory = false,\n+\t\t\t.online_type = DAX_ONLINE_DEFAULT,\n \t\t};\n \t\tstruct dev_dax *dev_dax = __devm_create_dev_dax(\u0026data);\n \n@@ -1527,6 +1529,7 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data)\n \tida_init(\u0026dev_dax-\u003eida);\n \n \tdev_dax-\u003ememmap_on_memory = data-\u003ememmap_on_memory;\n+\tdev_dax-\u003eonline_type = data-\u003eonline_type;\n \n \tinode = dax_inode(dax_dev);\n \tdev-\u003edevt = inode-\u003ei_rdev;\ndiff --git a/drivers/dax/bus.h b/drivers/dax/bus.h\nindex 5909171a4428b7..3bc76bc0a14582 100644\n--- a/drivers/dax/bus.h\n+++ b/drivers/dax/bus.h\n@@ -3,6 +3,7 @@\n #ifndef __DAX_BUS_H__\n #define __DAX_BUS_H__\n #include \u003clinux/device.h\u003e\n+#include \u003clinux/memory_hotplug.h\u003e\n #include \u003clinux/platform_device.h\u003e\n #include \u003clinux/range.h\u003e\n #include \u003clinux/workqueue.h\u003e\n@@ -16,6 +17,13 @@ struct dax_region;\n #define IORESOURCE_DAX_STATIC BIT(0)\n #define IORESOURCE_DAX_KMEM BIT(1)\n \n+/*\n+ * online_type sentinel: the device was created without an explicit online\n+ * policy, so the system default is resolved when the kmem driver binds,\n+ * (not at device-creation time, which would freeze a stale policy).\n+ */\n+#define DAX_ONLINE_DEFAULT\t(-1)\n+\n struct dax_region *alloc_dax_region(struct device *parent, int region_id,\n \t\tstruct range *range, int target_node, unsigned int align,\n \t\tunsigned long flags);\n@@ -26,6 +34,7 @@ struct dev_dax_data {\n \tresource_size_t size;\n \tint id;\n \tbool memmap_on_memory;\n+\tint online_type;\t/* enum mmop, or DAX_ONLINE_DEFAULT sentinel */\n };\n \n struct dev_dax *devm_create_dev_dax(struct dev_dax_data *data);\ndiff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c\nindex 3ab39b77843d5e..1a7ec621221347 100644\n--- a/drivers/dax/cxl.c\n+++ b/drivers/dax/cxl.c\n@@ -27,6 +27,7 @@ static int cxl_dax_region_probe(struct device *dev)\n \t\t.id = -1,\n \t\t.size = range_len(\u0026cxlr_dax-\u003ehpa_range),\n \t\t.memmap_on_memory = true,\n+\t\t.online_type = DAX_ONLINE_DEFAULT,\n \t};\n \n \treturn PTR_ERR_OR_ZERO(devm_create_dev_dax(\u0026data));\ndiff --git a/drivers/dax/dax-private.h b/drivers/dax/dax-private.h\nindex 81e4af49e39c10..902e922dc4e4f1 100644\n--- a/drivers/dax/dax-private.h\n+++ b/drivers/dax/dax-private.h\n@@ -8,6 +8,7 @@\n #include \u003clinux/device.h\u003e\n #include \u003clinux/cdev.h\u003e\n #include \u003clinux/idr.h\u003e\n+#include \u003clinux/memory_hotplug.h\u003e\n \n /* private routines between core files */\n struct dax_device;\n@@ -79,6 +80,8 @@ struct dev_dax_range {\n  * @dev: device core\n  * @pgmap: pgmap for memmap setup / lifetime (driver owned)\n  * @memmap_on_memory: allow kmem to put the memmap in the memory\n+ * @online_type: MMOP_* online type for memory hotplug, or DAX_ONLINE_DEFAULT\n+ *\t\t to resolve the system default policy when kmem binds\n  * @nr_range: size of @ranges\n  * @ranges: range tuples of memory used\n  */\n@@ -95,6 +98,7 @@ struct dev_dax {\n \tstruct device dev;\n \tstruct dev_pagemap *pgmap;\n \tbool memmap_on_memory;\n+\tint online_type;\t/* enum mmop, or DAX_ONLINE_DEFAULT sentinel */\n \tint nr_range;\n \tstruct dev_dax_range *ranges;\n };\ndiff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c\nindex af21f66bf872c6..2de3bc9251729b 100644\n--- a/drivers/dax/hmem/hmem.c\n+++ b/drivers/dax/hmem/hmem.c\n@@ -37,6 +37,7 @@ static int dax_hmem_probe(struct platform_device *pdev)\n \t\t.id = -1,\n \t\t.size = region_idle ? 0 : range_len(\u0026mri-\u003erange),\n \t\t.memmap_on_memory = false,\n+\t\t.online_type = DAX_ONLINE_DEFAULT,\n \t};\n \n \treturn PTR_ERR_OR_ZERO(devm_create_dev_dax(\u0026data));\ndiff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c\nindex a18e2b968e4dae..19effe0da3dcef 100644\n--- a/drivers/dax/kmem.c\n+++ b/drivers/dax/kmem.c\n@@ -33,9 +33,7 @@ static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)\n \tstruct dev_dax_range *dax_range = \u0026dev_dax-\u003eranges[i];\n \tstruct range *range = \u0026dax_range-\u003erange;\n \n-\t/* memory-block align the hotplug range */\n-\tr-\u003estart = ALIGN(range-\u003estart, memory_block_size_bytes());\n-\tr-\u003eend = ALIGN_DOWN(range-\u003eend + 1, memory_block_size_bytes()) - 1;\n+\t*r = memory_block_aligned_range(range);\n \tif (r-\u003estart \u003e= r-\u003eend) {\n \t\tr-\u003estart = range-\u003estart;\n \t\tr-\u003eend = range-\u003eend;\n@@ -44,9 +42,15 @@ static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)\n \treturn 0;\n }\n \n+#define DAX_KMEM_UNPLUGGED\t(-1)\n+\n struct dax_kmem_data {\n \tconst char *res_name;\n \tint mgid;\n+\tint numa_node;\n+\tstruct dev_dax *dev_dax;\n+\tint state;\n+\tstruct mutex lock; /* protects hotplug state transitions */\n \tstruct resource *res[];\n };\n \n@@ -65,15 +69,334 @@ static void kmem_put_memory_types(void)\n \tmt_put_memory_types(\u0026kmem_memory_types);\n }\n \n+/* True for the online states a kmem dax device can hold. */\n+static bool dax_kmem_state_is_online(int state)\n+{\n+\treturn state == MMOP_ONLINE ||\n+\t       state == MMOP_ONLINE_KERNEL ||\n+\t       state == MMOP_ONLINE_MOVABLE;\n+}\n+\n+/**\n+ * dax_kmem_do_hotplug - hotplug memory for dax kmem device\n+ * @dev_dax: the dev_dax instance\n+ * @data: the dax_kmem_data structure with resource tracking\n+ * @online_type: the online policy to use for the memory blocks\n+ *\n+ * Hotplugs all ranges in the dev_dax region as system memory with the\n+ * provided online policy (offline, online, online_movable, online_kernel).\n+ *\n+ * Returns the number of successfully mapped ranges, or negative error.\n+ */\n+static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,\n+\t\t\t       struct dax_kmem_data *data,\n+\t\t\t       int online_type)\n+{\n+\tstruct device *dev = \u0026dev_dax-\u003edev;\n+\tint i, rc, added = 0;\n+\tmhp_t mhp_flags;\n+\n+\tif (dax_kmem_state_is_online(data-\u003estate))\n+\t\treturn -EINVAL;\n+\n+\tif (online_type \u003c MMOP_OFFLINE || online_type \u003e MMOP_ONLINE_MOVABLE)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n+\t\tstruct range range;\n+\n+\t\trc = dax_kmem_range(dev_dax, i, \u0026range);\n+\t\tif (rc)\n+\t\t\tcontinue;\n+\n+\t\t/*\n+\t\t * init_resources() is best-effort: if a reservation conflict\n+\t\t * occurs it keeps the range but leaves res[i]=NULL. For hotplug\n+\t\t * on probe systems, this means kmem will partially online.\n+\t\t *\n+\t\t * We have to keep this behavior not to break those systems.\n+\t\t * For those systems - atomicity only applies to valid ranges.\n+\t\t */\n+\t\tif (!data-\u003eres[i])\n+\t\t\tcontinue;\n+\n+\t\tmhp_flags = MHP_NID_IS_MGID;\n+\t\tif (dev_dax-\u003ememmap_on_memory)\n+\t\t\tmhp_flags |= MHP_MEMMAP_ON_MEMORY;\n+\n+\t\t/*\n+\t\t * Ensure that future kexec'd kernels will not treat\n+\t\t * this as RAM automatically.\n+\t\t */\n+\t\trc = __add_memory_driver_managed(data-\u003emgid, range.start,\n+\t\t\t\trange_len(\u0026range), kmem_name, mhp_flags,\n+\t\t\t\tonline_type);\n+\n+\t\tif (rc) {\n+\t\t\tdev_warn(dev, \"mapping%d: %#llx-%#llx memory add failed\\n\",\n+\t\t\t\t i, range.start, range.end);\n+\t\t\t/*\n+\t\t\t * Release the reservation for the range that failed to\n+\t\t\t * add so a later hotremove does not try to remove memory\n+\t\t\t * that was never added.\n+\t\t\t */\n+\t\t\tif (data-\u003eres[i]) {\n+\t\t\t\tremove_resource(data-\u003eres[i]);\n+\t\t\t\tkfree(data-\u003eres[i]);\n+\t\t\t\tdata-\u003eres[i] = NULL;\n+\t\t\t}\n+\t\t\tif (added)\n+\t\t\t\tcontinue;\n+\t\t\treturn rc;\n+\t\t}\n+\t\tadded++;\n+\t}\n+\n+\treturn added;\n+}\n+\n+/**\n+ * dax_kmem_init_resources - create memory regions for dax kmem\n+ * @dev_dax: the dev_dax instance\n+ * @data: the dax_kmem_data structure with resource tracking\n+ *\n+ * Initializes all the resources for the DAX\n+ *\n+ * Returns the number of successfully mapped ranges, or negative error.\n+ */\n+static int dax_kmem_init_resources(struct dev_dax *dev_dax,\n+\t\t\t\t   struct dax_kmem_data *data)\n+{\n+\tstruct device *dev = \u0026dev_dax-\u003edev;\n+\tint i, rc, mapped = 0;\n+\n+\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n+\t\tstruct resource *res;\n+\t\tstruct range range;\n+\n+\t\trc = dax_kmem_range(dev_dax, i, \u0026range);\n+\t\tif (rc)\n+\t\t\tcontinue;\n+\n+\t\t/* Skip ranges already added */\n+\t\tif (data-\u003eres[i])\n+\t\t\tcontinue;\n+\n+\t\t/* Region is permanently reserved if hotremove fails. */\n+\t\tres = request_mem_region(range.start, range_len(\u0026range),\n+\t\t\t\t\t data-\u003eres_name);\n+\t\tif (!res) {\n+\t\t\tdev_warn(dev, \"mapping%d: %#llx-%#llx could not reserve region\\n\",\n+\t\t\t\t i, range.start, range.end);\n+\t\t\t/*\n+\t\t\t * Once some memory has been onlined we can't\n+\t\t\t * assume that it can be un-onlined safely.\n+\t\t\t */\n+\t\t\tif (mapped)\n+\t\t\t\tcontinue;\n+\t\t\treturn -EBUSY;\n+\t\t}\n+\t\tdata-\u003eres[i] = res;\n+\t\t/*\n+\t\t * Set flags appropriate for System RAM.  Leave ..._BUSY clear\n+\t\t * so that add_memory() can add a child resource.  Do not\n+\t\t * inherit flags from the parent since it may set new flags\n+\t\t * unknown to us that will break add_memory() below.\n+\t\t */\n+\t\tres-\u003eflags = IORESOURCE_SYSTEM_RAM;\n+\t\tmapped++;\n+\t}\n+\treturn mapped;\n+}\n+\n+#ifdef CONFIG_MEMORY_HOTREMOVE\n+/**\n+ * dax_kmem_do_hotremove - hot-remove memory for dax kmem device\n+ * @dev_dax: the dev_dax instance\n+ * @data: the dax_kmem_data structure with resource tracking\n+ *\n+ * Offlines and removes every currently-added range in the dev_dax region\n+ * atomically: either all ranges are offlined and removed, or none are and\n+ * the device is returned to its prior state.\n+ *\n+ * Returns 0 on success, or a negative errno on failure.\n+ */\n+static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,\n+\t\t\t\t struct dax_kmem_data *data)\n+{\n+\tstruct device *dev = \u0026dev_dax-\u003edev;\n+\tstruct range *ranges;\n+\tint i, nr_ranges = 0, rc;\n+\n+\tranges = kmalloc_array(dev_dax-\u003enr_range, sizeof(*ranges), GFP_KERNEL);\n+\tif (!ranges)\n+\t\treturn -ENOMEM;\n+\n+\t/* Collect the ranges that were actually added during probe. */\n+\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n+\t\tstruct range range;\n+\n+\t\tif (!data-\u003eres[i])\n+\t\t\tcontinue;\n+\t\tif (dax_kmem_range(dev_dax, i, \u0026range))\n+\t\t\tcontinue;\n+\t\tranges[nr_ranges++] = range;\n+\t}\n+\n+\t/* Nothing added means nothing to remove. */\n+\tif (!nr_ranges) {\n+\t\tkfree(ranges);\n+\t\treturn 0;\n+\t}\n+\n+\trc = offline_and_remove_memory_ranges(ranges, nr_ranges);\n+\tkfree(ranges);\n+\tif (rc) {\n+\t\t/* Recoverable: the ranges rolled back, nothing is leaked yet. */\n+\t\tdev_err(dev, \"hotremove failed, device left online: %d\\n\", rc);\n+\t\treturn rc;\n+\t}\n+\n+\t/* All ranges removed; release the reserved resources. */\n+\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n+\t\tif (!data-\u003eres[i])\n+\t\t\tcontinue;\n+\t\tremove_resource(data-\u003eres[i]);\n+\t\tkfree(data-\u003eres[i]);\n+\t\tdata-\u003eres[i] = NULL;\n+\t}\n+\n+\treturn 0;\n+}\n+#else\n+static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,\n+\t\t\t\t struct dax_kmem_data *data)\n+{\n+\treturn -EBUSY;\n+}\n+#endif /* CONFIG_MEMORY_HOTREMOVE */\n+\n+/**\n+ * dax_kmem_cleanup_resources - remove the dax memory resources\n+ * @dev_dax: the dev_dax instance\n+ * @data: the dax_kmem_data structure with resource tracking\n+ *\n+ * Removes all resources in the dev_dax region.\n+ */\n+static void dax_kmem_cleanup_resources(struct dev_dax *dev_dax,\n+\t\t\t\t       struct dax_kmem_data *data)\n+{\n+\tint i;\n+\n+\t/*\n+\t * If the device unbind occurs before memory is hotremoved, we can never\n+\t * remove the memory (requires reboot).  Attempting an offline operation\n+\t * here may cause deadlock and a failure to finish the unbind.\n+\t *\n+\t * Note: This leaks the resources.\n+\t */\n+\tif (WARN(((data-\u003estate != DAX_KMEM_UNPLUGGED) \u0026\u0026\n+\t\t  (data-\u003estate != MMOP_OFFLINE)),\n+\t\t \"Hotplug memory regions stuck online until reboot\"))\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n+\t\tif (!data-\u003eres[i])\n+\t\t\tcontinue;\n+\t\tremove_resource(data-\u003eres[i]);\n+\t\tkfree(data-\u003eres[i]);\n+\t\tdata-\u003eres[i] = NULL;\n+\t}\n+}\n+\n+static int dax_kmem_parse_state(const char *buf)\n+{\n+\tint online_type;\n+\n+\t/* \"unplugged\" is kmem-specific - the rest map to MMOP_ */\n+\tif (sysfs_streq(buf, \"unplugged\"))\n+\t\treturn DAX_KMEM_UNPLUGGED;\n+\n+\tonline_type = mhp_online_type_from_str(buf);\n+\t/* Disallow \"offline\": it's not useful and creates race conditions */\n+\tif (online_type == MMOP_OFFLINE)\n+\t\treturn -EINVAL;\n+\treturn online_type;\n+}\n+\n+static ssize_t state_show(struct device *dev,\n+\t\t\t    struct device_attribute *attr, char *buf)\n+{\n+\tstruct dax_kmem_data *data = dev_get_drvdata(dev);\n+\tconst char *state_str;\n+\n+\tif (!data)\n+\t\treturn -ENXIO;\n+\n+\tif (data-\u003estate == DAX_KMEM_UNPLUGGED)\n+\t\tstate_str = \"unplugged\";\n+\telse\n+\t\tstate_str = mhp_online_type_to_str(data-\u003estate);\n+\n+\treturn sysfs_emit(buf, \"%s\\n\", state_str ?: \"unknown\");\n+}\n+\n+static ssize_t state_store(struct device *dev, struct device_attribute *attr,\n+\t\t\t     const char *buf, size_t len)\n+{\n+\tstruct dev_dax *dev_dax = to_dev_dax(dev);\n+\tstruct dax_kmem_data *data = dev_get_drvdata(dev);\n+\tint online_type;\n+\tint rc;\n+\n+\tif (!data)\n+\t\treturn -ENXIO;\n+\n+\tonline_type = dax_kmem_parse_state(buf);\n+\tif (online_type \u003c DAX_KMEM_UNPLUGGED)\n+\t\treturn online_type;\n+\n+\tguard(mutex)(\u0026data-\u003elock);\n+\n+\t/* Already in requested state */\n+\tif (data-\u003estate == online_type)\n+\t\treturn len;\n+\n+\tif (online_type == DAX_KMEM_UNPLUGGED) {\n+\t\trc = dax_kmem_do_hotremove(dev_dax, data);\n+\t\tif (rc)\n+\t\t\treturn rc;\n+\t\tdata-\u003estate = DAX_KMEM_UNPLUGGED;\n+\t\treturn len;\n+\t}\n+\n+\t/* Onlining is only allowed from the unplugged state. */\n+\tif (data-\u003estate != DAX_KMEM_UNPLUGGED)\n+\t\treturn -EBUSY;\n+\n+\t/* Re-acquire resources if previously unplugged, otherwise no-op */\n+\trc = dax_kmem_init_resources(dev_dax, data);\n+\tif (rc \u003c 0)\n+\t\treturn rc;\n+\n+\trc = dax_kmem_do_hotplug(dev_dax, data, online_type);\n+\tif (rc \u003c 0)\n+\t\treturn rc;\n+\n+\tdata-\u003estate = online_type;\n+\treturn len;\n+}\n+static DEVICE_ATTR_RW(state);\n+\n static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\n {\n \tstruct device *dev = \u0026dev_dax-\u003edev;\n \tunsigned long total_len = 0, orig_len = 0;\n \tstruct dax_kmem_data *data;\n \tstruct memory_dev_type *mtype;\n-\tint i, rc, mapped = 0;\n-\tmhp_t mhp_flags;\n+\tint i, rc;\n \tint numa_node;\n+\tint online_type;\n \tint adist = MEMTIER_DEFAULT_DAX_ADISTANCE;\n \n \t/*\n@@ -133,68 +456,38 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\n \tif (rc \u003c 0)\n \t\tgoto err_reg_mgid;\n \tdata-\u003emgid = rc;\n+\tdata-\u003enuma_node = numa_node;\n+\tdata-\u003edev_dax = dev_dax;\n+\tdata-\u003estate = DAX_KMEM_UNPLUGGED;\n+\tmutex_init(\u0026data-\u003elock);\n \n-\tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n-\t\tstruct resource *res;\n-\t\tstruct range range;\n-\n-\t\trc = dax_kmem_range(dev_dax, i, \u0026range);\n-\t\tif (rc)\n-\t\t\tcontinue;\n-\n-\t\t/* Region is permanently reserved if hotremove fails. */\n-\t\tres = request_mem_region(range.start, range_len(\u0026range), data-\u003eres_name);\n-\t\tif (!res) {\n-\t\t\tdev_warn(dev, \"mapping%d: %#llx-%#llx could not reserve region\\n\",\n-\t\t\t\t\ti, range.start, range.end);\n-\t\t\t/*\n-\t\t\t * Once some memory has been onlined we can't\n-\t\t\t * assume that it can be un-onlined safely.\n-\t\t\t */\n-\t\t\tif (mapped)\n-\t\t\t\tcontinue;\n-\t\t\trc = -EBUSY;\n-\t\t\tgoto err_request_mem;\n-\t\t}\n-\t\tdata-\u003eres[i] = res;\n-\n-\t\t/*\n-\t\t * Set flags appropriate for System RAM.  Leave ..._BUSY clear\n-\t\t * so that add_memory() can add a child resource.  Do not\n-\t\t * inherit flags from the parent since it may set new flags\n-\t\t * unknown to us that will break add_memory() below.\n-\t\t */\n-\t\tres-\u003eflags = IORESOURCE_SYSTEM_RAM;\n+\tdev_set_drvdata(dev, data);\n \n-\t\tmhp_flags = MHP_NID_IS_MGID;\n-\t\tif (dev_dax-\u003ememmap_on_memory)\n-\t\t\tmhp_flags |= MHP_MEMMAP_ON_MEMORY;\n+\trc = dax_kmem_init_resources(dev_dax, data);\n+\tif (rc \u003c 0)\n+\t\tgoto err_resources;\n \n-\t\t/*\n-\t\t * Ensure that future kexec'd kernels will not treat\n-\t\t * this as RAM automatically.\n-\t\t */\n-\t\trc = add_memory_driver_managed(data-\u003emgid, range.start,\n-\t\t\t\trange_len(\u0026range), kmem_name, mhp_flags);\n+\t/* Resolve system default at bind time in case it changed */\n+\tonline_type = dev_dax-\u003eonline_type;\n+\tif (online_type == DAX_ONLINE_DEFAULT)\n+\t\tonline_type = mhp_get_default_online_type();\n \n-\t\tif (rc) {\n-\t\t\tdev_warn(dev, \"mapping%d: %#llx-%#llx memory add failed\\n\",\n-\t\t\t\t\ti, range.start, range.end);\n-\t\t\tremove_resource(res);\n-\t\t\tkfree(res);\n-\t\t\tdata-\u003eres[i] = NULL;\n-\t\t\tif (mapped)\n-\t\t\t\tcontinue;\n-\t\t\tgoto err_request_mem;\n-\t\t}\n-\t\tmapped++;\n-\t}\n+\t/* Always create blocks for backward compatibility, even if offline */\n+\trc = dax_kmem_do_hotplug(dev_dax, data, online_type);\n+\tif (rc \u003c 0)\n+\t\tgoto err_hotplug;\n+\tdata-\u003estate = online_type;\n \n-\tdev_set_drvdata(dev, data);\n+\trc = device_create_file(dev, \u0026dev_attr_state);\n+\tif (rc)\n+\t\tdev_warn(dev, \"failed to create state sysfs entry\\n\");\n \n \treturn 0;\n \n-err_request_mem:\n+err_hotplug:\n+\tdax_kmem_cleanup_resources(dev_dax, data);\n+err_resources:\n+\tdev_set_drvdata(dev, NULL);\n \tmemory_group_unregister(data-\u003emgid);\n err_reg_mgid:\n \tkfree(data-\u003eres_name);\n@@ -206,65 +499,86 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\n }\n \n #ifdef CONFIG_MEMORY_HOTREMOVE\n-static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\n+/*\n+ * Remove the device's added ranges with remove_memory().\n+ * Unlike the sysfs unplug path it never offlines and fails if the blocks are\n+ * online (-EBUSY), so it is safe from unbind. Failures leak until reboot.\n+ *\n+ * Returns 0 only if every added range was removed.\n+ */\n+static int dax_kmem_remove_ranges(struct dev_dax *dev_dax,\n+\t\t\t\t  struct dax_kmem_data *data)\n {\n-\tint i, success = 0;\n-\tint node = dev_dax-\u003etarget_node;\n \tstruct device *dev = \u0026dev_dax-\u003edev;\n-\tstruct dax_kmem_data *data = dev_get_drvdata(dev);\n+\tint i, rc = 0;\n \n-\t/*\n-\t * We have one shot for removing memory, if some memory blocks were not\n-\t * offline prior to calling this function remove_memory() will fail, and\n-\t * there is no way to hotremove this memory until reboot because device\n-\t * unbind will succeed even if we return failure.\n-\t */\n \tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n \t\tstruct range range;\n-\t\tint rc;\n \n-\t\trc = dax_kmem_range(dev_dax, i, \u0026range);\n-\t\tif (rc)\n+\t\tif (!data-\u003eres[i] || dax_kmem_range(dev_dax, i, \u0026range))\n \t\t\tcontinue;\n-\n-\t\t/* range was never added during probe */\n-\t\tif (!data-\u003eres[i]) {\n-\t\t\tsuccess++;\n+\t\tif (remove_memory(range.start, range_len(\u0026range))) {\n+\t\t\tdev_warn(dev, \"mapping%d: %#llx-%#llx stuck online until reboot\\n\",\n+\t\t\t\t i, range.start, range.end);\n+\t\t\trc = -EBUSY;\n \t\t\tcontinue;\n \t\t}\n+\t\tremove_resource(data-\u003eres[i]);\n+\t\tkfree(data-\u003eres[i]);\n+\t\tdata-\u003eres[i] = NULL;\n+\t}\n+\treturn rc;\n+}\n \n-\t\trc = remove_memory(range.start, range_len(\u0026range));\n-\t\tif (rc == 0) {\n-\t\t\tremove_resource(data-\u003eres[i]);\n-\t\t\tkfree(data-\u003eres[i]);\n-\t\t\tdata-\u003eres[i] = NULL;\n-\t\t\tsuccess++;\n-\t\t\tcontinue;\n-\t\t}\n+static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\n+{\n+\tint node = dev_dax-\u003etarget_node;\n+\tstruct device *dev = \u0026dev_dax-\u003edev;\n+\tstruct dax_kmem_data *data = dev_get_drvdata(dev);\n+\n+\tdevice_remove_file(dev, \u0026dev_attr_state);\n+\t/*\n+\t * If UNPLUGGED: state is known clean and reboot can clean up.\n+\t *\n+\t * If ONLINE_*: memory cannot be removed here: offlining during an\n+\t * uninterruptible unbind can deadlock. Leak the resources until reboot.\n+\t *\n+\t * If OFFLINE: blocks are attempted to remove with remove_memory(),\n+\t * which never attempts offlining. A block onlined behind our back\n+\t * fails -EBUSY and is leaked.\n+\t */\n+\tif (dax_kmem_state_is_online(data-\u003estate)) {\n+\t\tdev_warn(dev, \"Hotplug regions stuck online until reboot\\n\");\n+\t\tany_hotremove_failed = true;\n+\t\treturn;\n+\t} else if (data-\u003estate == MMOP_OFFLINE \u0026\u0026\n+\t\t   dax_kmem_remove_ranges(dev_dax, data)) {\n \t\tany_hotremove_failed = true;\n-\t\tdev_err(dev,\n-\t\t\t\"mapping%d: %#llx-%#llx cannot be hotremoved until the next reboot\\n\",\n-\t\t\t\ti, range.start, range.end);\n+\t\tdev_warn(dev, \"Unplug failed, resources leaked until reboot\\n\");\n+\t\treturn;\n \t}\n \n-\tif (success \u003e= dev_dax-\u003enr_range) {\n-\t\tmemory_group_unregister(data-\u003emgid);\n-\t\tkfree(data-\u003eres_name);\n-\t\tkfree(data);\n-\t\tdev_set_drvdata(dev, NULL);\n-\t\t/*\n-\t\t * Clear the memtype association on successful unplug.\n-\t\t * If not, we have memory blocks left which can be\n-\t\t * offlined/onlined later. We need to keep memory_dev_type\n-\t\t * for that. This implies this reference will be around\n-\t\t * till next reboot.\n-\t\t */\n-\t\tclear_node_memory_type(node, NULL);\n-\t}\n+\tdax_kmem_cleanup_resources(dev_dax, data);\n+\tmemory_group_unregister(data-\u003emgid);\n+\tkfree(data-\u003eres_name);\n+\tkfree(data);\n+\tdev_set_drvdata(dev, NULL);\n+\t/*\n+\t * Clear the memtype association on successful unplug.\n+\t * If not, we have memory blocks left which can be\n+\t * offlined/onlined later. We need to keep memory_dev_type\n+\t * for that. This implies this reference will be around\n+\t * till next reboot.\n+\t */\n+\tclear_node_memory_type(node, NULL);\n }\n #else\n static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\n {\n+\tstruct device *dev = \u0026dev_dax-\u003edev;\n+\n+\tdevice_remove_file(dev, \u0026dev_attr_state);\n+\n \t/*\n \t * Without hotremove purposely leak the request_mem_region() for the\n \t * device-dax range and return '0' to -\u003eremove() attempts. The removal\ndiff --git a/drivers/dax/pmem.c b/drivers/dax/pmem.c\nindex bee93066a84977..e7adace6919506 100644\n--- a/drivers/dax/pmem.c\n+++ b/drivers/dax/pmem.c\n@@ -63,6 +63,7 @@ static struct dev_dax *__dax_pmem_probe(struct device *dev)\n \t\t.pgmap = \u0026pgmap,\n \t\t.size = range_len(\u0026range),\n \t\t.memmap_on_memory = false,\n+\t\t.online_type = DAX_ONLINE_DEFAULT,\n \t};\n \n \treturn devm_create_dev_dax(\u0026data);\ndiff --git a/include/linux/memory.h b/include/linux/memory.h\nindex 463dc02f6cff05..1783299073e479 100644\n--- a/include/linux/memory.h\n+++ b/include/linux/memory.h\n@@ -20,6 +20,7 @@\n #include \u003clinux/compiler.h\u003e\n #include \u003clinux/mutex.h\u003e\n #include \u003clinux/memory_hotplug.h\u003e\n+#include \u003clinux/range.h\u003e\n \n #define MIN_MEMORY_BLOCK_SIZE     (1UL \u003c\u003c SECTION_SIZE_BITS)\n \n@@ -100,6 +101,32 @@ int arch_get_memory_phys_device(unsigned long start_pfn);\n unsigned long memory_block_size_bytes(void);\n int set_memory_block_size_order(unsigned int order);\n \n+/**\n+ * memory_block_aligned_range - align a physical address range to memory blocks\n+ * @range: the input range to align\n+ *\n+ * Aligns the start address up and the end address down to memory block\n+ * boundaries. This is required for memory hotplug operations which must\n+ * operate on memory-block aligned ranges.\n+ *\n+ * Returns the aligned range. Callers should check that the returned\n+ * range is valid (aligned.start \u003c aligned.end) before using it.\n+ */\n+static inline struct range memory_block_aligned_range(const struct range *range)\n+{\n+\tstruct range aligned;\n+\n+\taligned.start = ALIGN(range-\u003estart, memory_block_size_bytes());\n+\taligned.end = ALIGN_DOWN(range-\u003eend + 1, memory_block_size_bytes());\n+\t/* No whole block fits (e.g. range below the first boundary): empty. */\n+\tif (aligned.end \u003c= aligned.start)\n+\t\taligned.start = aligned.end;\n+\telse\n+\t\taligned.end -= 1;\n+\n+\treturn aligned;\n+}\n+\n struct memory_notify {\n \tunsigned long start_pfn;\n \tunsigned long nr_pages;\ndiff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h\nindex 7c9d66729c6095..db10d50f30ae1b 100644\n--- a/include/linux/memory_hotplug.h\n+++ b/include/linux/memory_hotplug.h\n@@ -127,6 +127,7 @@ extern int arch_add_memory(int nid, u64 start, u64 size,\n extern u64 max_mem_size;\n \n extern int mhp_online_type_from_str(const char *str);\n+const char *mhp_online_type_to_str(int online_type);\n \n /* If movable_node boot option specified */\n extern bool movable_node_enabled;\n@@ -267,6 +268,8 @@ extern int offline_pages(unsigned long start_pfn, unsigned long nr_pages,\n extern int remove_memory(u64 start, u64 size);\n extern void __remove_memory(u64 start, u64 size);\n extern int offline_and_remove_memory(u64 start, u64 size);\n+int offline_and_remove_memory_ranges(const struct range *ranges,\n+\t\tunsigned int nr_ranges);\n \n #else\n static inline void try_offline_node(int nid) {}\n@@ -283,6 +286,12 @@ static inline int remove_memory(u64 start, u64 size)\n }\n \n static inline void __remove_memory(u64 start, u64 size) {}\n+\n+static inline int offline_and_remove_memory_ranges(const struct range *ranges,\n+\t\tunsigned int nr_ranges)\n+{\n+\treturn -EBUSY;\n+}\n #endif /* CONFIG_MEMORY_HOTREMOVE */\n \n #ifdef CONFIG_MEMORY_HOTPLUG\n@@ -294,6 +303,9 @@ extern int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags);\n extern int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags);\n extern int add_memory_resource(int nid, struct resource *resource,\n \t\t\t       mhp_t mhp_flags);\n+int __add_memory_driver_managed(int nid, u64 start, u64 size,\n+\t\t\t\tconst char *resource_name, mhp_t mhp_flags,\n+\t\t\t\tenum mmop online_type);\n extern int add_memory_driver_managed(int nid, u64 start, u64 size,\n \t\t\t\t     const char *resource_name,\n \t\t\t\t     mhp_t mhp_flags);\n@@ -316,6 +328,8 @@ extern struct zone *zone_for_pfn_range(enum mmop online_type,\n extern int arch_create_linear_mapping(int nid, u64 start, u64 size,\n \t\t\t\t      struct mhp_params *params);\n void arch_remove_linear_mapping(u64 start, u64 size);\n+#else\n+static inline enum mmop mhp_get_default_online_type(void) { return MMOP_OFFLINE; }\n #endif /* CONFIG_MEMORY_HOTPLUG */\n \n #endif /* __LINUX_MEMORY_HOTPLUG_H */\ndiff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c\nindex 7ac19fab226324..3225364bec2f76 100644\n--- a/mm/memory_hotplug.c\n+++ b/mm/memory_hotplug.c\n@@ -239,6 +239,7 @@ enum mmop mhp_get_default_online_type(void)\n \n \treturn mhp_default_online_type;\n }\n+EXPORT_SYMBOL_GPL(mhp_get_default_online_type);\n \n void mhp_set_default_online_type(enum mmop online_type)\n {\n@@ -1337,7 +1338,9 @@ static int check_hotplug_memory_range(u64 start, u64 size)\n \n static int online_memory_block(struct memory_block *mem, void *arg)\n {\n-\tmem-\u003eonline_type = mhp_get_default_online_type();\n+\tenum mmop *online_type = arg;\n+\n+\tmem-\u003eonline_type = *online_type;\n \treturn device_online(\u0026mem-\u003edev);\n }\n \n@@ -1491,7 +1494,8 @@ static int create_altmaps_and_memory_blocks(int nid, struct memory_group *group,\n  *\n  * we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG\n  */\n-int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)\n+static int __add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags,\n+\t\t\t\t enum mmop online_type)\n {\n \tstruct mhp_params params = { .pgprot = pgprot_mhp(PAGE_KERNEL) };\n \tenum memblock_flags memblock_flags = MEMBLOCK_NONE;\n@@ -1581,8 +1585,9 @@ int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)\n \t\tmerge_system_ram_resource(res);\n \n \t/* online pages if requested */\n-\tif (mhp_get_default_online_type() != MMOP_OFFLINE)\n-\t\twalk_memory_blocks(start, size, NULL, online_memory_block);\n+\tif (online_type != MMOP_OFFLINE)\n+\t\twalk_memory_blocks(start, size, \u0026online_type,\n+\t\t\t\t   online_memory_block);\n \n \treturn ret;\n error:\n@@ -1598,7 +1603,13 @@ int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)\n \treturn ret;\n }\n \n-/* requires device_hotplug_lock, see add_memory_resource() */\n+int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)\n+{\n+\treturn __add_memory_resource(nid, res, mhp_flags,\n+\t\t\t\t     mhp_get_default_online_type());\n+}\n+\n+/* requires device_hotplug_lock, see __add_memory_resource() */\n int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags)\n {\n \tstruct resource *res;\n@@ -1626,7 +1637,15 @@ int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags)\n }\n EXPORT_SYMBOL_GPL(add_memory);\n \n-/*\n+/**\n+ * __add_memory_driver_managed - add driver-managed memory with explicit online_type\n+ * @nid: NUMA node ID where the memory will be added\n+ * @start: Start physical address of the memory range\n+ * @size: Size of the memory range in bytes\n+ * @resource_name: Resource name in format \"System RAM ($DRIVER)\"\n+ * @mhp_flags: Memory hotplug flags\n+ * @online_type: Auto-Online behavior (offline, online, kernel, movable)\n+ *\n  * Add special, driver-managed memory to the system as system RAM. Such\n  * memory is not exposed via the raw firmware-provided memmap as system\n  * RAM, instead, it is detected and added by a driver - during cold boot,\n@@ -1634,6 +1653,7 @@ EXPORT_SYMBOL_GPL(add_memory);\n  *\n  * Reasons why this memory should not be used for the initial memmap of a\n  * kexec kernel or for placing kexec images:\n+ *\n  * - The booting kernel is in charge of determining how this memory will be\n  *   used (e.g., use persistent memory as system RAM)\n  * - Coordination with a hypervisor is required before this memory\n@@ -1646,9 +1666,12 @@ EXPORT_SYMBOL_GPL(add_memory);\n  *\n  * The resource_name (visible via /proc/iomem) has to have the format\n  * \"System RAM ($DRIVER)\".\n+ *\n+ * Return: 0 on success, negative error code on failure.\n  */\n-int add_memory_driver_managed(int nid, u64 start, u64 size,\n-\t\t\t      const char *resource_name, mhp_t mhp_flags)\n+int __add_memory_driver_managed(int nid, u64 start, u64 size,\n+\t\tconst char *resource_name, mhp_t mhp_flags,\n+\t\tenum mmop online_type)\n {\n \tstruct resource *res;\n \tint rc;\n@@ -1658,6 +1681,9 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,\n \t    resource_name[strlen(resource_name) - 1] != ')')\n \t\treturn -EINVAL;\n \n+\tif (online_type \u003c MMOP_OFFLINE || online_type \u003e MMOP_ONLINE_MOVABLE)\n+\t\treturn -EINVAL;\n+\n \tlock_device_hotplug();\n \n \tres = register_memory_resource(start, size, resource_name);\n@@ -1666,7 +1692,7 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,\n \t\tgoto out_unlock;\n \t}\n \n-\trc = add_memory_resource(nid, res, mhp_flags);\n+\trc = __add_memory_resource(nid, res, mhp_flags, online_type);\n \tif (rc \u003c 0)\n \t\trelease_memory_resource(res);\n \n@@ -1674,6 +1700,30 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,\n \tunlock_device_hotplug();\n \treturn rc;\n }\n+EXPORT_SYMBOL_FOR_MODULES(__add_memory_driver_managed, \"kmem\");\n+\n+/**\n+ * add_memory_driver_managed - add driver-managed memory\n+ * @nid: NUMA node ID where the memory will be added\n+ * @start: Start physical address of the memory range\n+ * @size: Size of the memory range in bytes\n+ * @resource_name: Resource name in format \"System RAM ($DRIVER)\"\n+ * @mhp_flags: Memory hotplug flags\n+ *\n+ * Add driver-managed memory with the system default online type set by\n+ * build config or kernel boot parameter.\n+ *\n+ * See __add_memory_driver_managed for more details.\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int add_memory_driver_managed(int nid, u64 start, u64 size,\n+\t\t\t      const char *resource_name, mhp_t mhp_flags)\n+{\n+\treturn __add_memory_driver_managed(nid, start, size, resource_name,\n+\t\t\tmhp_flags,\n+\t\t\tmhp_get_default_online_type());\n+}\n EXPORT_SYMBOL_GPL(add_memory_driver_managed);\n \n /*\n@@ -2379,58 +2429,95 @@ static int try_reonline_memory_block(struct memory_block *mem, void *arg)\n  */\n int offline_and_remove_memory(u64 start, u64 size)\n {\n-\tconst unsigned long mb_count = size / memory_block_size_bytes();\n+\tstruct range range = {\n+\t\t.start = start,\n+\t\t.end = start + size - 1,\n+\t};\n+\n+\treturn offline_and_remove_memory_ranges(\u0026range, 1);\n+}\n+EXPORT_SYMBOL_GPL(offline_and_remove_memory);\n+\n+/**\n+ * offline_and_remove_memory_ranges - offline and remove multiple memory ranges\n+ * @ranges: array of physical address ranges to offline and remove\n+ * @nr_ranges: number of entries in @ranges\n+ *\n+ * Offline and remove several memory ranges as one operation, serialized\n+ * against other hotplug operations by a single lock_device_hotplug().\n+ *\n+ * This offlines all ranges before removing any of them.  If offlining any\n+ * range fails, the entire process is reverted and nothing is removed.\n+ * This provides a fully atomic semantic for unplugging an entire device.\n+ *\n+ * Each range must be memory-block aligned in start and size.\n+ *\n+ * Return: 0 on success, negative errno otherwise.  On failure no range has\n+ * been removed.\n+ */\n+int offline_and_remove_memory_ranges(const struct range *ranges,\n+\t\tunsigned int nr_ranges)\n+{\n+\tunsigned long mb_count = 0;\n \tuint8_t *online_types, *tmp;\n-\tint rc;\n+\tunsigned int i;\n+\tint rc = 0;\n \n-\tif (!IS_ALIGNED(start, memory_block_size_bytes()) ||\n-\t    !IS_ALIGNED(size, memory_block_size_bytes()) || !size)\n+\tif (!ranges || !nr_ranges)\n \t\treturn -EINVAL;\n \n+\tfor (i = 0; i \u003c nr_ranges; i++) {\n+\t\tconst u64 start = ranges[i].start;\n+\t\tconst u64 size = range_len(\u0026ranges[i]);\n+\n+\t\tif (!IS_ALIGNED(start, memory_block_size_bytes()) ||\n+\t\t    !IS_ALIGNED(size, memory_block_size_bytes()) || !size)\n+\t\t\treturn -EINVAL;\n+\t\tmb_count += size / memory_block_size_bytes();\n+\t}\n+\n \t/*\n-\t * We'll remember the old online type of each memory block, so we can\n-\t * try to revert whatever we did when offlining one memory block fails\n-\t * after offlining some others succeeded.\n+\t * Remember the old online type of every memory block across all ranges,\n+\t * so we can revert if offlining a later block fails.  All entries start\n+\t * as MMOP_OFFLINE so blocks we never touched are skipped on rollback.\n \t */\n \tonline_types = kmalloc_array(mb_count, sizeof(*online_types),\n \t\t\t\t     GFP_KERNEL);\n \tif (!online_types)\n \t\treturn -ENOMEM;\n-\t/*\n-\t * Initialize all states to MMOP_OFFLINE, so when we abort processing in\n-\t * try_offline_memory_block(), we'll skip all unprocessed blocks in\n-\t * try_reonline_memory_block().\n-\t */\n \tmemset(online_types, MMOP_OFFLINE, mb_count);\n \n \tlock_device_hotplug();\n \n+\t/* Phase 1: offline every block in every range. */\n \ttmp = online_types;\n-\trc = walk_memory_blocks(start, size, \u0026tmp, try_offline_memory_block);\n-\n-\t/*\n-\t * In case we succeeded to offline all memory, remove it.\n-\t * This cannot fail as it cannot get onlined in the meantime.\n-\t */\n-\tif (!rc) {\n-\t\trc = try_remove_memory(start, size);\n+\tfor (i = 0; i \u003c nr_ranges; i++) {\n+\t\trc = walk_memory_blocks(ranges[i].start, range_len(\u0026ranges[i]),\n+\t\t\t\t\t\u0026tmp, try_offline_memory_block);\n \t\tif (rc)\n-\t\t\tpr_err(\"%s: Failed to remove memory: %d\", __func__, rc);\n+\t\t\tbreak;\n \t}\n \n-\t/*\n-\t * Rollback what we did. While memory onlining might theoretically fail\n-\t * (nacked by a notifier), it barely ever happens.\n-\t */\n+\t/* If any failure occurred at all, rollback any changes and bail */\n \tif (rc) {\n \t\ttmp = online_types;\n-\t\twalk_memory_blocks(start, size, \u0026tmp,\n-\t\t\t\t   try_reonline_memory_block);\n+\t\tfor (i = 0; i \u003c nr_ranges; i++)\n+\t\t\twalk_memory_blocks(ranges[i].start,\n+\t\t\t\t\t   range_len(\u0026ranges[i]), \u0026tmp,\n+\t\t\t\t\t   try_reonline_memory_block);\n+\t\tgoto out_unlock;\n \t}\n+\n+\t/* Phase 2: Remove. This should never fail holding the hotplug lock */\n+\tfor (i = 0; i \u003c nr_ranges; i++)\n+\t\tWARN_ON_ONCE(try_remove_memory(ranges[i].start,\n+\t\t\t\t\t       range_len(\u0026ranges[i])));\n+\n+out_unlock:\n \tunlock_device_hotplug();\n \n \tkfree(online_types);\n \treturn rc;\n }\n-EXPORT_SYMBOL_GPL(offline_and_remove_memory);\n+EXPORT_SYMBOL_GPL(offline_and_remove_memory_ranges);\n #endif /* CONFIG_MEMORY_HOTREMOVE */\ndiff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile\nindex 8d4db2241cc2a2..5528682a3a9122 100644\n--- a/tools/testing/selftests/Makefile\n+++ b/tools/testing/selftests/Makefile\n@@ -14,6 +14,7 @@ TARGETS += core\n TARGETS += cpufreq\n TARGETS += cpu-hotplug\n TARGETS += damon\n+TARGETS += dax\n TARGETS += devices/error_logs\n TARGETS += devices/probe\n TARGETS += dmabuf-heaps\ndiff --git a/tools/testing/selftests/dax/Makefile b/tools/testing/selftests/dax/Makefile\nnew file mode 100644\nindex 00000000000000..25a4f3d73a5baa\n--- /dev/null\n+++ b/tools/testing/selftests/dax/Makefile\n@@ -0,0 +1,6 @@\n+# SPDX-License-Identifier: GPL-2.0\n+all:\n+\n+TEST_PROGS := dax-kmem-hotplug.sh\n+\n+include ../lib.mk\ndiff --git a/tools/testing/selftests/dax/config b/tools/testing/selftests/dax/config\nnew file mode 100644\nindex 00000000000000..4c9aaeb6ceb41a\n--- /dev/null\n+++ b/tools/testing/selftests/dax/config\n@@ -0,0 +1,4 @@\n+CONFIG_DEV_DAX=m\n+CONFIG_DEV_DAX_KMEM=m\n+CONFIG_MEMORY_HOTPLUG=y\n+CONFIG_MEMORY_HOTREMOVE=y\ndiff --git a/tools/testing/selftests/dax/dax-kmem-hotplug.sh b/tools/testing/selftests/dax/dax-kmem-hotplug.sh\nnew file mode 100755\nindex 00000000000000..c8bbaf6178ed9c\n--- /dev/null\n+++ b/tools/testing/selftests/dax/dax-kmem-hotplug.sh\n@@ -0,0 +1,190 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Exercise the dax/kmem \"state\" sysfs attribute:\n+#   /sys/bus/dax/devices/daxX.Y/state  -\u003e  unplugged | online | online_kernel | online_movable\n+#\n+# The test needs a dax device already bound to the kmem driver.\n+# If no suitable device is found the tests SKIP.\n+#\n+# A dax device can be provisioned with the memmap= boot param, e.g.:\n+#   memmap=2G!4G\n+#\n+# then, in the booted system:\n+#\n+#   ndctl create-namespace -m devdax -e namespace0.0 -f\n+#   daxctl reconfigure-device -N -m system-ram dax0.0   # bind kmem\n+#   ./dax-kmem-hotplug.sh\n+\n+# shellcheck disable=SC1091\n+DIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\n+. \"$DIR\"/../kselftest/ktap_helpers.sh\n+\n+DAX_BASE=/sys/bus/dax/devices\n+\n+memtotal_kb() { awk '/^MemTotal:/ {print $2}' /proc/meminfo; }\n+get_state() { cat \"$HP\" 2\u003e/dev/null; }\n+# set_state STATE -- write a state to the state attribute; returns the\n+# write's exit status (0 = accepted by the kernel)\n+set_state() { echo \"$1\" \u003e \"$HP\" 2\u003e/dev/null; }\n+\n+find_kmem_dax() {\n+\tlocal d drv\n+\tfor d in \"$DAX_BASE\"/dax*; do\n+\t\t[ -e \"$d/state\" ] || continue\n+\t\tdrv=$(readlink \"$d/driver\" 2\u003e/dev/null)\n+\t\t[ \"$(basename \"${drv:-}\")\" = kmem ] || continue\n+\t\tbasename \"$d\"\n+\t\treturn 0\n+\tdone\n+\treturn 1\n+}\n+\n+ktap_print_header\n+\n+if [ \"$UID\" != 0 ]; then\n+\tktap_skip_all \"must be run as root\"\n+\texit \"$KSFT_SKIP\"\n+fi\n+\n+DAX=$(find_kmem_dax)\n+if [ -z \"$DAX\" ]; then\n+\tktap_skip_all \"no kmem-bound dax device with a state attribute\"\n+\texit \"$KSFT_SKIP\"\n+fi\n+HP=$DAX_BASE/$DAX/state\n+ORIG=$(get_state)\n+\n+# A failure to reach the baseline is environmental (memory in use), not an\n+# interface failure, so skip rather than fail.\n+set_state unplugged; rc=$?\n+if [ \"$rc\" != 0 ] || [ \"$(get_state)\" != unplugged ]; then\n+\tktap_skip_all \"$DAX: cannot reach 'unplugged' baseline (memory in use?)\"\n+\t[ -n \"$ORIG\" ] \u0026\u0026 set_state \"$ORIG\"\n+\texit \"$KSFT_SKIP\"\n+fi\n+mt_unplugged=$(memtotal_kb)\n+\n+DRV=/sys/bus/dax/drivers/kmem\n+AOB=/sys/devices/system/memory/auto_online_blocks\n+\n+ktap_print_msg \"using $DAX (initial state was: $ORIG)\"\n+ktap_set_plan 8\n+\n+# A public (N_MEMORY) kmem node onlined into a kernel zone (online/online_kernel)\n+# collects unmovable allocations and can then never be offlined, which would\n+# wedge the device for the rest of this test.  So this test only ever\n+# successfully onlines online_movable, the one mode that is reliably unpluggable.\n+\n+set_state online_movable; rc=$?\n+mt_online=$(memtotal_kb)\n+if [ \"$rc\" = 0 ] \u0026\u0026 [ \"$(get_state)\" = online_movable ] \u0026\u0026 [ \"$mt_online\" -gt \"$mt_unplugged\" ]; then\n+\tktap_test_pass \"online_movable: state=online_movable, MemTotal $mt_unplugged -\u003e $mt_online kB\"\n+else\n+\tktap_test_fail \"online_movable: rc=$rc state=$(get_state) MemTotal $mt_unplugged -\u003e $mt_online\"\n+fi\n+\n+set_state online_movable; rc=$?\n+if [ \"$rc\" = 0 ] \u0026\u0026 [ \"$(get_state)\" = online_movable ]; then\n+\tktap_test_pass \"online_movable idempotent\"\n+else\n+\tktap_test_fail \"online_movable idempotent: rc=$rc state=$(get_state)\"\n+fi\n+\n+# A different online type is rejected without an intervening unplug.  The write\n+# is refused before any hotplug, so this never actually onlines a kernel zone.\n+set_state online_kernel; rc=$?\n+if [ \"$rc\" != 0 ] \u0026\u0026 [ \"$(get_state)\" = online_movable ]; then\n+\tktap_test_pass \"reject online_kernel without intervening unplug (no kernel-zone online)\"\n+else\n+\tktap_test_fail \"online_movable-\u003eonline_kernel not rejected: rc=$rc state=$(get_state)\"\n+fi\n+\n+set_state unplugged; rc=$?\n+mt=$(memtotal_kb)\n+if [ \"$rc\" = 0 ] \u0026\u0026 [ \"$(get_state)\" = unplugged ] \u0026\u0026 [ \"$mt\" -lt \"$mt_online\" ]; then\n+\tktap_test_pass \"unplug from online_movable: MemTotal $mt_online -\u003e $mt kB\"\n+else\n+\tktap_test_fail \"unplug from online_movable: rc=$rc state=$(get_state) MemTotal $mt_online -\u003e $mt\"\n+fi\n+\n+before=$(get_state)\n+set_state bogus_state; rc=$?\n+if [ \"$rc\" != 0 ] \u0026\u0026 [ \"$(get_state)\" = \"$before\" ]; then\n+\tktap_test_pass \"reject invalid state string\"\n+else\n+\tktap_test_fail \"invalid state not rejected: rc=$rc state=$(get_state)\"\n+fi\n+\n+# The online_movable -\u003e unplug cycle once regressed: a re-online failed to\n+# re-reserve the per-range resources, so a later unplug reported success while\n+# leaving the memory online.  Assert each iteration really adds and frees memory.\n+set_state unplugged\n+cycle_ok=1; fail_i=0; on=0; off=0\n+for i in 1 2 3; do\n+\tif ! set_state online_movable; then cycle_ok=0; fail_i=$i; break; fi\n+\ton=$(memtotal_kb)\n+\tif ! set_state unplugged; then cycle_ok=0; fail_i=$i; break; fi\n+\toff=$(memtotal_kb)\n+\tif [ \"$on\" -le \"$mt_unplugged\" ] || [ \"$off\" -ge \"$on\" ]; then\n+\t\tcycle_ok=0; fail_i=$i; break\n+\tfi\n+done\n+if [ \"$cycle_ok\" = 1 ]; then\n+\tktap_test_pass \"online_movable/unplug cycle re-acquires resources (3x: added and freed each time)\"\n+else\n+\tktap_test_fail \"online_movable/unplug cycle regressed at iteration $fail_i (on=$on off=$off baseline=$mt_unplugged)\"\n+fi\n+\n+# change system default online policy while the device is unbound, and show\n+# the new system default policy is utilized across bindings.\n+set_state unplugged\n+if [ -w \"$AOB\" ] \u0026\u0026 [ -w \"$DRV/unbind\" ] \u0026\u0026 [ -w \"$DRV/bind\" ]; then\n+\torig_aob=$(cat \"$AOB\")\n+\techo \"$DAX\" \u003e \"$DRV/unbind\" 2\u003e/dev/null\n+\techo offline \u003e \"$AOB\" 2\u003e/dev/null\n+\techo \"$DAX\" \u003e \"$DRV/bind\" 2\u003e/dev/null\n+\tsleep 1\n+\tst=$(get_state)\n+\techo \"$orig_aob\" \u003e \"$AOB\" 2\u003e/dev/null\t\t# restore system policy\n+\tif [ \"$st\" = offline ]; then\n+\t\tktap_test_pass \"online policy resolved at bind: auto_online_blocks=offline -\u003e state=offline\"\n+\telse\n+\t\tktap_test_fail \"bind-time policy not honored: state=$st (expected offline)\"\n+\tfi\n+\tset_state unplugged 2\u003e/dev/null\n+else\n+\tktap_test_skip \"auto_online_blocks or driver bind/unbind not writable\"\n+fi\n+\n+[ -n \"$ORIG\" ] \u0026\u0026 set_state \"$ORIG\"\n+\n+# DESTRUCTIVE: unbinding the driver while memory is online causes the resources\n+# to leak - but the unbind should not deadlock.  Instead the driver leaks it\n+# with a single \"stuck online\" warning. This leaves the memory online and the\n+# device unbound until reboot, so it runs last - and only if we can run it,\n+# leaving the restored state above untouched otherwise.  online_movable only:\n+# this test never onlines a public node into a kernel zone.\n+if [ -w \"$DRV/unbind\" ]; then\n+\tset_state unplugged; set_state online_movable\n+fi\n+if [ \"$(get_state)\" = online_movable ] \u0026\u0026 [ -w \"$DRV/unbind\" ]; then\n+\tmt_on=$(memtotal_kb)\n+\tdmesg -C 2\u003e/dev/null\n+\techo \"$DAX\" \u003e \"$DRV/unbind\" 2\u003e/dev/null\n+\tmt_after=$(memtotal_kb)\n+\t# The leaked \"System RAM (kmem)\" regions stay in the iomem tree; reading\n+\t# their names dereferences res_name, which a buggy unbind already freed.\n+\t# Walk /proc/iomem to provoke that use-after-free (caught by KASAN).\n+\tcat /proc/iomem \u003e /dev/null 2\u003e\u00261\n+\tsplat=$(dmesg 2\u003e/dev/null | grep -ciE \"KASAN|BUG:|use-after-free|general protection|Oops|refcount_t\")\n+\tif [ \"$splat\" = 0 ] \u0026\u0026 [ \"$mt_after\" -ge \"$mt_on\" ]; then\n+\t\tktap_test_pass \"unbind while online: memory left online, no UAF/oops (MemTotal $mt_on -\u003e $mt_after kB)\"\n+\telse\n+\t\tktap_test_fail \"unbind while online regressed: splat=$splat MemTotal $mt_on -\u003e $mt_after kB\"\n+\tfi\n+else\n+\tktap_test_skip \"could not online device for unbind-while-online test\"\n+fi\n+\n+ktap_finished\ndiff --git a/tools/testing/selftests/dax/settings b/tools/testing/selftests/dax/settings\nnew file mode 100644\nindex 00000000000000..ba4d85f74cd6b9\n--- /dev/null\n+++ b/tools/testing/selftests/dax/settings\n@@ -0,0 +1 @@\n+timeout=90\n"
}

2/1 2026/07/01 04:28 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "DEV_DAX",
    "DEV_DAX_KMEM",
    "MEMORY_HOTPLUG",
    "MEMORY_HOTREMOVE"
  ],
  "FocusSymbols": [
    "dax_kmem_do_hotplug",
    "dax_kmem_do_hotremove",
    "dax_kmem_init_resources",
    "dax_kmem_cleanup_resources",
    "dax_kmem_parse_state",
    "offline_and_remove_memory_ranges",
    "__add_memory_driver_managed",
    "mhp_online_type_to_str"
  ],
  "Reasoning": "The patch introduces a new sysfs attribute to control the memory hotplug state of DAX kmem devices. It adds logic to handle hotplugging and hotremoving memory ranges, including new core memory hotplug functions like `offline_and_remove_memory_ranges` and `__add_memory_driver_managed`. These are reachable via sysfs and device binding/unbinding, making them suitable for fuzzing.",
  "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 637122e5a22d30d7a50ebbf3d4ad2ea4e2b4e657
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 1 04:28:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/ABI/testing/sysfs-bus-dax b/Documentation/ABI/testing/sysfs-bus-dax
index b34266bfae49ae..2dcad1e9dad0d8 100644
--- a/Documentation/ABI/testing/sysfs-bus-dax
+++ b/Documentation/ABI/testing/sysfs-bus-dax
@@ -151,3 +151,29 @@ Description:
 		memmap_on_memory parameter for memory_hotplug. This is
 		typically set on the kernel command line -
 		memory_hotplug.memmap_on_memory set to 'true' or 'force'."
+
+What:		/sys/bus/dax/devices/daxX.Y/state
+Date:		June, 2026
+KernelVersion:	v6.21
+Contact:	nvdimm@lists.linux.dev
+Description:
+		(RW) Controls the state of the memory region.
+		Applies to all memory blocks associated with the device.
+		Only applies to dax_kmem devices.
+
+		Reading returns the current state; the writable states mirror
+		the per-block /sys/devices/system/memory/memoryX/state ABI::
+
+		  "unplugged": memory blocks are not present
+		  "online": memory is online, zone chosen by the kernel
+		  "online_kernel": memory is online in ZONE_NORMAL
+		  "online_movable": memory is online in ZONE_MOVABLE
+
+		"offline" (memory blocks are present but offline) may also be
+		reported - this happens when the device is bound while the
+		auto_online_blocks policy is "offline".  It cannot be written,
+		as it's not useful and creates device destruction races.
+
+		A device can only be onlined from the "unplugged" state, so a
+		device must be returned to "unplugged" before it can be onlined
+		into a different state.
diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index bcfe2d9f4adbd9..5eead3346f1e32 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -46,6 +46,15 @@ int mhp_online_type_from_str(const char *str)
 	}
 	return -EINVAL;
 }
+EXPORT_SYMBOL_GPL(mhp_online_type_from_str);
+
+const char *mhp_online_type_to_str(int online_type)
+{
+	if (online_type < 0 || online_type >= (int)ARRAY_SIZE(online_type_to_str))
+		return NULL;
+	return online_type_to_str[online_type];
+}
+EXPORT_SYMBOL_GPL(mhp_online_type_to_str);
 
 #define to_memory_block(dev) container_of(dev, struct memory_block, dev)
 
diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
index d8aea5fe0af2c5..8d83b5577a8ea0 100644
--- a/drivers/dax/bus.c
+++ b/drivers/dax/bus.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright(c) 2017-2018 Intel Corporation. All rights reserved. */
 #include <linux/memremap.h>
+#include <linux/memory_hotplug.h>
 #include <linux/device.h>
 #include <linux/mutex.h>
 #include <linux/list.h>
@@ -394,6 +395,7 @@ static ssize_t create_store(struct device *dev, struct device_attribute *attr,
 			.size = 0,
 			.id = -1,
 			.memmap_on_memory = false,
+			.online_type = DAX_ONLINE_DEFAULT,
 		};
 		struct dev_dax *dev_dax = __devm_create_dev_dax(&data);
 
@@ -1527,6 +1529,7 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data)
 	ida_init(&dev_dax->ida);
 
 	dev_dax->memmap_on_memory = data->memmap_on_memory;
+	dev_dax->online_type = data->online_type;
 
 	inode = dax_inode(dax_dev);
 	dev->devt = inode->i_rdev;
diff --git a/drivers/dax/bus.h b/drivers/dax/bus.h
index 5909171a4428b7..3bc76bc0a14582 100644
--- a/drivers/dax/bus.h
+++ b/drivers/dax/bus.h
@@ -3,6 +3,7 @@
 #ifndef __DAX_BUS_H__
 #define __DAX_BUS_H__
 #include <linux/device.h>
+#include <linux/memory_hotplug.h>
 #include <linux/platform_device.h>
 #include <linux/range.h>
 #include <linux/workqueue.h>
@@ -16,6 +17,13 @@ struct dax_region;
 #define IORESOURCE_DAX_STATIC BIT(0)
 #define IORESOURCE_DAX_KMEM BIT(1)
 
+/*
+ * online_type sentinel: the device was created without an explicit online
+ * policy, so the system default is resolved when the kmem driver binds,
+ * (not at device-creation time, which would freeze a stale policy).
+ */
+#define DAX_ONLINE_DEFAULT	(-1)
+
 struct dax_region *alloc_dax_region(struct device *parent, int region_id,
 		struct range *range, int target_node, unsigned int align,
 		unsigned long flags);
@@ -26,6 +34,7 @@ struct dev_dax_data {
 	resource_size_t size;
 	int id;
 	bool memmap_on_memory;
+	int online_type;	/* enum mmop, or DAX_ONLINE_DEFAULT sentinel */
 };
 
 struct dev_dax *devm_create_dev_dax(struct dev_dax_data *data);
diff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c
index 3ab39b77843d5e..1a7ec621221347 100644
--- a/drivers/dax/cxl.c
+++ b/drivers/dax/cxl.c
@@ -27,6 +27,7 @@ static int cxl_dax_region_probe(struct device *dev)
 		.id = -1,
 		.size = range_len(&cxlr_dax->hpa_range),
 		.memmap_on_memory = true,
+		.online_type = DAX_ONLINE_DEFAULT,
 	};
 
 	return PTR_ERR_OR_ZERO(devm_create_dev_dax(&data));
diff --git a/drivers/dax/dax-private.h b/drivers/dax/dax-private.h
index 81e4af49e39c10..902e922dc4e4f1 100644
--- a/drivers/dax/dax-private.h
+++ b/drivers/dax/dax-private.h
@@ -8,6 +8,7 @@
 #include <linux/device.h>
 #include <linux/cdev.h>
 #include <linux/idr.h>
+#include <linux/memory_hotplug.h>
 
 /* private routines between core files */
 struct dax_device;
@@ -79,6 +80,8 @@ struct dev_dax_range {
  * @dev: device core
  * @pgmap: pgmap for memmap setup / lifetime (driver owned)
  * @memmap_on_memory: allow kmem to put the memmap in the memory
+ * @online_type: MMOP_* online type for memory hotplug, or DAX_ONLINE_DEFAULT
+ *		 to resolve the system default policy when kmem binds
  * @nr_range: size of @ranges
  * @ranges: range tuples of memory used
  */
@@ -95,6 +98,7 @@ struct dev_dax {
 	struct device dev;
 	struct dev_pagemap *pgmap;
 	bool memmap_on_memory;
+	int online_type;	/* enum mmop, or DAX_ONLINE_DEFAULT sentinel */
 	int nr_range;
 	struct dev_dax_range *ranges;
 };
diff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c
index af21f66bf872c6..2de3bc9251729b 100644
--- a/drivers/dax/hmem/hmem.c
+++ b/drivers/dax/hmem/hmem.c
@@ -37,6 +37,7 @@ static int dax_hmem_probe(struct platform_device *pdev)
 		.id = -1,
 		.size = region_idle ? 0 : range_len(&mri->range),
 		.memmap_on_memory = false,
+		.online_type = DAX_ONLINE_DEFAULT,
 	};
 
 	return PTR_ERR_OR_ZERO(devm_create_dev_dax(&data));
diff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c
index a18e2b968e4dae..19effe0da3dcef 100644
--- a/drivers/dax/kmem.c
+++ b/drivers/dax/kmem.c
@@ -33,9 +33,7 @@ static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)
 	struct dev_dax_range *dax_range = &dev_dax->ranges[i];
 	struct range *range = &dax_range->range;
 
-	/* memory-block align the hotplug range */
-	r->start = ALIGN(range->start, memory_block_size_bytes());
-	r->end = ALIGN_DOWN(range->end + 1, memory_block_size_bytes()) - 1;
+	*r = memory_block_aligned_range(range);
 	if (r->start >= r->end) {
 		r->start = range->start;
 		r->end = range->end;
@@ -44,9 +42,15 @@ static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)
 	return 0;
 }
 
+#define DAX_KMEM_UNPLUGGED	(-1)
+
 struct dax_kmem_data {
 	const char *res_name;
 	int mgid;
+	int numa_node;
+	struct dev_dax *dev_dax;
+	int state;
+	struct mutex lock; /* protects hotplug state transitions */
 	struct resource *res[];
 };
 
@@ -65,15 +69,334 @@ static void kmem_put_memory_types(void)
 	mt_put_memory_types(&kmem_memory_types);
 }
 
+/* True for the online states a kmem dax device can hold. */
+static bool dax_kmem_state_is_online(int state)
+{
+	return state == MMOP_ONLINE ||
+	       state == MMOP_ONLINE_KERNEL ||
+	       state == MMOP_ONLINE_MOVABLE;
+}
+
+/**
+ * dax_kmem_do_hotplug - hotplug memory for dax kmem device
+ * @dev_dax: the dev_dax instance
+ * @data: the dax_kmem_data structure with resource tracking
+ * @online_type: the online policy to use for the memory blocks
+ *
+ * Hotplugs all ranges in the dev_dax region as system memory with the
+ * provided online policy (offline, online, online_movable, online_kernel).
+ *
+ * Returns the number of successfully mapped ranges, or negative error.
+ */
+static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,
+			       struct dax_kmem_data *data,
+			       int online_type)
+{
+	struct device *dev = &dev_dax->dev;
+	int i, rc, added = 0;
+	mhp_t mhp_flags;
+
+	if (dax_kmem_state_is_online(data->state))
+		return -EINVAL;
+
+	if (online_type < MMOP_OFFLINE || online_type > MMOP_ONLINE_MOVABLE)
+		return -EINVAL;
+
+	for (i = 0; i < dev_dax->nr_range; i++) {
+		struct range range;
+
+		rc = dax_kmem_range(dev_dax, i, &range);
+		if (rc)
+			continue;
+
+		/*
+		 * init_resources() is best-effort: if a reservation conflict
+		 * occurs it keeps the range but leaves res[i]=NULL. For hotplug
+		 * on probe systems, this means kmem will partially online.
+		 *
+		 * We have to keep this behavior not to break those systems.
+		 * For those systems - atomicity only applies to valid ranges.
+		 */
+		if (!data->res[i])
+			continue;
+
+		mhp_flags = MHP_NID_IS_MGID;
+		if (dev_dax->memmap_on_memory)
+			mhp_flags |= MHP_MEMMAP_ON_MEMORY;
+
+		/*
+		 * Ensure that future kexec'd kernels will not treat
+		 * this as RAM automatically.
+		 */
+		rc = __add_memory_driver_managed(data->mgid, range.start,
+				range_len(&range), kmem_name, mhp_flags,
+				online_type);
+
+		if (rc) {
+			dev_warn(dev, "mapping%d: %#llx-%#llx memory add failed\n",
+				 i, range.start, range.end);
+			/*
+			 * Release the reservation for the range that failed to
+			 * add so a later hotremove does not try to remove memory
+			 * that was never added.
+			 */
+			if (data->res[i]) {
+				remove_resource(data->res[i]);
+				kfree(data->res[i]);
+				data->res[i] = NULL;
+			}
+			if (added)
+				continue;
+			return rc;
+		}
+		added++;
+	}
+
+	return added;
+}
+
+/**
+ * dax_kmem_init_resources - create memory regions for dax kmem
+ * @dev_dax: the dev_dax instance
+ * @data: the dax_kmem_data structure with resource tracking
+ *
+ * Initializes all the resources for the DAX
+ *
+ * Returns the number of successfully mapped ranges, or negative error.
+ */
+static int dax_kmem_init_resources(struct dev_dax *dev_dax,
+				   struct dax_kmem_data *data)
+{
+	struct device *dev = &dev_dax->dev;
+	int i, rc, mapped = 0;
+
+	for (i = 0; i < dev_dax->nr_range; i++) {
+		struct resource *res;
+		struct range range;
+
+		rc = dax_kmem_range(dev_dax, i, &range);
+		if (rc)
+			continue;
+
+		/* Skip ranges already added */
+		if (data->res[i])
+			continue;
+
+		/* Region is permanently reserved if hotremove fails. */
+		res = request_mem_region(range.start, range_len(&range),
+					 data->res_name);
+		if (!res) {
+			dev_warn(dev, "mapping%d: %#llx-%#llx could not reserve region\n",
+				 i, range.start, range.end);
+			/*
+			 * Once some memory has been onlined we can't
+			 * assume that it can be un-onlined safely.
+			 */
+			if (mapped)
+				continue;
+			return -EBUSY;
+		}
+		data->res[i] = res;
+		/*
+		 * Set flags appropriate for System RAM.  Leave ..._BUSY clear
+		 * so that add_memory() can add a child resource.  Do not
+		 * inherit flags from the parent since it may set new flags
+		 * unknown to us that will break add_memory() below.
+		 */
+		res->flags = IORESOURCE_SYSTEM_RAM;
+		mapped++;
+	}
+	return mapped;
+}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+/**
+ * dax_kmem_do_hotremove - hot-remove memory for dax kmem device
+ * @dev_dax: the dev_dax instance
+ * @data: the dax_kmem_data structure with resource tracking
+ *
+ * Offlines and removes every currently-added range in the dev_dax region
+ * atomically: either all ranges are offlined and removed, or none are and
+ * the device is returned to its prior state.
+ *
+ * Returns 0 on success, or a negative errno on failure.
+ */
+static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,
+				 struct dax_kmem_data *data)
+{
+	struct device *dev = &dev_dax->dev;
+	struct range *ranges;
+	int i, nr_ranges = 0, rc;
+
+	ranges = kmalloc_array(dev_dax->nr_range, sizeof(*ranges), GFP_KERNEL);
+	if (!ranges)
+		return -ENOMEM;
+
+	/* Collect the ranges that were actually added during probe. */
+	for (i = 0; i < dev_dax->nr_range; i++) {
+		struct range range;
+
+		if (!data->res[i])
+			continue;
+		if (dax_kmem_range(dev_dax, i, &range))
+			continue;
+		ranges[nr_ranges++] = range;
+	}
+
+	/* Nothing added means nothing to remove. */
+	if (!nr_ranges) {
+		kfree(ranges);
+		return 0;
+	}
+
+	rc = offline_and_remove_memory_ranges(ranges, nr_ranges);
+	kfree(ranges);
+	if (rc) {
+		/* Recoverable: the ranges rolled back, nothing is leaked yet. */
+		dev_err(dev, "hotremove failed, device left online: %d\n", rc);
+		return rc;
+	}
+
+	/* All ranges removed; release the reserved resources. */
+	for (i = 0; i < dev_dax->nr_range; i++) {
+		if (!data->res[i])
+			continue;
+		remove_resource(data->res[i]);
+		kfree(data->res[i]);
+		data->res[i] = NULL;
+	}
+
+	return 0;
+}
+#else
+static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,
+				 struct dax_kmem_data *data)
+{
+	return -EBUSY;
+}
+#endif /* CONFIG_MEMORY_HOTREMOVE */
+
+/**
+ * dax_kmem_cleanup_resources - remove the dax memory resources
+ * @dev_dax: the dev_dax instance
+ * @data: the dax_kmem_data structure with resource tracking
+ *
+ * Removes all resources in the dev_dax region.
+ */
+static void dax_kmem_cleanup_resources(struct dev_dax *dev_dax,
+				       struct dax_kmem_data *data)
+{
+	int i;
+
+	/*
+	 * If the device unbind occurs before memory is hotremoved, we can never
+	 * remove the memory (requires reboot).  Attempting an offline operation
+	 * here may cause deadlock and a failure to finish the unbind.
+	 *
+	 * Note: This leaks the resources.
+	 */
+	if (WARN(((data->state != DAX_KMEM_UNPLUGGED) &&
+		  (data->state != MMOP_OFFLINE)),
+		 "Hotplug memory regions stuck online until reboot"))
+		return;
+
+	for (i = 0; i < dev_dax->nr_range; i++) {
+		if (!data->res[i])
+			continue;
+		remove_resource(data->res[i]);
+		kfree(data->res[i]);
+		data->res[i] = NULL;
+	}
+}
+
+static int dax_kmem_parse_state(const char *buf)
+{
+	int online_type;
+
+	/* "unplugged" is kmem-specific - the rest map to MMOP_ */
+	if (sysfs_streq(buf, "unplugged"))
+		return DAX_KMEM_UNPLUGGED;
+
+	online_type = mhp_online_type_from_str(buf);
+	/* Disallow "offline": it's not useful and creates race conditions */
+	if (online_type == MMOP_OFFLINE)
+		return -EINVAL;
+	return online_type;
+}
+
+static ssize_t state_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct dax_kmem_data *data = dev_get_drvdata(dev);
+	const char *state_str;
+
+	if (!data)
+		return -ENXIO;
+
+	if (data->state == DAX_KMEM_UNPLUGGED)
+		state_str = "unplugged";
+	else
+		state_str = mhp_online_type_to_str(data->state);
+
+	return sysfs_emit(buf, "%s\n", state_str ?: "unknown");
+}
+
+static ssize_t state_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct dev_dax *dev_dax = to_dev_dax(dev);
+	struct dax_kmem_data *data = dev_get_drvdata(dev);
+	int online_type;
+	int rc;
+
+	if (!data)
+		return -ENXIO;
+
+	online_type = dax_kmem_parse_state(buf);
+	if (online_type < DAX_KMEM_UNPLUGGED)
+		return online_type;
+
+	guard(mutex)(&data->lock);
+
+	/* Already in requested state */
+	if (data->state == online_type)
+		return len;
+
+	if (online_type == DAX_KMEM_UNPLUGGED) {
+		rc = dax_kmem_do_hotremove(dev_dax, data);
+		if (rc)
+			return rc;
+		data->state = DAX_KMEM_UNPLUGGED;
+		return len;
+	}
+
+	/* Onlining is only allowed from the unplugged state. */
+	if (data->state != DAX_KMEM_UNPLUGGED)
+		return -EBUSY;
+
+	/* Re-acquire resources if previously unplugged, otherwise no-op */
+	rc = dax_kmem_init_resources(dev_dax, data);
+	if (rc < 0)
+		return rc;
+
+	rc = dax_kmem_do_hotplug(dev_dax, data, online_type);
+	if (rc < 0)
+		return rc;
+
+	data->state = online_type;
+	return len;
+}
+static DEVICE_ATTR_RW(state);
+
 static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
 {
 	struct device *dev = &dev_dax->dev;
 	unsigned long total_len = 0, orig_len = 0;
 	struct dax_kmem_data *data;
 	struct memory_dev_type *mtype;
-	int i, rc, mapped = 0;
-	mhp_t mhp_flags;
+	int i, rc;
 	int numa_node;
+	int online_type;
 	int adist = MEMTIER_DEFAULT_DAX_ADISTANCE;
 
 	/*
@@ -133,68 +456,38 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
 	if (rc < 0)
 		goto err_reg_mgid;
 	data->mgid = rc;
+	data->numa_node = numa_node;
+	data->dev_dax = dev_dax;
+	data->state = DAX_KMEM_UNPLUGGED;
+	mutex_init(&data->lock);
 
-	for (i = 0; i < dev_dax->nr_range; i++) {
-		struct resource *res;
-		struct range range;
-
-		rc = dax_kmem_range(dev_dax, i, &range);
-		if (rc)
-			continue;
-
-		/* Region is permanently reserved if hotremove fails. */
-		res = request_mem_region(range.start, range_len(&range), data->res_name);
-		if (!res) {
-			dev_warn(dev, "mapping%d: %#llx-%#llx could not reserve region\n",
-					i, range.start, range.end);
-			/*
-			 * Once some memory has been onlined we can't
-			 * assume that it can be un-onlined safely.
-			 */
-			if (mapped)
-				continue;
-			rc = -EBUSY;
-			goto err_request_mem;
-		}
-		data->res[i] = res;
-
-		/*
-		 * Set flags appropriate for System RAM.  Leave ..._BUSY clear
-		 * so that add_memory() can add a child resource.  Do not
-		 * inherit flags from the parent since it may set new flags
-		 * unknown to us that will break add_memory() below.
-		 */
-		res->flags = IORESOURCE_SYSTEM_RAM;
+	dev_set_drvdata(dev, data);
 
-		mhp_flags = MHP_NID_IS_MGID;
-		if (dev_dax->memmap_on_memory)
-			mhp_flags |= MHP_MEMMAP_ON_MEMORY;
+	rc = dax_kmem_init_resources(dev_dax, data);
+	if (rc < 0)
+		goto err_resources;
 
-		/*
-		 * Ensure that future kexec'd kernels will not treat
-		 * this as RAM automatically.
-		 */
-		rc = add_memory_driver_managed(data->mgid, range.start,
-				range_len(&range), kmem_name, mhp_flags);
+	/* Resolve system default at bind time in case it changed */
+	online_type = dev_dax->online_type;
+	if (online_type == DAX_ONLINE_DEFAULT)
+		online_type = mhp_get_default_online_type();
 
-		if (rc) {
-			dev_warn(dev, "mapping%d: %#llx-%#llx memory add failed\n",
-					i, range.start, range.end);
-			remove_resource(res);
-			kfree(res);
-			data->res[i] = NULL;
-			if (mapped)
-				continue;
-			goto err_request_mem;
-		}
-		mapped++;
-	}
+	/* Always create blocks for backward compatibility, even if offline */
+	rc = dax_kmem_do_hotplug(dev_dax, data, online_type);
+	if (rc < 0)
+		goto err_hotplug;
+	data->state = online_type;
 
-	dev_set_drvdata(dev, data);
+	rc = device_create_file(dev, &dev_attr_state);
+	if (rc)
+		dev_warn(dev, "failed to create state sysfs entry\n");
 
 	return 0;
 
-err_request_mem:
+err_hotplug:
+	dax_kmem_cleanup_resources(dev_dax, data);
+err_resources:
+	dev_set_drvdata(dev, NULL);
 	memory_group_unregister(data->mgid);
 err_reg_mgid:
 	kfree(data->res_name);
@@ -206,65 +499,86 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
 }
 
 #ifdef CONFIG_MEMORY_HOTREMOVE
-static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
+/*
+ * Remove the device's added ranges with remove_memory().
+ * Unlike the sysfs unplug path it never offlines and fails if the blocks are
+ * online (-EBUSY), so it is safe from unbind. Failures leak until reboot.
+ *
+ * Returns 0 only if every added range was removed.
+ */
+static int dax_kmem_remove_ranges(struct dev_dax *dev_dax,
+				  struct dax_kmem_data *data)
 {
-	int i, success = 0;
-	int node = dev_dax->target_node;
 	struct device *dev = &dev_dax->dev;
-	struct dax_kmem_data *data = dev_get_drvdata(dev);
+	int i, rc = 0;
 
-	/*
-	 * We have one shot for removing memory, if some memory blocks were not
-	 * offline prior to calling this function remove_memory() will fail, and
-	 * there is no way to hotremove this memory until reboot because device
-	 * unbind will succeed even if we return failure.
-	 */
 	for (i = 0; i < dev_dax->nr_range; i++) {
 		struct range range;
-		int rc;
 
-		rc = dax_kmem_range(dev_dax, i, &range);
-		if (rc)
+		if (!data->res[i] || dax_kmem_range(dev_dax, i, &range))
 			continue;
-
-		/* range was never added during probe */
-		if (!data->res[i]) {
-			success++;
+		if (remove_memory(range.start, range_len(&range))) {
+			dev_warn(dev, "mapping%d: %#llx-%#llx stuck online until reboot\n",
+				 i, range.start, range.end);
+			rc = -EBUSY;
 			continue;
 		}
+		remove_resource(data->res[i]);
+		kfree(data->res[i]);
+		data->res[i] = NULL;
+	}
+	return rc;
+}
 
-		rc = remove_memory(range.start, range_len(&range));
-		if (rc == 0) {
-			remove_resource(data->res[i]);
-			kfree(data->res[i]);
-			data->res[i] = NULL;
-			success++;
-			continue;
-		}
+static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
+{
+	int node = dev_dax->target_node;
+	struct device *dev = &dev_dax->dev;
+	struct dax_kmem_data *data = dev_get_drvdata(dev);
+
+	device_remove_file(dev, &dev_attr_state);
+	/*
+	 * If UNPLUGGED: state is known clean and reboot can clean up.
+	 *
+	 * If ONLINE_*: memory cannot be removed here: offlining during an
+	 * uninterruptible unbind can deadlock. Leak the resources until reboot.
+	 *
+	 * If OFFLINE: blocks are attempted to remove with remove_memory(),
+	 * which never attempts offlining. A block onlined behind our back
+	 * fails -EBUSY and is leaked.
+	 */
+	if (dax_kmem_state_is_online(data->state)) {
+		dev_warn(dev, "Hotplug regions stuck online until reboot\n");
+		any_hotremove_failed = true;
+		return;
+	} else if (data->state == MMOP_OFFLINE &&
+		   dax_kmem_remove_ranges(dev_dax, data)) {
 		any_hotremove_failed = true;
-		dev_err(dev,
-			"mapping%d: %#llx-%#llx cannot be hotremoved until the next reboot\n",
-				i, range.start, range.end);
+		dev_warn(dev, "Unplug failed, resources leaked until reboot\n");
+		return;
 	}
 
-	if (success >= dev_dax->nr_range) {
-		memory_group_unregister(data->mgid);
-		kfree(data->res_name);
-		kfree(data);
-		dev_set_drvdata(dev, NULL);
-		/*
-		 * Clear the memtype association on successful unplug.
-		 * If not, we have memory blocks left which can be
-		 * offlined/onlined later. We need to keep memory_dev_type
-		 * for that. This implies this reference will be around
-		 * till next reboot.
-		 */
-		clear_node_memory_type(node, NULL);
-	}
+	dax_kmem_cleanup_resources(dev_dax, data);
+	memory_group_unregister(data->mgid);
+	kfree(data->res_name);
+	kfree(data);
+	dev_set_drvdata(dev, NULL);
+	/*
+	 * Clear the memtype association on successful unplug.
+	 * If not, we have memory blocks left which can be
+	 * offlined/onlined later. We need to keep memory_dev_type
+	 * for that. This implies this reference will be around
+	 * till next reboot.
+	 */
+	clear_node_memory_type(node, NULL);
 }
 #else
 static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
 {
+	struct device *dev = &dev_dax->dev;
+
+	device_remove_file(dev, &dev_attr_state);
+
 	/*
 	 * Without hotremove purposely leak the request_mem_region() for the
 	 * device-dax range and return '0' to ->remove() attempts. The removal
diff --git a/drivers/dax/pmem.c b/drivers/dax/pmem.c
index bee93066a84977..e7adace6919506 100644
--- a/drivers/dax/pmem.c
+++ b/drivers/dax/pmem.c
@@ -63,6 +63,7 @@ static struct dev_dax *__dax_pmem_probe(struct device *dev)
 		.pgmap = &pgmap,
 		.size = range_len(&range),
 		.memmap_on_memory = false,
+		.online_type = DAX_ONLINE_DEFAULT,
 	};
 
 	return devm_create_dev_dax(&data);
diff --git a/include/linux/memory.h b/include/linux/memory.h
index 463dc02f6cff05..1783299073e479 100644
--- a/include/linux/memory.h
+++ b/include/linux/memory.h
@@ -20,6 +20,7 @@
 #include <linux/compiler.h>
 #include <linux/mutex.h>
 #include <linux/memory_hotplug.h>
+#include <linux/range.h>
 
 #define MIN_MEMORY_BLOCK_SIZE     (1UL << SECTION_SIZE_BITS)
 
@@ -100,6 +101,32 @@ int arch_get_memory_phys_device(unsigned long start_pfn);
 unsigned long memory_block_size_bytes(void);
 int set_memory_block_size_order(unsigned int order);
 
+/**
+ * memory_block_aligned_range - align a physical address range to memory blocks
+ * @range: the input range to align
+ *
+ * Aligns the start address up and the end address down to memory block
+ * boundaries. This is required for memory hotplug operations which must
+ * operate on memory-block aligned ranges.
+ *
+ * Returns the aligned range. Callers should check that the returned
+ * range is valid (aligned.start < aligned.end) before using it.
+ */
+static inline struct range memory_block_aligned_range(const struct range *range)
+{
+	struct range aligned;
+
+	aligned.start = ALIGN(range->start, memory_block_size_bytes());
+	aligned.end = ALIGN_DOWN(range->end + 1, memory_block_size_bytes());
+	/* No whole block fits (e.g. range below the first boundary): empty. */
+	if (aligned.end <= aligned.start)
+		aligned.start = aligned.end;
+	else
+		aligned.end -= 1;
+
+	return aligned;
+}
+
 struct memory_notify {
 	unsigned long start_pfn;
 	unsigned long nr_pages;
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h
index 7c9d66729c6095..db10d50f30ae1b 100644
--- a/include/linux/memory_hotplug.h
+++ b/include/linux/memory_hotplug.h
@@ -127,6 +127,7 @@ extern int arch_add_memory(int nid, u64 start, u64 size,
 extern u64 max_mem_size;
 
 extern int mhp_online_type_from_str(const char *str);
+const char *mhp_online_type_to_str(int online_type);
 
 /* If movable_node boot option specified */
 extern bool movable_node_enabled;
@@ -267,6 +268,8 @@ extern int offline_pages(unsigned long start_pfn, unsigned long nr_pages,
 extern int remove_memory(u64 start, u64 size);
 extern void __remove_memory(u64 start, u64 size);
 extern int offline_and_remove_memory(u64 start, u64 size);
+int offline_and_remove_memory_ranges(const struct range *ranges,
+		unsigned int nr_ranges);
 
 #else
 static inline void try_offline_node(int nid) {}
@@ -283,6 +286,12 @@ static inline int remove_memory(u64 start, u64 size)
 }
 
 static inline void __remove_memory(u64 start, u64 size) {}
+
+static inline int offline_and_remove_memory_ranges(const struct range *ranges,
+		unsigned int nr_ranges)
+{
+	return -EBUSY;
+}
 #endif /* CONFIG_MEMORY_HOTREMOVE */
 
 #ifdef CONFIG_MEMORY_HOTPLUG
@@ -294,6 +303,9 @@ extern int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags);
 extern int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags);
 extern int add_memory_resource(int nid, struct resource *resource,
 			       mhp_t mhp_flags);
+int __add_memory_driver_managed(int nid, u64 start, u64 size,
+				const char *resource_name, mhp_t mhp_flags,
+				enum mmop online_type);
 extern int add_memory_driver_managed(int nid, u64 start, u64 size,
 				     const char *resource_name,
 				     mhp_t mhp_flags);
@@ -316,6 +328,8 @@ extern struct zone *zone_for_pfn_range(enum mmop online_type,
 extern int arch_create_linear_mapping(int nid, u64 start, u64 size,
 				      struct mhp_params *params);
 void arch_remove_linear_mapping(u64 start, u64 size);
+#else
+static inline enum mmop mhp_get_default_online_type(void) { return MMOP_OFFLINE; }
 #endif /* CONFIG_MEMORY_HOTPLUG */
 
 #endif /* __LINUX_MEMORY_HOTPLUG_H */
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 7ac19fab226324..3225364bec2f76 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -239,6 +239,7 @@ enum mmop mhp_get_default_online_type(void)
 
 	return mhp_default_online_type;
 }
+EXPORT_SYMBOL_GPL(mhp_get_default_online_type);
 
 void mhp_set_default_online_type(enum mmop online_type)
 {
@@ -1337,7 +1338,9 @@ static int check_hotplug_memory_range(u64 start, u64 size)
 
 static int online_memory_block(struct memory_block *mem, void *arg)
 {
-	mem->online_type = mhp_get_default_online_type();
+	enum mmop *online_type = arg;
+
+	mem->online_type = *online_type;
 	return device_online(&mem->dev);
 }
 
@@ -1491,7 +1494,8 @@ static int create_altmaps_and_memory_blocks(int nid, struct memory_group *group,
  *
  * we are OK calling __meminit stuff here - we have CONFIG_MEMORY_HOTPLUG
  */
-int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
+static int __add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags,
+				 enum mmop online_type)
 {
 	struct mhp_params params = { .pgprot = pgprot_mhp(PAGE_KERNEL) };
 	enum memblock_flags memblock_flags = MEMBLOCK_NONE;
@@ -1581,8 +1585,9 @@ int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
 		merge_system_ram_resource(res);
 
 	/* online pages if requested */
-	if (mhp_get_default_online_type() != MMOP_OFFLINE)
-		walk_memory_blocks(start, size, NULL, online_memory_block);
+	if (online_type != MMOP_OFFLINE)
+		walk_memory_blocks(start, size, &online_type,
+				   online_memory_block);
 
 	return ret;
 error:
@@ -1598,7 +1603,13 @@ int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
 	return ret;
 }
 
-/* requires device_hotplug_lock, see add_memory_resource() */
+int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
+{
+	return __add_memory_resource(nid, res, mhp_flags,
+				     mhp_get_default_online_type());
+}
+
+/* requires device_hotplug_lock, see __add_memory_resource() */
 int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags)
 {
 	struct resource *res;
@@ -1626,7 +1637,15 @@ int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags)
 }
 EXPORT_SYMBOL_GPL(add_memory);
 
-/*
+/**
+ * __add_memory_driver_managed - add driver-managed memory with explicit online_type
+ * @nid: NUMA node ID where the memory will be added
+ * @start: Start physical address of the memory range
+ * @size: Size of the memory range in bytes
+ * @resource_name: Resource name in format "System RAM ($DRIVER)"
+ * @mhp_flags: Memory hotplug flags
+ * @online_type: Auto-Online behavior (offline, online, kernel, movable)
+ *
  * Add special, driver-managed memory to the system as system RAM. Such
  * memory is not exposed via the raw firmware-provided memmap as system
  * RAM, instead, it is detected and added by a driver - during cold boot,
@@ -1634,6 +1653,7 @@ EXPORT_SYMBOL_GPL(add_memory);
  *
  * Reasons why this memory should not be used for the initial memmap of a
  * kexec kernel or for placing kexec images:
+ *
  * - The booting kernel is in charge of determining how this memory will be
  *   used (e.g., use persistent memory as system RAM)
  * - Coordination with a hypervisor is required before this memory
@@ -1646,9 +1666,12 @@ EXPORT_SYMBOL_GPL(add_memory);
  *
  * The resource_name (visible via /proc/iomem) has to have the format
  * "System RAM ($DRIVER)".
+ *
+ * Return: 0 on success, negative error code on failure.
  */
-int add_memory_driver_managed(int nid, u64 start, u64 size,
-			      const char *resource_name, mhp_t mhp_flags)
+int __add_memory_driver_managed(int nid, u64 start, u64 size,
+		const char *resource_name, mhp_t mhp_flags,
+		enum mmop online_type)
 {
 	struct resource *res;
 	int rc;
@@ -1658,6 +1681,9 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,
 	    resource_name[strlen(resource_name) - 1] != ')')
 		return -EINVAL;
 
+	if (online_type < MMOP_OFFLINE || online_type > MMOP_ONLINE_MOVABLE)
+		return -EINVAL;
+
 	lock_device_hotplug();
 
 	res = register_memory_resource(start, size, resource_name);
@@ -1666,7 +1692,7 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,
 		goto out_unlock;
 	}
 
-	rc = add_memory_resource(nid, res, mhp_flags);
+	rc = __add_memory_resource(nid, res, mhp_flags, online_type);
 	if (rc < 0)
 		release_memory_resource(res);
 
@@ -1674,6 +1700,30 @@ int add_memory_driver_managed(int nid, u64 start, u64 size,
 	unlock_device_hotplug();
 	return rc;
 }
+EXPORT_SYMBOL_FOR_MODULES(__add_memory_driver_managed, "kmem");
+
+/**
+ * add_memory_driver_managed - add driver-managed memory
+ * @nid: NUMA node ID where the memory will be added
+ * @start: Start physical address of the memory range
+ * @size: Size of the memory range in bytes
+ * @resource_name: Resource name in format "System RAM ($DRIVER)"
+ * @mhp_flags: Memory hotplug flags
+ *
+ * Add driver-managed memory with the system default online type set by
+ * build config or kernel boot parameter.
+ *
+ * See __add_memory_driver_managed for more details.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int add_memory_driver_managed(int nid, u64 start, u64 size,
+			      const char *resource_name, mhp_t mhp_flags)
+{
+	return __add_memory_driver_managed(nid, start, size, resource_name,
+			mhp_flags,
+			mhp_get_default_online_type());
+}
 EXPORT_SYMBOL_GPL(add_memory_driver_managed);
 
 /*
@@ -2379,58 +2429,95 @@ static int try_reonline_memory_block(struct memory_block *mem, void *arg)
  */
 int offline_and_remove_memory(u64 start, u64 size)
 {
-	const unsigned long mb_count = size / memory_block_size_bytes();
+	struct range range = {
+		.start = start,
+		.end = start + size - 1,
+	};
+
+	return offline_and_remove_memory_ranges(&range, 1);
+}
+EXPORT_SYMBOL_GPL(offline_and_remove_memory);
+
+/**
+ * offline_and_remove_memory_ranges - offline and remove multiple memory ranges
+ * @ranges: array of physical address ranges to offline and remove
+ * @nr_ranges: number of entries in @ranges
+ *
+ * Offline and remove several memory ranges as one operation, serialized
+ * against other hotplug operations by a single lock_device_hotplug().
+ *
+ * This offlines all ranges before removing any of them.  If offlining any
+ * range fails, the entire process is reverted and nothing is removed.
+ * This provides a fully atomic semantic for unplugging an entire device.
+ *
+ * Each range must be memory-block aligned in start and size.
+ *
+ * Return: 0 on success, negative errno otherwise.  On failure no range has
+ * been removed.
+ */
+int offline_and_remove_memory_ranges(const struct range *ranges,
+		unsigned int nr_ranges)
+{
+	unsigned long mb_count = 0;
 	uint8_t *online_types, *tmp;
-	int rc;
+	unsigned int i;
+	int rc = 0;
 
-	if (!IS_ALIGNED(start, memory_block_size_bytes()) ||
-	    !IS_ALIGNED(size, memory_block_size_bytes()) || !size)
+	if (!ranges || !nr_ranges)
 		return -EINVAL;
 
+	for (i = 0; i < nr_ranges; i++) {
+		const u64 start = ranges[i].start;
+		const u64 size = range_len(&ranges[i]);
+
+		if (!IS_ALIGNED(start, memory_block_size_bytes()) ||
+		    !IS_ALIGNED(size, memory_block_size_bytes()) || !size)
+			return -EINVAL;
+		mb_count += size / memory_block_size_bytes();
+	}
+
 	/*
-	 * We'll remember the old online type of each memory block, so we can
-	 * try to revert whatever we did when offlining one memory block fails
-	 * after offlining some others succeeded.
+	 * Remember the old online type of every memory block across all ranges,
+	 * so we can revert if offlining a later block fails.  All entries start
+	 * as MMOP_OFFLINE so blocks we never touched are skipped on rollback.
 	 */
 	online_types = kmalloc_array(mb_count, sizeof(*online_types),
 				     GFP_KERNEL);
 	if (!online_types)
 		return -ENOMEM;
-	/*
-	 * Initialize all states to MMOP_OFFLINE, so when we abort processing in
-	 * try_offline_memory_block(), we'll skip all unprocessed blocks in
-	 * try_reonline_memory_block().
-	 */
 	memset(online_types, MMOP_OFFLINE, mb_count);
 
 	lock_device_hotplug();
 
+	/* Phase 1: offline every block in every range. */
 	tmp = online_types;
-	rc = walk_memory_blocks(start, size, &tmp, try_offline_memory_block);
-
-	/*
-	 * In case we succeeded to offline all memory, remove it.
-	 * This cannot fail as it cannot get onlined in the meantime.
-	 */
-	if (!rc) {
-		rc = try_remove_memory(start, size);
+	for (i = 0; i < nr_ranges; i++) {
+		rc = walk_memory_blocks(ranges[i].start, range_len(&ranges[i]),
+					&tmp, try_offline_memory_block);
 		if (rc)
-			pr_err("%s: Failed to remove memory: %d", __func__, rc);
+			break;
 	}
 
-	/*
-	 * Rollback what we did. While memory onlining might theoretically fail
-	 * (nacked by a notifier), it barely ever happens.
-	 */
+	/* If any failure occurred at all, rollback any changes and bail */
 	if (rc) {
 		tmp = online_types;
-		walk_memory_blocks(start, size, &tmp,
-				   try_reonline_memory_block);
+		for (i = 0; i < nr_ranges; i++)
+			walk_memory_blocks(ranges[i].start,
+					   range_len(&ranges[i]), &tmp,
+					   try_reonline_memory_block);
+		goto out_unlock;
 	}
+
+	/* Phase 2: Remove. This should never fail holding the hotplug lock */
+	for (i = 0; i < nr_ranges; i++)
+		WARN_ON_ONCE(try_remove_memory(ranges[i].start,
+					       range_len(&ranges[i])));
+
+out_unlock:
 	unlock_device_hotplug();
 
 	kfree(online_types);
 	return rc;
 }
-EXPORT_SYMBOL_GPL(offline_and_remove_memory);
+EXPORT_SYMBOL_GPL(offline_and_remove_memory_ranges);
 #endif /* CONFIG_MEMORY_HOTREMOVE */
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 8d4db2241cc2a2..5528682a3a9122 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -14,6 +14,7 @@ TARGETS += core
 TARGETS += cpufreq
 TARGETS += cpu-hotplug
 TARGETS += damon
+TARGETS += dax
 TARGETS += devices/error_logs
 TARGETS += devices/probe
 TARGETS += dmabuf-heaps
diff --git a/tools/testing/selftests/dax/Makefile b/tools/testing/selftests/dax/Makefile
new file mode 100644
index 00000000000000..25a4f3d73a5baa
--- /dev/null
+++ b/tools/testing/selftests/dax/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+all:
+
+TEST_PROGS := dax-kmem-hotplug.sh
+
+include ../lib.mk
diff --git a/tools/testing/selftests/dax/config b/tools/testing/selftests/dax/config
new file mode 100644
index 00000000000000..4c9aaeb6ceb41a
--- /dev/null
+++ b/tools/testing/selftests/dax/config
@@ -0,0 +1,4 @@
+CONFIG_DEV_DAX=m
+CONFIG_DEV_DAX_KMEM=m
+CONFIG_MEMORY_HOTPLUG=y
+CONFIG_MEMORY_HOTREMOVE=y
diff --git a/tools/testing/selftests/dax/dax-kmem-hotplug.sh b/tools/testing/selftests/dax/dax-kmem-hotplug.sh
new file mode 100755
index 00000000000000..c8bbaf6178ed9c
--- /dev/null
+++ b/tools/testing/selftests/dax/dax-kmem-hotplug.sh
@@ -0,0 +1,190 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Exercise the dax/kmem "state" sysfs attribute:
+#   /sys/bus/dax/devices/daxX.Y/state  ->  unplugged | online | online_kernel | online_movable
+#
+# The test needs a dax device already bound to the kmem driver.
+# If no suitable device is found the tests SKIP.
+#
+# A dax device can be provisioned with the memmap= boot param, e.g.:
+#   memmap=2G!4G
+#
+# then, in the booted system:
+#
+#   ndctl create-namespace -m devdax -e namespace0.0 -f
+#   daxctl reconfigure-device -N -m system-ram dax0.0   # bind kmem
+#   ./dax-kmem-hotplug.sh
+
+# shellcheck disable=SC1091
+DIR="$(dirname "$(readlink -f "$0")")"
+. "$DIR"/../kselftest/ktap_helpers.sh
+
+DAX_BASE=/sys/bus/dax/devices
+
+memtotal_kb() { awk '/^MemTotal:/ {print $2}' /proc/meminfo; }
+get_state() { cat "$HP" 2>/dev/null; }
+# set_state STATE -- write a state to the state attribute; returns the
+# write's exit status (0 = accepted by the kernel)
+set_state() { echo "$1" > "$HP" 2>/dev/null; }
+
+find_kmem_dax() {
+	local d drv
+	for d in "$DAX_BASE"/dax*; do
+		[ -e "$d/state" ] || continue
+		drv=$(readlink "$d/driver" 2>/dev/null)
+		[ "$(basename "${drv:-}")" = kmem ] || continue
+		basename "$d"
+		return 0
+	done
+	return 1
+}
+
+ktap_print_header
+
+if [ "$UID" != 0 ]; then
+	ktap_skip_all "must be run as root"
+	exit "$KSFT_SKIP"
+fi
+
+DAX=$(find_kmem_dax)
+if [ -z "$DAX" ]; then
+	ktap_skip_all "no kmem-bound dax device with a state attribute"
+	exit "$KSFT_SKIP"
+fi
+HP=$DAX_BASE/$DAX/state
+ORIG=$(get_state)
+
+# A failure to reach the baseline is environmental (memory in use), not an
+# interface failure, so skip rather than fail.
+set_state unplugged; rc=$?
+if [ "$rc" != 0 ] || [ "$(get_state)" != unplugged ]; then
+	ktap_skip_all "$DAX: cannot reach 'unplugged' baseline (memory in use?)"
+	[ -n "$ORIG" ] && set_state "$ORIG"
+	exit "$KSFT_SKIP"
+fi
+mt_unplugged=$(memtotal_kb)
+
+DRV=/sys/bus/dax/drivers/kmem
+AOB=/sys/devices/system/memory/auto_online_blocks
+
+ktap_print_msg "using $DAX (initial state was: $ORIG)"
+ktap_set_plan 8
+
+# A public (N_MEMORY) kmem node onlined into a kernel zone (online/online_kernel)
+# collects unmovable allocations and can then never be offlined, which would
+# wedge the device for the rest of this test.  So this test only ever
+# successfully onlines online_movable, the one mode that is reliably unpluggable.
+
+set_state online_movable; rc=$?
+mt_online=$(memtotal_kb)
+if [ "$rc" = 0 ] && [ "$(get_state)" = online_movable ] && [ "$mt_online" -gt "$mt_unplugged" ]; then
+	ktap_test_pass "online_movable: state=online_movable, MemTotal $mt_unplugged -> $mt_online kB"
+else
+	ktap_test_fail "online_movable: rc=$rc state=$(get_state) MemTotal $mt_unplugged -> $mt_online"
+fi
+
+set_state online_movable; rc=$?
+if [ "$rc" = 0 ] && [ "$(get_state)" = online_movable ]; then
+	ktap_test_pass "online_movable idempotent"
+else
+	ktap_test_fail "online_movable idempotent: rc=$rc state=$(get_state)"
+fi
+
+# A different online type is rejected without an intervening unplug.  The write
+# is refused before any hotplug, so this never actually onlines a kernel zone.
+set_state online_kernel; rc=$?
+if [ "$rc" != 0 ] && [ "$(get_state)" = online_movable ]; then
+	ktap_test_pass "reject online_kernel without intervening unplug (no kernel-zone online)"
+else
+	ktap_test_fail "online_movable->online_kernel not rejected: rc=$rc state=$(get_state)"
+fi
+
+set_state unplugged; rc=$?
+mt=$(memtotal_kb)
+if [ "$rc" = 0 ] && [ "$(get_state)" = unplugged ] && [ "$mt" -lt "$mt_online" ]; then
+	ktap_test_pass "unplug from online_movable: MemTotal $mt_online -> $mt kB"
+else
+	ktap_test_fail "unplug from online_movable: rc=$rc state=$(get_state) MemTotal $mt_online -> $mt"
+fi
+
+before=$(get_state)
+set_state bogus_state; rc=$?
+if [ "$rc" != 0 ] && [ "$(get_state)" = "$before" ]; then
+	ktap_test_pass "reject invalid state string"
+else
+	ktap_test_fail "invalid state not rejected: rc=$rc state=$(get_state)"
+fi
+
+# The online_movable -> unplug cycle once regressed: a re-online failed to
+# re-reserve the per-range resources, so a later unplug reported success while
+# leaving the memory online.  Assert each iteration really adds and frees memory.
+set_state unplugged
+cycle_ok=1; fail_i=0; on=0; off=0
+for i in 1 2 3; do
+	if ! set_state online_movable; then cycle_ok=0; fail_i=$i; break; fi
+	on=$(memtotal_kb)
+	if ! set_state unplugged; then cycle_ok=0; fail_i=$i; break; fi
+	off=$(memtotal_kb)
+	if [ "$on" -le "$mt_unplugged" ] || [ "$off" -ge "$on" ]; then
+		cycle_ok=0; fail_i=$i; break
+	fi
+done
+if [ "$cycle_ok" = 1 ]; then
+	ktap_test_pass "online_movable/unplug cycle re-acquires resources (3x: added and freed each time)"
+else
+	ktap_test_fail "online_movable/unplug cycle regressed at iteration $fail_i (on=$on off=$off baseline=$mt_unplugged)"
+fi
+
+# change system default online policy while the device is unbound, and show
+# the new system default policy is utilized across bindings.
+set_state unplugged
+if [ -w "$AOB" ] && [ -w "$DRV/unbind" ] && [ -w "$DRV/bind" ]; then
+	orig_aob=$(cat "$AOB")
+	echo "$DAX" > "$DRV/unbind" 2>/dev/null
+	echo offline > "$AOB" 2>/dev/null
+	echo "$DAX" > "$DRV/bind" 2>/dev/null
+	sleep 1
+	st=$(get_state)
+	echo "$orig_aob" > "$AOB" 2>/dev/null		# restore system policy
+	if [ "$st" = offline ]; then
+		ktap_test_pass "online policy resolved at bind: auto_online_blocks=offline -> state=offline"
+	else
+		ktap_test_fail "bind-time policy not honored: state=$st (expected offline)"
+	fi
+	set_state unplugged 2>/dev/null
+else
+	ktap_test_skip "auto_online_blocks or driver bind/unbind not writable"
+fi
+
+[ -n "$ORIG" ] && set_state "$ORIG"
+
+# DESTRUCTIVE: unbinding the driver while memory is online causes the resources
+# to leak - but the unbind should not deadlock.  Instead the driver leaks it
+# with a single "stuck online" warning. This leaves the memory online and the
+# device unbound until reboot, so it runs last - and only if we can run it,
+# leaving the restored state above untouched otherwise.  online_movable only:
+# this test never onlines a public node into a kernel zone.
+if [ -w "$DRV/unbind" ]; then
+	set_state unplugged; set_state online_movable
+fi
+if [ "$(get_state)" = online_movable ] && [ -w "$DRV/unbind" ]; then
+	mt_on=$(memtotal_kb)
+	dmesg -C 2>/dev/null
+	echo "$DAX" > "$DRV/unbind" 2>/dev/null
+	mt_after=$(memtotal_kb)
+	# The leaked "System RAM (kmem)" regions stay in the iomem tree; reading
+	# their names dereferences res_name, which a buggy unbind already freed.
+	# Walk /proc/iomem to provoke that use-after-free (caught by KASAN).
+	cat /proc/iomem > /dev/null 2>&1
+	splat=$(dmesg 2>/dev/null | grep -ciE "KASAN|BUG:|use-after-free|general protection|Oops|refcount_t")
+	if [ "$splat" = 0 ] && [ "$mt_after" -ge "$mt_on" ]; then
+		ktap_test_pass "unbind while online: memory left online, no UAF/oops (MemTotal $mt_on -> $mt_after kB)"
+	else
+		ktap_test_fail "unbind while online regressed: splat=$splat MemTotal $mt_on -> $mt_after kB"
+	fi
+else
+	ktap_test_skip "could not online device for unbind-while-online test"
+fi
+
+ktap_finished
diff --git a/tools/testing/selftests/dax/settings b/tools/testing/selftests/dax/settings
new file mode 100644
index 00000000000000..ba4d85f74cd6b9
--- /dev/null
+++ b/tools/testing/selftests/dax/settings
@@ -0,0 +1 @@
+timeout=90

3/2 2026/07/01 04:28 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19003
							output: 32
							thoughts: 385
						

Thoughts:
**Analyzing a Hotplug Patch for DAX Kmem Devices**

Okay, so I've got this patch in front of me that's tweaking the memory hotplug and DAX kmem drivers. The meat of it seems to be adding a new sysfs attribute, specifically `/sys/bus/dax/devices/daxX.Y/state`, for DAX kmem devices. That's immediately interesting from a fuzzing perspective, as sysfs attributes are often entry points for syzkaller. There's also a new selftest script, `tools/testing/selftests/dax/dax-kmem-hotplug.sh`, which is always a good sign.

The patch itself modifies `drivers/dax/kmem.c` to handle the memory hotplug state transitions. I see it also adds `offline_and_remove_memory_ranges` to `mm/memory_hotplug.c` and tweaks `add_memory_driver_managed` to accept `online_type`. My initial thought is: *Is this even worth fuzzing? Can syzkaller reach it?*

My first step is figuring out how syzkaller can access this new functionality. The crucial bit here is the sysfs attribute, `state`. Syzkaller needs to know the path: `/sys/bus/dax/devices/daxX.Y/state`.  The core driver, according to the patch, is located in `drivers/dax/kmem.c`. So the question is, *does syzkaller have the right configurations enabled to even *see* this?*

I'll quickly check the config requirements.  I need to make sure that `CONFIG_DEV_DAX_KMEM`, `CONFIG_MEMORY_HOTPLUG`, and `CONFIG_MEMORY_HOTREMOVE` are all enabled in syzkaller.

Now, let's determine the reachability of the sysfs attribute. I should look directly in `drivers/dax/kmem.c`. I'm going to look for `state_store` and `state_show` - those are the functions that handle setting and getting the value, respectively. Also, I'll search for calls to `dax_kmem_do_hotplug` and `dax_kmem_do_hotremove` since those are likely where the hotplug/hotremove logic will be happening. I know the patch will be making calls to these functions in the driver code.

Okay, I'll dig into `drivers/dax/kmem.c` and start with `state_store`.



4/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "state_store",
  "PathPrefix": "drivers/dax/kmem.c"
}

Results:
{
  "Output": "drivers/dax/kmem.c=327=static ssize_t state_show(struct device *dev,\n--\ndrivers/dax/kmem.c-343-\ndrivers/dax/kmem.c:344:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/dax/kmem.c-345-\t\t\t     const char *buf, size_t len)\n"
}

5/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20093
							output: 38
							thoughts: 22
						

Thoughts:
Here's my analysis of the `drivers/dax/kmem.c` file, from a kernel driver perspective:

**Diving into drivers/dax/kmem.c**

Okay, let's get down to brass tacks. I need to understand what's changed within `drivers/dax/kmem.c`. My focus is pinpointing any additions or modifications to the core functions within this file, as these will likely be critical to understanding the evolution of the DAX (Direct Access) functionality related to kernel memory. I'll need to scrutinize any code that impacts memory allocation, deallocation, or management, especially if it relates to persistent memory or other non-volatile memory devices. I'm looking for the introduction of new features, changes to existing algorithms, or even optimizations made to improve performance or stability within the DAX subsystem. I'll be checking any memory management methods, especially those concerning direct access to memory. I will watch out for interactions with the page table or other memory-mapping aspects of the kernel. This is where the real meat of the driver resides, and the changes here will paint a clear picture of how the DAX layer is evolving to utilize persistent memory effectively.



6/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "^static.*dax_kmem_",
  "PathPrefix": "drivers/dax/kmem.c"
}

Results:
{
  "Output": "drivers/dax/kmem.c=29=static bool any_hotremove_failed;\ndrivers/dax/kmem.c-30-\ndrivers/dax/kmem.c:31:static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)\ndrivers/dax/kmem.c-32-{\n--\ndrivers/dax/kmem.c=66=static void kmem_put_memory_types(void)\n--\ndrivers/dax/kmem.c-72-/* True for the online states a kmem dax device can hold. */\ndrivers/dax/kmem.c:73:static bool dax_kmem_state_is_online(int state)\ndrivers/dax/kmem.c-74-{\n--\ndrivers/dax/kmem.c-90- */\ndrivers/dax/kmem.c:91:static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-92-\t\t\t       struct dax_kmem_data *data,\n--\ndrivers/dax/kmem.c-166- */\ndrivers/dax/kmem.c:167:static int dax_kmem_init_resources(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-168-\t\t\t\t   struct dax_kmem_data *data)\n--\ndrivers/dax/kmem.c-223- */\ndrivers/dax/kmem.c:224:static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-225-\t\t\t\t struct dax_kmem_data *data)\n--\ndrivers/dax/kmem.c-271-#else\ndrivers/dax/kmem.c:272:static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-273-\t\t\t\t struct dax_kmem_data *data)\n--\ndrivers/dax/kmem.c-285- */\ndrivers/dax/kmem.c:286:static void dax_kmem_cleanup_resources(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-287-\t\t\t\t       struct dax_kmem_data *data)\n--\ndrivers/dax/kmem.c-311-\ndrivers/dax/kmem.c:312:static int dax_kmem_parse_state(const char *buf)\ndrivers/dax/kmem.c-313-{\n--\ndrivers/dax/kmem.c=389=static DEVICE_ATTR_RW(state);\ndrivers/dax/kmem.c-390-\ndrivers/dax/kmem.c:391:static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\ndrivers/dax/kmem.c-392-{\n--\ndrivers/dax/kmem.c-508- */\ndrivers/dax/kmem.c:509:static int dax_kmem_remove_ranges(struct dev_dax *dev_dax,\ndrivers/dax/kmem.c-510-\t\t\t\t  struct dax_kmem_data *data)\n--\ndrivers/dax/kmem.c-532-\ndrivers/dax/kmem.c:533:static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\ndrivers/dax/kmem.c-534-{\n--\ndrivers/dax/kmem.c-575-#else\ndrivers/dax/kmem.c:576:static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\ndrivers/dax/kmem.c-577-{\n--\ndrivers/dax/kmem.c-592-\ndrivers/dax/kmem.c:593:static struct dax_device_driver device_dax_kmem_driver = {\ndrivers/dax/kmem.c-594-\t.probe = dev_dax_kmem_probe,\n--\ndrivers/dax/kmem.c-598-\ndrivers/dax/kmem.c:599:static int __init dax_kmem_init(void)\ndrivers/dax/kmem.c-600-{\n--\ndrivers/dax/kmem.c-619-\ndrivers/dax/kmem.c:620:static void __exit dax_kmem_exit(void)\ndrivers/dax/kmem.c-621-{\n"
}

7/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21622
							output: 37
							thoughts: 168
						

Thoughts:
**Analyzing the Kernel Patch for Memory Hotplug and DAX kmem Functionality**

Okay, so this patch is definitely introducing some new features to the kernel; mainly focused around memory hotplug and the DAX kmem driver. I can see that it's adding new sysfs attributes and, of course, the critical hotplug and hotremove logic. I need to get into the details of this.

I'm particularly interested in the `state_store`, `state_show`, `dax_kmem_do_hotplug`, and `dax_kmem_do_hotremove` functions. I'll need to understand how those interact with `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, and `dax_kmem_parse_state`. And of course, the central hotplug function, `__add_memory_driver_managed`, is crucial here. I'll need to see how the ranges are actually offline'd and removed, for which the `offline_and_remove_memory_ranges` in `mm/memory_hotplug.c` function looks like a good place to start my investigation.



8/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "offline_and_remove_memory_ranges",
  "PathPrefix": "mm/memory_hotplug.c"
}

Results:
{
  "Output": "mm/memory_hotplug.c=2430=int offline_and_remove_memory(u64 start, u64 size)\n--\nmm/memory_hotplug.c-2436-\nmm/memory_hotplug.c:2437:\treturn offline_and_remove_memory_ranges(\u0026range, 1);\nmm/memory_hotplug.c-2438-}\nmm/memory_hotplug.c=2439=EXPORT_SYMBOL_GPL(offline_and_remove_memory);\n--\nmm/memory_hotplug.c-2441-/**\nmm/memory_hotplug.c:2442: * offline_and_remove_memory_ranges - offline and remove multiple memory ranges\nmm/memory_hotplug.c-2443- * @ranges: array of physical address ranges to offline and remove\n--\nmm/memory_hotplug.c-2457- */\nmm/memory_hotplug.c:2458:int offline_and_remove_memory_ranges(const struct range *ranges,\nmm/memory_hotplug.c-2459-\t\tunsigned int nr_ranges)\n--\nmm/memory_hotplug.c-2521-}\nmm/memory_hotplug.c:2522:EXPORT_SYMBOL_GPL(offline_and_remove_memory_ranges);\nmm/memory_hotplug.c-2523-#endif /* CONFIG_MEMORY_HOTREMOVE */\n"
}

9/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22440
							output: 36
							thoughts: 25
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into Memory Hotplug Implementation**

Okay, so we need to understand how the system's memory management handles the dynamic addition of memory. A good starting point, given the context, is `__add_memory_driver_managed` within `mm/memory_hotplug.c`. This function name itself hints at core functionality related to memory hotplug, specifically involving drivers and, importantly, a managed context. I want to see how this function orchestrates the addition from a high-level. I'll be looking for how it interacts with the lower-level memory management structures, and the potential locks involved. We want to see how this function interfaces with the device drivers that participate in the memory hotplug process, and if there are any special handling mechanisms. I'm expecting this to be the central point where the new memory region is integrated into the system's physical address map, page tables are updated (or created if it's a completely new node), and any necessary driver-specific initialization or notification is performed. It's time to dig in.



10/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "__add_memory_driver_managed",
  "PathPrefix": "mm/memory_hotplug.c"
}

Results:
{
  "Output": "mm/memory_hotplug.c=1638=EXPORT_SYMBOL_GPL(add_memory);\n--\nmm/memory_hotplug.c-1640-/**\nmm/memory_hotplug.c:1641: * __add_memory_driver_managed - add driver-managed memory with explicit online_type\nmm/memory_hotplug.c-1642- * @nid: NUMA node ID where the memory will be added\n--\nmm/memory_hotplug.c-1671- */\nmm/memory_hotplug.c:1672:int __add_memory_driver_managed(int nid, u64 start, u64 size,\nmm/memory_hotplug.c-1673-\t\tconst char *resource_name, mhp_t mhp_flags,\n--\nmm/memory_hotplug.c-1702-}\nmm/memory_hotplug.c:1703:EXPORT_SYMBOL_FOR_MODULES(__add_memory_driver_managed, \"kmem\");\nmm/memory_hotplug.c-1704-\n--\nmm/memory_hotplug.c-1715- *\nmm/memory_hotplug.c:1716: * See __add_memory_driver_managed for more details.\nmm/memory_hotplug.c-1717- *\n--\nmm/memory_hotplug.c=1720=int add_memory_driver_managed(int nid, u64 start, u64 size,\n--\nmm/memory_hotplug.c-1722-{\nmm/memory_hotplug.c:1723:\treturn __add_memory_driver_managed(nid, start, size, resource_name,\nmm/memory_hotplug.c-1724-\t\t\tmhp_flags,\n"
}

11/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23192
							output: 32
							thoughts: 277
						

Thoughts:
**Analyzing the Kernel Patch for DAX kmem and Memory Hotplug**

Okay, so this patch is introducing some fairly significant functional additions to the kernel – specifically around memory hotplug and the DAX kmem driver. My initial assessment is that it looks like we're getting new sysfs attributes and, crucially, the hotplug/hotremove logic for DAX kmem devices.

Looking at the code, the functions that immediately grab my attention are `state_store`, `state_show`, `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, and `dax_kmem_parse_state`, as well as `offline_and_remove_memory_ranges` and `__add_memory_driver_managed`. They appear to be the core of the new functionality.

Now, let's make sure we have the correct configuration options in place. I need to verify that `CONFIG_DEV_DAX_KMEM`, `CONFIG_MEMORY_HOTPLUG`, and `CONFIG_MEMORY_HOTREMOVE` are all set appropriately. Obviously, `CONFIG_DEV_DAX_KMEM` is the driver itself, so that's a given. `CONFIG_MEMORY_HOTPLUG` is probably going to be required, judging by the presence of `__add_memory_driver_managed`, and `CONFIG_MEMORY_HOTREMOVE` for `offline_and_remove_memory_ranges`, obviously. We will need `CONFIG_DEV_DAX` as well.

I should check the `drivers/dax/Kconfig` file to confirm the exact names and any dependencies they might have.



12/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "config DEV_DAX",
  "PathPrefix": "drivers/dax/Kconfig"
}

Results:
{
  "Output": "drivers/dax/Kconfig=6=if DAX\ndrivers/dax/Kconfig-7-\ndrivers/dax/Kconfig:8:config DEV_DAX\ndrivers/dax/Kconfig-9-\ttristate \"Device DAX: direct access mapping device\"\n--\ndrivers/dax/Kconfig-18-\ndrivers/dax/Kconfig:19:config DEV_DAX_PMEM\ndrivers/dax/Kconfig-20-\ttristate \"PMEM DAX: direct access to persistent memory\"\n--\ndrivers/dax/Kconfig-29-\ndrivers/dax/Kconfig:30:config DEV_DAX_HMEM\ndrivers/dax/Kconfig-31-\ttristate \"HMEM DAX: direct access to 'specific purpose' memory\"\n--\ndrivers/dax/Kconfig-49-\ndrivers/dax/Kconfig:50:config DEV_DAX_CXL\ndrivers/dax/Kconfig-51-\ttristate \"CXL DAX: direct access to CXL RAM regions\"\n--\ndrivers/dax/Kconfig-63-\ndrivers/dax/Kconfig:64:config DEV_DAX_HMEM_DEVICES\ndrivers/dax/Kconfig-65-\tdepends on DEV_DAX_HMEM \u0026\u0026 DAX\n--\ndrivers/dax/Kconfig-67-\ndrivers/dax/Kconfig:68:config DEV_DAX_FSDEV\ndrivers/dax/Kconfig-69-\ttristate\n--\ndrivers/dax/Kconfig-72-\ndrivers/dax/Kconfig:73:config DEV_DAX_KMEM\ndrivers/dax/Kconfig-74-\ttristate \"KMEM DAX: map dax-devices as System-RAM\"\n"
}

13/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24322
							output: 31
							thoughts: 63
						

Thoughts:
**Examining Memory Configuration Options**

Okay, so I'm looking at these configurations: `DEV_DAX`, `DEV_DAX_KMEM`, `MEMORY_HOTPLUG`, and `MEMORY_HOTREMOVE`. Right now, I need to get a better understanding of how the memory hotplug and hotremove functionalities are defined in the kernel. Therefore, I will turn my attention to `mm/Kconfig` to get a good look at `MEMORY_HOTPLUG` and `MEMORY_HOTREMOVE`, to start with. Let's see what those reveal.



14/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "config MEMORY_HOTPLUG",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=519=config ARCH_ENABLE_MEMORY_HOTPLUG\n--\nmm/Kconfig-522-# eventually, we can have this option just 'select SPARSEMEM'\nmm/Kconfig:523:menuconfig MEMORY_HOTPLUG\nmm/Kconfig-524-\tbool \"Memory hotplug\"\n"
}

15/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24630
							output: 30
							thoughts: 8
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and delivered in the first person:

**Memory Hotremove - Considerations and Implications**

Alright, so we're talking about `MEMORY_HOTREMOVE`. Hmm, my mind immediately jumps to the various implications. We need to dissect this properly, especially given the complexities of modern memory management. Let's see... the core issue is, of course, the removal of memory from a running system. This isn't just a simple deallocation; it's a dynamic change to the fundamental hardware resource available.

First off, I need to consider the level of abstraction we're at. Are we talking about a hypervisor environment, a bare-metal setup, or something in between? This drastically changes the context, right? The virtualization layer adds another layer of indirection, and its implications on hot-remove behaviour can be non-trivial, especially with techniques like ballooning, or memory overcommitment. What are the hypervisor’s guarantees around transparent memory removal, and how does it deal with physical address translation?

Then there's the question of the OS itself. How well does the kernel handle the hot-remove event? Does it properly track and free pages, and does it do so in a consistent and safe manner? We need to verify that we are not introducing any inconsistencies in the page table mappings, and avoid memory corruption. We're assuming the kernel has been configured to support this, but configuration can vary widely. If we are in a system with many NUMA nodes, we also need to consider the impact to the locality of memory operations.

Let's not forget the applications running. How are they informed (if at all) of the shrinking memory space? If we're removing memory that's actively in use, or containing data, then we're in trouble. How do we ensure applications aren't accessing or expecting memory regions that are about to be unmapped? Are they utilizing shared memory, and if so, how do we handle it? What about memory-mapped files and caches? Data loss is the ultimate concern. Think of the transactional applications, for example. We need atomicity here.

Thinking of this, what are the best practices for guaranteeing a safe memory hot-remove? What are the mechanisms available? Do we need to introduce explicit memory draining procedures? What’s the implication for the reliability of the system, and what about its performance? Is there a performance hit associated with the hot-remove procedure itself? How are we accounting for lock contention?

And finally, diagnostics. If something goes wrong during the hot-remove process – a kernel panic, a corrupted application, or some other unexpected failure – how do we debug it? What kind of logging and tracing is available? What tools are we going to use to examine this behavior?

So, yeah... `MEMORY_HOTREMOVE`. It's not a simple switch; it’s a whole ecosystem of implications. We need a rigorous and systematic approach to address this properly.



16/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "config MEMORY_HOTREMOVE",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=589=endchoice\nmm/Kconfig-590-\nmm/Kconfig:591:config MEMORY_HOTREMOVE\nmm/Kconfig-592-\tbool \"Allow for memory hot remove\"\n"
}

17/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25366
							output: 23
							thoughts: 282
						

Thoughts:
**Analyzing the Potential Targets**

Okay, yes, I see the functions we're looking at. Now, let's nail down what to focus on for this analysis. From the look of it, we're interested in: `state_store`, `state_show`, `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, `dax_kmem_parse_state`, `offline_and_remove_memory_ranges`, and `__add_memory_driver_managed`.

Aha, wait a minute. I remember that `state_store` and `state_show` are static functions located within `drivers/dax/kmem.c`. So that's the scope here. So let's refine this and just work with those specific instances in the dax kmem driver: `state_store`, `state_show`, `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, `dax_kmem_parse_state`, `offline_and_remove_memory_ranges`, and `__add_memory_driver_managed`.

However, before we proceed too far, those names `state_store` and `state_show` are quite generic. Syzkaller could easily pick up on similar names in other parts of the kernel. Just to be sure, let's take a quick look to confirm if those specific functions are unique in the code base. We can't be too careful.



18/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "state_store",
  "PathPrefix": ""
}

Results:
{
  "Output": "arch/powerpc/kernel/eeh_sysfs.c=42=static ssize_t eeh_pe_state_show(struct device *dev,\n--\narch/powerpc/kernel/eeh_sysfs.c-55-\narch/powerpc/kernel/eeh_sysfs.c:56:static ssize_t eeh_pe_state_store(struct device *dev,\narch/powerpc/kernel/eeh_sysfs.c-57-\t\t\t\t  struct device_attribute *attr,\n--\narch/powerpc/platforms/83xx/suspend.c=263=static int agent_thread_fn(void *data)\n--\narch/powerpc/platforms/83xx/suspend.c-275-\t\t * to avoid mixing the two with such a configuration (or\narch/powerpc/platforms/83xx/suspend.c:276:\t\t * else fix it by adding a mutex to state_store that we can\narch/powerpc/platforms/83xx/suspend.c-277-\t\t * synchronize with).\n--\ndrivers/base/memory.c=394=static int memory_subsys_offline(struct device *dev)\n--\ndrivers/base/memory.c-403-\ndrivers/base/memory.c:404:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/base/memory.c-405-\t\t\t   const char *buf, size_t count)\n--\ndrivers/cpuidle/sysfs.c=413=static ssize_t cpuidle_state_show(struct kobject *kobj, struct attribute *attr,\n--\ndrivers/cpuidle/sysfs.c-426-\ndrivers/cpuidle/sysfs.c:427:static ssize_t cpuidle_state_store(struct kobject *kobj, struct attribute *attr,\ndrivers/cpuidle/sysfs.c-428-\t\t\t\t   const char *buf, size_t size)\n--\ndrivers/cpuidle/sysfs.c=445=static const struct sysfs_ops cpuidle_state_sysfs_ops = {\ndrivers/cpuidle/sysfs.c-446-\t.show = cpuidle_state_show,\ndrivers/cpuidle/sysfs.c:447:\t.store = cpuidle_state_store,\ndrivers/cpuidle/sysfs.c-448-};\n--\ndrivers/crypto/intel/qat/qat_common/adf_sysfs.c=19=static ssize_t state_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/crypto/intel/qat/qat_common/adf_sysfs.c-30-\ndrivers/crypto/intel/qat/qat_common/adf_sysfs.c:31:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/crypto/intel/qat/qat_common/adf_sysfs.c-32-\t\t\t   const char *buf, size_t count)\n--\ndrivers/dax/kmem.c=327=static ssize_t state_show(struct device *dev,\n--\ndrivers/dax/kmem.c-343-\ndrivers/dax/kmem.c:344:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/dax/kmem.c-345-\t\t\t     const char *buf, size_t len)\n--\ndrivers/gpu/drm/i915/gt/intel_execlists_submission.c=2219=static void execlists_capture_work(struct work_struct *work)\n--\ndrivers/gpu/drm/i915/gt/intel_execlists_submission.c-2241-\t/* Publish the error state, and announce it to the world */\ndrivers/gpu/drm/i915/gt/intel_execlists_submission.c:2242:\ti915_error_state_store(cap-\u003eerror);\ndrivers/gpu/drm/i915/gt/intel_execlists_submission.c-2243-\ti915_gpu_coredump_put(cap-\u003eerror);\n--\ndrivers/gpu/drm/i915/i915_gpu_error.c=2214=i915_gpu_coredump(struct intel_gt *gt, intel_engine_mask_t engine_mask, u32 dump_flags)\n--\ndrivers/gpu/drm/i915/i915_gpu_error.c-2228-\ndrivers/gpu/drm/i915/i915_gpu_error.c:2229:void i915_error_state_store(struct i915_gpu_coredump *error)\ndrivers/gpu/drm/i915/i915_gpu_error.c-2230-{\n--\ndrivers/gpu/drm/i915/i915_gpu_error.c=2260=void i915_capture_error_state(struct intel_gt *gt,\n--\ndrivers/gpu/drm/i915/i915_gpu_error.c-2270-\ndrivers/gpu/drm/i915/i915_gpu_error.c:2271:\ti915_error_state_store(error);\ndrivers/gpu/drm/i915/i915_gpu_error.c-2272-\ti915_gpu_coredump_put(error);\n--\ndrivers/gpu/drm/i915/i915_gpu_error.h=303=void i915_vma_capture_finish(struct intel_gt_coredump *gt,\n--\ndrivers/gpu/drm/i915/i915_gpu_error.h-305-\ndrivers/gpu/drm/i915/i915_gpu_error.h:306:void i915_error_state_store(struct i915_gpu_coredump *error);\ndrivers/gpu/drm/i915/i915_gpu_error.h-307-\n--\ndrivers/gpu/drm/i915/i915_gpu_error.h=391=static inline void\ndrivers/gpu/drm/i915/i915_gpu_error.h:392:i915_error_state_store(struct i915_gpu_coredump *error)\ndrivers/gpu/drm/i915/i915_gpu_error.h-393-{\n--\ndrivers/greybus/bundle.c=30=static ssize_t state_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/greybus/bundle.c-40-\ndrivers/greybus/bundle.c:41:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/greybus/bundle.c-42-\t\t\t   const char *buf, size_t size)\n--\ndrivers/greybus/interface.c=513=static ssize_t power_state_show(struct device *dev,\n--\ndrivers/greybus/interface.c-523-\ndrivers/greybus/interface.c:524:static ssize_t power_state_store(struct device *dev,\ndrivers/greybus/interface.c-525-\t\t\t\t struct device_attribute *attr, const char *buf,\n--\ndrivers/hwmon/emc1403.c=34=static ssize_t power_state_show(struct device *dev, struct device_attribute *attr, char *buf)\n--\ndrivers/hwmon/emc1403.c-45-\ndrivers/hwmon/emc1403.c:46:static ssize_t power_state_store(struct device *dev, struct device_attribute *attr,\ndrivers/hwmon/emc1403.c-47-\t\t\t\t const char *buf, size_t count)\n--\ndrivers/hwtracing/coresight/coresight-etm3x-sysfs.c=937=static ssize_t seq_curr_state_show(struct device *dev,\n--\ndrivers/hwtracing/coresight/coresight-etm3x-sysfs.c-961-\ndrivers/hwtracing/coresight/coresight-etm3x-sysfs.c:962:static ssize_t seq_curr_state_store(struct device *dev,\ndrivers/hwtracing/coresight/coresight-etm3x-sysfs.c-963-\t\t\t\t    struct device_attribute *attr,\n--\ndrivers/hwtracing/coresight/coresight-etm4x-sysfs.c=1414=static ssize_t seq_state_show(struct device *dev,\n--\ndrivers/hwtracing/coresight/coresight-etm4x-sysfs.c-1425-\ndrivers/hwtracing/coresight/coresight-etm4x-sysfs.c:1426:static ssize_t seq_state_store(struct device *dev,\ndrivers/hwtracing/coresight/coresight-etm4x-sysfs.c-1427-\t\t\t       struct device_attribute *attr,\n--\ndrivers/i2c/muxes/i2c-mux-pca954x.c=359=static ssize_t idle_state_show(struct device *dev,\n--\ndrivers/i2c/muxes/i2c-mux-pca954x.c-369-\ndrivers/i2c/muxes/i2c-mux-pca954x.c:370:static ssize_t idle_state_store(struct device *dev,\ndrivers/i2c/muxes/i2c-mux-pca954x.c-371-\t\t\t\tstruct device_attribute *attr,\n--\ndrivers/iio/industrialio-event.c=272=static enum iio_event_info iio_ev_attr_info(struct iio_dev_attr *attr)\n--\ndrivers/iio/industrialio-event.c-276-\ndrivers/iio/industrialio-event.c:277:static ssize_t iio_ev_state_store(struct device *dev,\ndrivers/iio/industrialio-event.c-278-\t\t\t\t  struct device_attribute *attr,\n--\ndrivers/iio/industrialio-event.c=385=static int iio_device_add_event(struct iio_dev *indio_dev,\n--\ndrivers/iio/industrialio-event.c-416-\t\t\tshow = iio_ev_state_show;\ndrivers/iio/industrialio-event.c:417:\t\t\tstore = iio_ev_state_store;\ndrivers/iio/industrialio-event.c-418-\t\t} else {\n--\ndrivers/leds/trigger/ledtrig-transient.c=119=static ssize_t transient_state_show(struct device *dev,\n--\ndrivers/leds/trigger/ledtrig-transient.c-129-\ndrivers/leds/trigger/ledtrig-transient.c:130:static ssize_t transient_state_store(struct device *dev,\ndrivers/leds/trigger/ledtrig-transient.c-131-\t\tstruct device_attribute *attr, const char *buf, size_t size)\n--\ndrivers/leds/trigger/ledtrig-transient.c=151=static DEVICE_ATTR(duration, 0644, transient_duration_show,\ndrivers/leds/trigger/ledtrig-transient.c-152-\t\t   transient_duration_store);\ndrivers/leds/trigger/ledtrig-transient.c:153:static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store);\ndrivers/leds/trigger/ledtrig-transient.c-154-\n--\ndrivers/md/md.c=3079=static ssize_t\ndrivers/md/md.c:3080:state_store(struct md_rdev *rdev, const char *buf, size_t len)\ndrivers/md/md.c-3081-{\n--\ndrivers/md/md.c=3248=static struct rdev_sysfs_entry rdev_state =\ndrivers/md/md.c:3249:__ATTR_PREALLOC(state, S_IRUGO|S_IWUSR, state_show, state_store);\ndrivers/md/md.c-3250-\n--\ndrivers/md/md.c=3735=rdev_attr_store(struct kobject *kobj, struct attribute *attr,\n--\ndrivers/md/md.c-3751-\ndrivers/md/md.c:3752:\tif (entry-\u003estore == state_store) {\ndrivers/md/md.c-3753-\t\tif (cmd_match(page, \"remove\"))\n--\ndrivers/md/md.c=4647=static ssize_t\ndrivers/md/md.c:4648:array_state_store(struct mddev *mddev, const char *buf, size_t len)\ndrivers/md/md.c-4649-{\n--\ndrivers/md/md.c=4778=static struct md_sysfs_entry md_array_state =\ndrivers/md/md.c:4779:__ATTR_PREALLOC(array_state, S_IRUGO|S_IWUSR, array_state_show, array_state_store);\ndrivers/md/md.c-4780-\n--\ndrivers/md/md.c=6113=md_attr_store(struct kobject *kobj, struct attribute *attr,\n--\ndrivers/md/md.c-6125-\ndrivers/md/md.c:6126:\tif (entry-\u003estore == array_state_store \u0026\u0026 cmd_match(page, \"clear\"))\ndrivers/md/md.c-6127-\t\tkn = sysfs_break_active_protection(kobj, attr);\n--\ndrivers/misc/bh1770glc.c=568=static irqreturn_t bh1770_irq(int irq, void *data)\n--\ndrivers/misc/bh1770glc.c-628-\ndrivers/misc/bh1770glc.c:629:static ssize_t bh1770_power_state_store(struct device *dev,\ndrivers/misc/bh1770glc.c-630-\t\t\t\t      struct device_attribute *attr,\n--\ndrivers/misc/bh1770glc.c=1136=static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, bh1770_power_state_show,\ndrivers/misc/bh1770glc.c:1137:\t\t\t\t\t\t bh1770_power_state_store);\ndrivers/misc/bh1770glc.c-1138-\n--\ndrivers/net/wireless/ti/wlcore/sysfs.c=15=static ssize_t bt_coex_state_show(struct device *dev,\n--\ndrivers/net/wireless/ti/wlcore/sysfs.c-29-\ndrivers/net/wireless/ti/wlcore/sysfs.c:30:static ssize_t bt_coex_state_store(struct device *dev,\ndrivers/net/wireless/ti/wlcore/sysfs.c-31-\t\t\t\t   struct device_attribute *attr,\n--\ndrivers/nvme/target/configfs.c=1881=static ssize_t nvmet_ana_group_ana_state_show(struct config_item *item,\n--\ndrivers/nvme/target/configfs.c-1895-\ndrivers/nvme/target/configfs.c:1896:static ssize_t nvmet_ana_group_ana_state_store(struct config_item *item,\ndrivers/nvme/target/configfs.c-1897-\t\tconst char *page, size_t count)\n--\ndrivers/pcmcia/ds.c=1046=static ssize_t pm_state_show(struct device *dev, struct device_attribute *attr, char *buf)\n--\ndrivers/pcmcia/ds.c-1055-\ndrivers/pcmcia/ds.c:1056:static ssize_t pm_state_store(struct device *dev, struct device_attribute *attr,\ndrivers/pcmcia/ds.c-1057-\t\t\t      const char *buf, size_t count)\n--\ndrivers/platform/x86/asus-wmi.c=1113=static const struct attribute_group kbd_rgb_mode_group = {\n--\ndrivers/platform/x86/asus-wmi.c-1117-/* TUF Laptop Keyboard RGB State **********************************************/\ndrivers/platform/x86/asus-wmi.c:1118:static ssize_t kbd_rgb_state_store(struct device *dev,\ndrivers/platform/x86/asus-wmi.c-1119-\t\t\t\t struct device_attribute *attr,\n--\ndrivers/platform/x86/dell/alienware-wmi-base.c=295=static ssize_t lighting_control_state_show(struct device *dev,\n--\ndrivers/platform/x86/dell/alienware-wmi-base.c-308-\ndrivers/platform/x86/dell/alienware-wmi-base.c:309:static ssize_t lighting_control_state_store(struct device *dev,\ndrivers/platform/x86/dell/alienware-wmi-base.c-310-\t\t\t\t\t    struct device_attribute *attr,\n--\ndrivers/platform/x86/huawei-wmi.c=559=static ssize_t fn_lock_state_show(struct device *dev,\n--\ndrivers/platform/x86/huawei-wmi.c-571-\ndrivers/platform/x86/huawei-wmi.c:572:static ssize_t fn_lock_state_store(struct device *dev,\ndrivers/platform/x86/huawei-wmi.c-573-\t\tstruct device_attribute *attr,\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c=1373=static ssize_t wlsw_emulstate_show(struct device_driver *drv, char *buf)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1377-\ndrivers/platform/x86/lenovo/thinkpad_acpi.c:1378:static ssize_t wlsw_emulstate_store(struct device_driver *drv, const char *buf,\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1379-\t\t\t\t    size_t count)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c=1396=static ssize_t bluetooth_emulstate_show(struct device_driver *drv, char *buf)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1400-\ndrivers/platform/x86/lenovo/thinkpad_acpi.c:1401:static ssize_t bluetooth_emulstate_store(struct device_driver *drv,\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1402-\t\t\t\t\t const char *buf, size_t count)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c=1416=static ssize_t wwan_emulstate_show(struct device_driver *drv, char *buf)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1420-\ndrivers/platform/x86/lenovo/thinkpad_acpi.c:1421:static ssize_t wwan_emulstate_store(struct device_driver *drv, const char *buf,\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1422-\t\t\t\t    size_t count)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c=1436=static ssize_t uwb_emulstate_show(struct device_driver *drv, char *buf)\n--\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1440-\ndrivers/platform/x86/lenovo/thinkpad_acpi.c:1441:static ssize_t uwb_emulstate_store(struct device_driver *drv, const char *buf,\ndrivers/platform/x86/lenovo/thinkpad_acpi.c-1442-\t\t\t\t   size_t count)\n--\ndrivers/regulator/userspace-consumer.c=42=static ssize_t state_show(struct device *dev,\n--\ndrivers/regulator/userspace-consumer.c-52-\ndrivers/regulator/userspace-consumer.c:53:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\ndrivers/regulator/userspace-consumer.c-54-\t\t\t   const char *buf, size_t count)\n--\ndrivers/remoteproc/remoteproc_sysfs.c=178=static ssize_t state_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/remoteproc/remoteproc_sysfs.c-188-/* Change remote processor state via sysfs */\ndrivers/remoteproc/remoteproc_sysfs.c:189:static ssize_t state_store(struct device *dev,\ndrivers/remoteproc/remoteproc_sysfs.c-190-\t\t\t      struct device_attribute *attr,\n--\ndrivers/s390/block/dasd_devmap.c=1804=static ssize_t dasd_reservation_state_show(struct device *dev,\n--\ndrivers/s390/block/dasd_devmap.c-1824-\ndrivers/s390/block/dasd_devmap.c:1825:static ssize_t dasd_reservation_state_store(struct device *dev,\ndrivers/s390/block/dasd_devmap.c-1826-\t\t\t\t\t    struct device_attribute *attr,\n--\ndrivers/s390/block/dasd_devmap.c=1847=static DEVICE_ATTR(last_known_reservation_state, 0644,\ndrivers/s390/block/dasd_devmap.c:1848:\t\t   dasd_reservation_state_show, dasd_reservation_state_store);\ndrivers/s390/block/dasd_devmap.c-1849-\n--\ndrivers/scsi/lpfc/lpfc_attr.c=1330=lpfc_oas_supported_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/scsi/lpfc/lpfc_attr.c-1341-/**\ndrivers/scsi/lpfc/lpfc_attr.c:1342: * lpfc_link_state_store - Transition the link_state on an HBA port\ndrivers/scsi/lpfc/lpfc_attr.c-1343- * @dev: class device that is converted into a Scsi_host.\n--\ndrivers/scsi/lpfc/lpfc_attr.c=1353=static ssize_t\ndrivers/scsi/lpfc/lpfc_attr.c:1354:lpfc_link_state_store(struct device *dev, struct device_attribute *attr,\ndrivers/scsi/lpfc/lpfc_attr.c-1355-\t\tconst char *buf, size_t count)\n--\ndrivers/scsi/lpfc/lpfc_attr.c=3120=static DEVICE_ATTR(link_state, S_IRUGO | S_IWUSR, lpfc_link_state_show,\ndrivers/scsi/lpfc/lpfc_attr.c:3121:\t\tlpfc_link_state_store);\ndrivers/scsi/lpfc/lpfc_attr.c-3122-static DEVICE_ATTR(option_rom_version, S_IRUGO,\n--\ndrivers/scsi/lpfc/lpfc_attr.c=3406=lpfc_oas_lun_state_show(struct device *dev, struct device_attribute *attr,\n--\ndrivers/scsi/lpfc/lpfc_attr.c-3415-/**\ndrivers/scsi/lpfc/lpfc_attr.c:3416: * lpfc_oas_lun_state_store - Store the state (enabled or disabled)\ndrivers/scsi/lpfc/lpfc_attr.c-3417- *\t\t\t    of whether luns will be enabled or disabled\n--\ndrivers/scsi/lpfc/lpfc_attr.c=3429=static ssize_t\ndrivers/scsi/lpfc/lpfc_attr.c:3430:lpfc_oas_lun_state_store(struct device *dev, struct device_attribute *attr,\ndrivers/scsi/lpfc/lpfc_attr.c-3431-\t\t\t const char *buf, size_t count)\n--\ndrivers/scsi/lpfc/lpfc_attr.c=3452=static DEVICE_ATTR(lpfc_xlane_lun_state, S_IRUGO | S_IWUSR,\ndrivers/scsi/lpfc/lpfc_attr.c:3453:\t\t   lpfc_oas_lun_state_show, lpfc_oas_lun_state_store);\ndrivers/scsi/lpfc/lpfc_attr.c-3454-\n--\ndrivers/scsi/megaraid/megaraid_sas_base.c=3344=static ssize_t\ndrivers/scsi/megaraid/megaraid_sas_base.c:3345:fw_crash_state_store(struct device *cdev,\ndrivers/scsi/megaraid/megaraid_sas_base.c-3346-\tstruct device_attribute *attr, const char *buf, size_t count)\n--\ndrivers/scsi/myrb.c=1760=static ssize_t raid_state_show(struct device *dev,\n--\ndrivers/scsi/myrb.c-1804-\ndrivers/scsi/myrb.c:1805:static ssize_t raid_state_store(struct device *dev,\ndrivers/scsi/myrb.c-1806-\t\tstruct device_attribute *attr, const char *buf, size_t count)\n--\ndrivers/scsi/myrs.c=934=static ssize_t raid_state_show(struct device *dev,\n--\ndrivers/scsi/myrs.c-968-\ndrivers/scsi/myrs.c:969:static ssize_t raid_state_store(struct device *dev,\ndrivers/scsi/myrs.c-970-\t\tstruct device_attribute *attr, const char *buf, size_t count)\n--\ndrivers/staging/greybus/arche-apb-ctrl.c=244=void apb_ctrl_poweroff(struct device *dev)\n--\ndrivers/staging/greybus/arche-apb-ctrl.c-248-\ndrivers/staging/greybus/arche-apb-ctrl.c:249:static ssize_t state_store(struct device *dev,\ndrivers/staging/greybus/arche-apb-ctrl.c-250-\t\t\t   struct device_attribute *attr,\n--\ndrivers/staging/greybus/arche-platform.c=281=arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)\n--\ndrivers/staging/greybus/arche-platform.c-305-\ndrivers/staging/greybus/arche-platform.c:306:static ssize_t state_store(struct device *dev,\ndrivers/staging/greybus/arche-platform.c-307-\t\t\t   struct device_attribute *attr,\n--\ndrivers/target/target_core_configfs.c=2960=static ssize_t target_tg_pt_gp_alua_access_state_show(struct config_item *item,\n--\ndrivers/target/target_core_configfs.c-2966-\ndrivers/target/target_core_configfs.c:2967:static ssize_t target_tg_pt_gp_alua_access_state_store(struct config_item *item,\ndrivers/target/target_core_configfs.c-2968-\t\tconst char *page, size_t count)\n--\ndrivers/thermal/thermal_sysfs.c=531=static ssize_t\ndrivers/thermal/thermal_sysfs.c:532:cur_state_store(struct device *dev, struct device_attribute *attr,\ndrivers/thermal/thermal_sysfs.c-533-\t\tconst char *buf, size_t count)\n--\ndrivers/tty/hvc/hvcs.c=366=static DEVICE_ATTR(current_vty,\n--\ndrivers/tty/hvc/hvcs.c-368-\ndrivers/tty/hvc/hvcs.c:369:static ssize_t hvcs_vterm_state_store(struct device *dev, struct device_attribute *attr, const char *buf,\ndrivers/tty/hvc/hvcs.c-370-\t\tsize_t count)\n--\ndrivers/tty/hvc/hvcs.c=419=static DEVICE_ATTR(vterm_state, S_IRUGO | S_IWUSR,\ndrivers/tty/hvc/hvcs.c:420:\t\thvcs_vterm_state_show, hvcs_vterm_state_store);\ndrivers/tty/hvc/hvcs.c-421-\n--\ndrivers/usb/atm/cxacru.c=315=static ssize_t adsl_state_show(struct device *dev,\n--\ndrivers/usb/atm/cxacru.c-331-\ndrivers/usb/atm/cxacru.c:332:static ssize_t adsl_state_store(struct device *dev,\ndrivers/usb/atm/cxacru.c-333-\tstruct device_attribute *attr, const char *buf, size_t count)\n--\ndrivers/xen/xen-pciback/pci_stub.c=1305=static DRIVER_ATTR_RO(irq_handlers);\ndrivers/xen/xen-pciback/pci_stub.c-1306-\ndrivers/xen/xen-pciback/pci_stub.c:1307:static ssize_t irq_handler_state_store(struct device_driver *drv,\ndrivers/xen/xen-pciback/pci_stub.c-1308-\t\t\t\t       const char *buf, size_t count)\n--\ninclude/net/inet_sock.h=364=int inet_sk_rebuild_header(struct sock *sk);\n--\ninclude/net/inet_sock.h-369- *\ninclude/net/inet_sock.h:370: * Paired with inet_sk_state_store(). Used in places we don't hold socket lock:\ninclude/net/inet_sock.h-371- * tcp_diag_get_info(), tcp_get_info(), tcp_poll(), get_tcp4_sock() ...\n--\ninclude/net/inet_sock.h=373=static inline int inet_sk_state_load(const struct sock *sk)\n--\ninclude/net/inet_sock.h-379-/**\ninclude/net/inet_sock.h:380: * inet_sk_state_store - update sk-\u003esk_state\ninclude/net/inet_sock.h-381- * @sk: socket pointer\n--\ninclude/net/inet_sock.h-386- */\ninclude/net/inet_sock.h:387:void inet_sk_state_store(struct sock *sk, int newstate);\ninclude/net/inet_sock.h-388-\n--\nkernel/power/main.c=772=static suspend_state_t decode_state(const char *buf, size_t n)\n--\nkernel/power/main.c-798-\nkernel/power/main.c:799:static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,\nkernel/power/main.c-800-\t\t\t   const char *buf, size_t n)\n--\nmm/damon/sysfs.c=2351=static int damon_sysfs_handle_cmd(enum damon_sysfs_cmd cmd,\n--\nmm/damon/sysfs.c-2390-\nmm/damon/sysfs.c:2391:static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,\nmm/damon/sysfs.c-2392-\t\tconst char *buf, size_t count)\n--\nnet/bridge/br_sysfs_br.c=158=static int set_stp_state(struct net_bridge *br, unsigned long val,\n--\nnet/bridge/br_sysfs_br.c-163-\nnet/bridge/br_sysfs_br.c:164:static ssize_t stp_state_store(struct device *d,\nnet/bridge/br_sysfs_br.c-165-\t\t\t       struct device_attribute *attr, const char *buf,\n--\nnet/ipv4/af_inet.c=1356=EXPORT_SYMBOL(inet_sk_set_state);\nnet/ipv4/af_inet.c-1357-\nnet/ipv4/af_inet.c:1358:void inet_sk_state_store(struct sock *sk, int newstate)\nnet/ipv4/af_inet.c-1359-{\n--\nnet/ipv4/inet_connection_sock.c=1321=int inet_csk_listen_start(struct sock *sk)\n--\nnet/ipv4/inet_connection_sock.c-1340-\t */\nnet/ipv4/inet_connection_sock.c:1341:\tinet_sk_state_store(sk, TCP_LISTEN);\nnet/ipv4/inet_connection_sock.c-1342-\terr = sk-\u003esk_prot-\u003eget_port(sk, inet-\u003einet_num);\n--\nnet/ipv4/tcp.c=2960=void tcp_set_state(struct sock *sk, int state)\n--\nnet/ipv4/tcp.c-3026-\t */\nnet/ipv4/tcp.c:3027:\tinet_sk_state_store(sk, state);\nnet/ipv4/tcp.c-3028-}\n--\nnet/mptcp/pm_kernel.c=852=static int mptcp_pm_nl_create_listen_socket(struct sock *sk,\n--\nnet/mptcp/pm_kernel.c-904-\t * under the msk socket lock. For the moment, that will not bring\nnet/mptcp/pm_kernel.c:905:\t * anything more than only calling inet_sk_state_store(), because the\nnet/mptcp/pm_kernel.c-906-\t * old status is known (TCP_CLOSE).\nnet/mptcp/pm_kernel.c-907-\t */\nnet/mptcp/pm_kernel.c:908:\tinet_sk_state_store(newsk, TCP_LISTEN);\nnet/mptcp/pm_kernel.c-909-\tlock_sock(ssk);\n--\nnet/mptcp/protocol.c=3169=void mptcp_set_state(struct sock *sk, int state)\n--\nnet/mptcp/protocol.c-3188-\nnet/mptcp/protocol.c:3189:\tinet_sk_state_store(sk, state);\nnet/mptcp/protocol.c-3190-}\n--\nnet/rfkill/core.c=808=static ssize_t state_show(struct device *dev, struct device_attribute *attr,\n--\nnet/rfkill/core.c-819-\nnet/rfkill/core.c:820:static ssize_t state_store(struct device *dev, struct device_attribute *attr,\nnet/rfkill/core.c-821-\t\t\t   const char *buf, size_t count)\n"
}

19/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35190
							output: 35
							thoughts: 254
						

Thoughts:
**Analyzing DAX KMEM Hotplug Implementation**

Okay, so I'm seeing a pattern here with the `state_store` and it's making me think about DAX KMEM hotplug. I'll need to focus on functions like `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, and the cleanup functions, as well as `dax_kmem_parse_state`, `offline_and_remove_memory_ranges`, and `__add_memory_driver_managed`. I need to ensure this is all handled gracefully.

First, let's confirm the existence of `dax_kmem_do_hotplug` by directly checking `drivers/dax/kmem.c`. Yes, it's there. That's a good starting point.

Now, I need to verify that the necessary configurations are in place. Are `CONFIG_DEV_DAX_KMEM`, `CONFIG_MEMORY_HOTPLUG`, and `CONFIG_MEMORY_HOTREMOVE` sufficient? I know `DEV_DAX` is also a critical dependency here, so let's make sure that's accounted for. 

Hang on, this dependency structure doesn't quite make sense. According to the Kconfig, `DEV_DAX_KMEM` does depend on `DEV_DAX` but also `MEMORY_HOTPLUG` and `MEMORY_HOTREMOVE`. Is the Kconfig correct? Time to go back and double check the dependencies in `drivers/dax/Kconfig`.



20/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "config DEV_DAX_KMEM",
  "PathPrefix": "drivers/dax/Kconfig"
}

Results:
{
  "Output": "drivers/dax/Kconfig=68=config DEV_DAX_FSDEV\n--\ndrivers/dax/Kconfig-72-\ndrivers/dax/Kconfig:73:config DEV_DAX_KMEM\ndrivers/dax/Kconfig-74-\ttristate \"KMEM DAX: map dax-devices as System-RAM\"\n"
}

21/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35921
							output: 34
							thoughts: 9
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:

**Initial Assessment**

Right, let's break this down. My immediate instinct is to identify the dependencies. We're dealing with a system, a process, a problem – whatever "it" is – and understanding the foundational elements is paramount. What are the key variables? The core inputs? The underlying assumptions? Pinpointing these dependencies is the essential first step to any effective analysis. Where do we begin to unravel complexity?



22/2 2026/07/01 04:29 tool read-file
0m Args:
{
  "File": "drivers/dax/Kconfig",
  "FirstLine": 70,
  "LineCount": 20
}

Results:
{
  "Contents": "  70:\t\tdepends on DEV_DAX \u0026\u0026 FS_DAX\n  71:\t\tdefault DEV_DAX\n  72:\t\n  73:\tconfig DEV_DAX_KMEM\n  74:\t\ttristate \"KMEM DAX: map dax-devices as System-RAM\"\n  75:\t\tdefault DEV_DAX\n  76:\t\tdepends on DEV_DAX\n  77:\t\tdepends on MEMORY_HOTPLUG # for add_memory() and friends\n  78:\t\thelp\n  79:\t\t  Support access to persistent, or other performance\n  80:\t\t  differentiated memory as if it were System RAM. This allows\n  81:\t\t  easier use of persistent memory by unmodified applications, or\n  82:\t\t  adds core kernel memory services to heterogeneous memory types\n  83:\t\t  (HMEM) marked \"reserved\" by platform firmware.\n  84:\t\n  85:\t\t  To use this feature, a DAX device must be unbound from the\n  86:\t\t  device_dax driver and bound to this kmem driver on each boot.\n  87:\t\n  88:\t\t  Say N if unsure.\n  89:\t\n"
}

23/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36378
							output: 35
							thoughts: 84
						

Thoughts:
Okay, here's my breakdown of this.

**Investigating the DEV_DAX_KMEM Dependency Chain**

Right, so I'm digging into how `DEV_DAX_KMEM` works within the kernel. My immediate thought is, it inherently relies on `DEV_DAX`, that much is clear, and also `MEMORY_HOTPLUG`, which makes sense for the dynamic nature of DAX. Now, here's the interesting bit. `MEMORY_HOTREMOVE` isn't *strictly* essential for just enabling `DEV_DAX_KMEM` itself. But I have to remember that without it, the *hotremove* functionality is disabled. So, to ensure we have the full picture, I should check if `MEMORY_HOTREMOVE` is actually used within `drivers/dax/kmem.c`. That file is where the core of this functionality resides, so that seems like the logical place to start. Let me take a look at it...



24/2 2026/07/01 04:29 tool grepper
0m Args:
{
  "Expression": "CONFIG_MEMORY_HOTREMOVE",
  "PathPrefix": "drivers/dax/kmem.c"
}

Results:
{
  "Output": "drivers/dax/kmem.c=167=static int dax_kmem_init_resources(struct dev_dax *dev_dax,\n--\ndrivers/dax/kmem.c-211-\ndrivers/dax/kmem.c:212:#ifdef CONFIG_MEMORY_HOTREMOVE\ndrivers/dax/kmem.c-213-/**\n--\ndrivers/dax/kmem.c=272=static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,\n--\ndrivers/dax/kmem.c-276-}\ndrivers/dax/kmem.c:277:#endif /* CONFIG_MEMORY_HOTREMOVE */\ndrivers/dax/kmem.c-278-\n--\ndrivers/dax/kmem.c=391=static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\n--\ndrivers/dax/kmem.c-500-\ndrivers/dax/kmem.c:501:#ifdef CONFIG_MEMORY_HOTREMOVE\ndrivers/dax/kmem.c-502-/*\n--\ndrivers/dax/kmem.c=576=static void dev_dax_kmem_remove(struct dev_dax *dev_dax)\n--\ndrivers/dax/kmem.c-590-}\ndrivers/dax/kmem.c:591:#endif /* CONFIG_MEMORY_HOTREMOVE */\ndrivers/dax/kmem.c-592-\n"
}

25/2 2026/07/01 04:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37098
							output: 30
							thoughts: 475
						

Thoughts:
**Analyzing DAX and Memory Hotplug/Hotremove Configuration**

Okay, so I'm dealing with memory hot-removal, confirmed by `CONFIG_MEMORY_HOTREMOVE`. The core configs involved are `DEV_DAX`, `DEV_DAX_KMEM`, `MEMORY_HOTPLUG`, and `MEMORY_HOTREMOVE`. Now, the crucial question is whether we need `DEV_DAX_PMEM`, `DEV_DAX_HMEM`, or even `DEV_DAX_CXL` for DAX device creation. The test script, with its `ndctl create-namespace -m devdax -e namespace0.0 -f` command, suggests at least `DEV_DAX_PMEM` or possibly `ND_BTT` are in play. However, remember syzkaller can generate DAX devices, perhaps by sysfs, assuming the underlying drivers are present. To be safe and comprehensive, I'll include `DEV_DAX_PMEM` and `DEV_DAX_HMEM` for now. Ultimately, `DEV_DAX` and `DEV_DAX_KMEM` are the essentials, along with the hotplug/hotremove options.

Next, I need to check the relevant symbols to ensure that everything is configured correctly. Looking at the `FocusSymbols`, I've got `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, `dax_kmem_parse_state`, `offline_and_remove_memory_ranges`, and `__add_memory_driver_managed`. I need to verify that `offline_and_remove_memory_ranges` is reachable. And, yes, it's called from `dax_kmem_do_hotremove` and `offline_and_remove_memory`. Likewise, `__add_memory_driver_managed` is reached through `dax_kmem_do_hotplug` and `add_memory_driver_managed`. Good, that makes sense.

Just to be absolutely certain I have not missed anything, I'll scan for any other potentially relevant functions. There are `mhp_online_type_from_str`, `mhp_online_type_to_str` and `memory_block_aligned_range`. I'll focus on `mhp_online_type_to_str` next.



26/2 2026/07/01 04:30 tool grepper
0m Args:
{
  "Expression": "mhp_online_type_to_str",
  "PathPrefix": ""
}

Results:
{
  "Output": "drivers/base/memory.c=49=EXPORT_SYMBOL_GPL(mhp_online_type_from_str);\ndrivers/base/memory.c-50-\ndrivers/base/memory.c:51:const char *mhp_online_type_to_str(int online_type)\ndrivers/base/memory.c-52-{\n--\ndrivers/base/memory.c-56-}\ndrivers/base/memory.c:57:EXPORT_SYMBOL_GPL(mhp_online_type_to_str);\ndrivers/base/memory.c-58-\n--\ndrivers/dax/kmem.c=327=static ssize_t state_show(struct device *dev,\n--\ndrivers/dax/kmem.c-338-\telse\ndrivers/dax/kmem.c:339:\t\tstate_str = mhp_online_type_to_str(data-\u003estate);\ndrivers/dax/kmem.c-340-\n--\ninclude/linux/memory_hotplug.h=129=extern int mhp_online_type_from_str(const char *str);\ninclude/linux/memory_hotplug.h:130:const char *mhp_online_type_to_str(int online_type);\ninclude/linux/memory_hotplug.h-131-\n"
}

27/2 2026/07/01 04:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38477
							output: 27
							thoughts: 47
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Reflecting on Recent Additions and a Potential Area for Examination**

Alright, confirmed – we've got the new `mhp_online_type_to_str` function integrated, as expected. And, excellent, `mhp_online_type_from_str` is now properly exported. That's a good step forward.

Now, with those fundamental pieces in place, I need to shift focus. Let's dig into that `memory_block_aligned_range` function and see what it is all about. There's a decent chance it has a relationship to the prior functions, in some way.



28/2 2026/07/01 04:30 tool grepper
0m Args:
{
  "Expression": "memory_block_aligned_range",
  "PathPrefix": ""
}

Results:
{
  "Output": "drivers/dax/kmem.c=31=static int dax_kmem_range(struct dev_dax *dev_dax, int i, struct range *r)\n--\ndrivers/dax/kmem.c-35-\ndrivers/dax/kmem.c:36:\t*r = memory_block_aligned_range(range);\ndrivers/dax/kmem.c-37-\tif (r-\u003estart \u003e= r-\u003eend) {\n--\ninclude/linux/memory.h=102=int set_memory_block_size_order(unsigned int order);\n--\ninclude/linux/memory.h-104-/**\ninclude/linux/memory.h:105: * memory_block_aligned_range - align a physical address range to memory blocks\ninclude/linux/memory.h-106- * @range: the input range to align\n--\ninclude/linux/memory.h-114- */\ninclude/linux/memory.h:115:static inline struct range memory_block_aligned_range(const struct range *range)\ninclude/linux/memory.h-116-{\n"
}

29/2 2026/07/01 04:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38984
							output: 245
							thoughts: 447
						

Thoughts:
**Analyzing DAX KMEM Hotplug Implementation**

Okay, so we've confirmed the inclusion of `memory_block_aligned_range`, which is good to see. My focus right now is on the key symbols involved in this hotplug implementation. I've identified `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, `dax_kmem_parse_state`, `offline_and_remove_memory_ranges`, `__add_memory_driver_managed`, and `mhp_online_type_to_str` as the critical functions to understand.

Now, let's confirm the visibility of those functions. I see that `dax_kmem_do_hotplug`, `dax_kmem_do_hotremove`, `dax_kmem_init_resources`, `dax_kmem_cleanup_resources`, and `dax_kmem_parse_state` are all static. That's relevant for understanding their scope within the kernel module. `offline_and_remove_memory_ranges`, `__add_memory_driver_managed`, and `mhp_online_type_to_str` are global, implying they're accessible across the system.

Next, I need to check the relevant configuration options: `DEV_DAX`, `DEV_DAX_KMEM`, `MEMORY_HOTPLUG`, and `MEMORY_HOTREMOVE`. Ah, and I should double-check that the config is exactly `CONFIG_DEV_DAX_KMEM`. Yes, it is.

The logic seems pretty clear. This patch is setting up a `/sys/bus/dax/devices/daxX.Y/state` attribute to control the hotplug state for DAX kmem devices. It's adding the necessary code to handle the hotplug and hotremove processes for memory ranges, using those new core memory hotplug functions. It looks like it's all reachable via sysfs and device binding/unbinding, which makes them prime targets for fuzzing. Okay, let's call `set-results`.



30/2 2026/07/01 04:30 tool set-results
0m Args:
{
  "EnableConfigs": [
    "DEV_DAX",
    "DEV_DAX_KMEM",
    "MEMORY_HOTPLUG",
    "MEMORY_HOTREMOVE"
  ],
  "FocusSymbols": [
    "dax_kmem_do_hotplug",
    "dax_kmem_do_hotremove",
    "dax_kmem_init_resources",
    "dax_kmem_cleanup_resources",
    "dax_kmem_parse_state",
    "offline_and_remove_memory_ranges",
    "__add_memory_driver_managed",
    "mhp_online_type_to_str"
  ],
  "Reasoning": "The patch introduces a new sysfs attribute to control the memory hotplug state of DAX kmem devices. It adds logic to handle hotplugging and hotremoving memory ranges, including new core memory hotplug functions like `offline_and_remove_memory_ranges` and `__add_memory_driver_managed`. These are reachable via sysfs and device binding/unbinding, making them suitable for fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "DEV_DAX",
    "DEV_DAX_KMEM",
    "MEMORY_HOTPLUG",
    "MEMORY_HOTREMOVE"
  ],
  "FocusSymbols": [
    "dax_kmem_do_hotplug",
    "dax_kmem_do_hotremove",
    "dax_kmem_init_resources",
    "dax_kmem_cleanup_resources",
    "dax_kmem_parse_state",
    "offline_and_remove_memory_ranges",
    "__add_memory_driver_managed",
    "mhp_online_type_to_str"
  ],
  "Reasoning": "The patch introduces a new sysfs attribute to control the memory hotplug state of DAX kmem devices. It adds logic to handle hotplugging and hotremoving memory ranges, including new core memory hotplug functions like `offline_and_remove_memory_ranges` and `__add_memory_driver_managed`. These are reachable via sysfs and device binding/unbinding, making them suitable for fuzzing.",
  "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)