| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/06 08:28 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"NUMA",
"NUMA_MIGRATION",
"CXL_BUS",
"CXL_REGION",
"DEV_DAX",
"DEV_DAX_KMEM",
"MEMORY_HOTPLUG"
],
"FocusSymbols": [
"mp_add_package_node",
"mp_add_package_node_by_initiator",
"mp_get_package_nodes",
"mp_get_package_cpu_nodes",
"mp_get_package_memory_only_nodes",
"mp_next_demotion_nodemask",
"mp_next_demotion_node",
"mp_next_promotion_nodemask",
"mp_next_promotion_node",
"mp_is_topology_symmetric",
"nearest_nodes_nodemask",
"mp_probe_package_id",
"register_mp_package_notifier",
"unregister_mp_package_notifier"
],
"Reasoning": "The patch introduces a new memory package topology concept for NUMA nodes, modifying core memory tiering, mempolicy, and CXL region handling. It adds new sysfs interfaces and a new mempolicy weighted interleave mode, which are reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/06 08:28 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 25a2a2aac649bcab6f40c1dc6ffaa35110edab7a\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Aug 6 08:28:25 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/ABI/testing/sysfs-devices-system-package b/Documentation/ABI/testing/sysfs-devices-system-package\nnew file mode 100644\nindex 0000000000000..6500f9e5ff19e\n--- /dev/null\n+++ b/Documentation/ABI/testing/sysfs-devices-system-package\n@@ -0,0 +1,35 @@\n+What:\t\t/sys/devices/system/package/\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tMemory package topology\n+\n+\t\tA \"memory package\" groups the NUMA nodes associated with one\n+\t\tphysical CPU package (socket): the nodes that have CPUs and\n+\t\tthe nodes that only have memory (e.g. CXL or HBM).\n+\n+\t\tAll attributes are read-only; the topology cannot be\n+\t\toverridden from user space.\n+\n+What:\t\t/sys/devices/system/package/packageN/package_nodes\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tAll NUMA nodes in this package, in nodelist format\n+\t\t(e.g. \"0,2\").\n+\n+What:\t\t/sys/devices/system/package/packageN/package_cpu_nodes\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tThe nodes that have CPUs in this package, in nodelist\n+\t\tformat.\n+\n+What:\t\t/sys/devices/system/package/packageN/package_mem_only_nodes\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tThe nodes that only have memory (e.g. CXL/HBM) in this\n+\t\tpackage, in nodelist format.\n+\n+What:\t\t/sys/devices/system/package/packageN/physical_package_id\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tThe physical package id this group corresponds to, as\n+\t\treported by CPU topology.\ndiff --git a/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave b/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave\nindex 649c0e9b895c4..d2ccba171c5ea 100644\n--- a/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave\n+++ b/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave\n@@ -52,3 +52,20 @@ Description:\tAuto-weighting configuration interface\n \n \t\tWriting a new weight to a node directly via the nodeN interface\n \t\twill also automatically switch the system to manual mode.\n+\n+What:\t\t/sys/kernel/mm/mempolicy/weighted_interleave/package_mode\n+Date:\t\tAugust 2026\n+Contact:\tLinux memory management mailing list \u003clinux-mm@kvack.org\u003e\n+Description:\tPackage-aware weighted interleave toggle\n+\n+\t\t'true' restricts weighted interleave node selection to the\n+\t\tNUMA nodes of the package (CPU socket) the allocating task\n+\t\tis running on. 'false' (the default) uses the existing\n+\t\tweighted interleave behavior.\n+\n+\t\tEnabling is rejected with -EINVAL while the package topology\n+\t\tis not symmetric.\n+\n+\t\tWriting any true value string (e.g. Y or 1) enables the\n+\t\trestriction, any false value string (e.g. N or 0) disables\n+\t\tit. All other strings return -EINVAL.\ndiff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c\nindex e50dc716d4e82..af66e2e06c624 100644\n--- a/drivers/cxl/core/region.c\n+++ b/drivers/cxl/core/region.c\n@@ -2673,6 +2673,55 @@ static int cxl_region_calculate_adistance(struct notifier_block *nb,\n \treturn NOTIFY_STOP;\n }\n \n+/*\n+ * Find a NUMA node to act as the initiator for this region: scan the\n+ * region's endpoint targets and return the first one that resolves to a\n+ * valid NUMA node.\n+ */\n+static int cxl_region_find_nearest_node(struct cxl_region *cxlr)\n+{\n+\tstruct cxl_region_params *p = \u0026cxlr-\u003eparams;\n+\tstruct cxl_endpoint_decoder *cxled = NULL;\n+\tstruct cxl_memdev *cxlmd = NULL;\n+\tint i, numa_node;\n+\n+\tfor (i = 0; i \u003c p-\u003enr_targets; i++) {\n+\t\tcxled = p-\u003etargets[i];\n+\t\tcxlmd = cxled_to_memdev(cxled);\n+\t\tnuma_node = dev_to_node(\u0026cxlmd-\u003edev);\n+\t\tif (numa_node != NUMA_NO_NODE)\n+\t\t\treturn numa_node;\n+\t}\n+\treturn NUMA_NO_NODE;\n+}\n+\n+/*\n+ * Package notifier callback: when a new memory node is onlined via dax\n+ * kmem, bind the node this CXL region backs to its memory package, using\n+ * the nearest region target as the initiator. Notifications for other\n+ * nodes are ignored.\n+ */\n+static int cxl_region_add_package_node(struct notifier_block *nb,\n+\t\t\t\t unsigned long dax_nid, void *data)\n+{\n+\tint region_nid, nearest_nid, ret;\n+\tstruct cxl_region *cxlr = container_of(nb, struct cxl_region, package_notifier);\n+\n+\tregion_nid = phys_to_target_node(cxlr-\u003eparams.res-\u003estart);\n+\tif (region_nid != dax_nid)\n+\t\treturn NOTIFY_DONE;\n+\n+\tnearest_nid = cxl_region_find_nearest_node(cxlr);\n+\tif (nearest_nid == NUMA_NO_NODE)\n+\t\treturn NOTIFY_DONE;\n+\n+\tret = mp_add_package_node_by_initiator(dax_nid, nearest_nid);\n+\tif (ret)\n+\t\treturn NOTIFY_DONE;\n+\n+\treturn NOTIFY_OK;\n+}\n+\n /**\n * devm_cxl_add_region - Adds a region to a decoder\n * @cxlrd: root decoder\n@@ -3852,6 +3901,7 @@ static void shutdown_notifiers(void *_cxlr)\n \n \tunregister_node_notifier(\u0026cxlr-\u003enode_notifier);\n \tunregister_mt_adistance_algorithm(\u0026cxlr-\u003eadist_notifier);\n+\tunregister_mp_package_notifier(\u0026cxlr-\u003epackage_notifier);\n }\n \n static void remove_debugfs(void *dentry)\n@@ -4066,6 +4116,10 @@ static int cxl_region_probe(struct device *dev)\n \tcxlr-\u003eadist_notifier.priority = 100;\n \tregister_mt_adistance_algorithm(\u0026cxlr-\u003eadist_notifier);\n \n+\tcxlr-\u003epackage_notifier.notifier_call = cxl_region_add_package_node;\n+\tcxlr-\u003epackage_notifier.priority = 100;\n+\tregister_mp_package_notifier(\u0026cxlr-\u003epackage_notifier);\n+\n \trc = devm_add_action_or_reset(\u0026cxlr-\u003edev, shutdown_notifiers, cxlr);\n \tif (rc)\n \t\treturn rc;\ndiff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h\nindex 1297594beaec3..9281ca3a5f0f8 100644\n--- a/drivers/cxl/cxl.h\n+++ b/drivers/cxl/cxl.h\n@@ -477,6 +477,7 @@ struct cxl_region {\n \tstruct access_coordinate coord[ACCESS_COORDINATE_MAX];\n \tstruct notifier_block node_notifier;\n \tstruct notifier_block adist_notifier;\n+\tstruct notifier_block package_notifier;\n };\n \n struct cxl_nvdimm_bridge {\ndiff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c\nindex 2cc8749bc8711..1de23f1963542 100644\n--- a/drivers/dax/kmem.c\n+++ b/drivers/dax/kmem.c\n@@ -94,6 +94,9 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)\n \tif (IS_ERR(mtype))\n \t\treturn PTR_ERR(mtype);\n \n+\t/* Resolve the memory package for this newly onlined kmem node. */\n+\tmp_probe_package_id(numa_node);\n+\n \tfor (i = 0; i \u003c dev_dax-\u003enr_range; i++) {\n \t\tstruct range range;\n \ndiff --git a/include/linux/memory-tiers.h b/include/linux/memory-tiers.h\nindex 7999c58629eeb..f8778c43429fb 100644\n--- a/include/linux/memory-tiers.h\n+++ b/include/linux/memory-tiers.h\n@@ -52,10 +52,25 @@ int mt_perf_to_adistance(struct access_coordinate *perf, int *adist);\n struct memory_dev_type *mt_find_alloc_memory_type(int adist,\n \t\t\t\t\t\t struct list_head *memory_types);\n void mt_put_memory_types(struct list_head *memory_types);\n+\n+int register_mp_package_notifier(struct notifier_block *notifier);\n+void unregister_mp_package_notifier(struct notifier_block *notifier);\n+int mp_probe_package_id(int nid);\n+int mp_add_package_node_by_initiator(int nid, int initiator_nid);\n+int mp_add_package_node(int nid);\n+int mp_get_package_nodes(int nid, nodemask_t *out);\n+int mp_get_package_cpu_nodes(int nid, nodemask_t *out);\n+int mp_get_package_memory_only_nodes(int nid, nodemask_t *out);\n+bool mp_is_topology_symmetric(void);\n #ifdef CONFIG_NUMA_MIGRATION\n int next_demotion_node(int node, const nodemask_t *allowed_mask);\n void node_get_allowed_targets(pg_data_t *pgdat, nodemask_t *targets);\n bool node_is_toptier(int node);\n+\n+int mp_next_demotion_nodemask(int nid, nodemask_t *out);\n+int mp_next_demotion_node(int nid);\n+int mp_next_promotion_nodemask(int nid, nodemask_t *out);\n+int mp_next_promotion_node(int nid);\n #else\n static inline int next_demotion_node(int node, const nodemask_t *allowed_mask)\n {\n@@ -71,6 +86,30 @@ static inline bool node_is_toptier(int node)\n {\n \treturn true;\n }\n+\n+static inline int mp_next_demotion_nodemask(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_next_demotion_node(int nid)\n+{\n+\treturn NUMA_NO_NODE;\n+}\n+\n+static inline int mp_next_promotion_nodemask(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_next_promotion_node(int nid)\n+{\n+\treturn NUMA_NO_NODE;\n+}\n #endif\n \n #else\n@@ -151,5 +190,79 @@ static inline struct memory_dev_type *mt_find_alloc_memory_type(int adist,\n static inline void mt_put_memory_types(struct list_head *memory_types)\n {\n }\n+\n+static inline int register_mp_package_notifier(struct notifier_block *notifier)\n+{\n+\treturn 0;\n+}\n+\n+static inline void unregister_mp_package_notifier(struct notifier_block *notifier)\n+{\n+}\n+\n+static inline int mp_probe_package_id(int nid)\n+{\n+\treturn NOTIFY_DONE;\n+}\n+\n+static inline int mp_add_package_node_by_initiator(int nid, int initiator_nid)\n+{\n+\treturn 0;\n+}\n+\n+static inline int mp_add_package_node(int nid)\n+{\n+\treturn 0;\n+}\n+\n+static inline int mp_get_package_nodes(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_get_package_cpu_nodes(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_get_package_memory_only_nodes(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline bool mp_is_topology_symmetric(void)\n+{\n+\treturn false;\n+}\n+\n+static inline int mp_next_demotion_nodemask(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_next_demotion_node(int nid)\n+{\n+\treturn NUMA_NO_NODE;\n+}\n+\n+static inline int mp_next_promotion_nodemask(int nid, nodemask_t *out)\n+{\n+\tif (out)\n+\t\tnodes_clear(*out);\n+\treturn -ENOENT;\n+}\n+\n+static inline int mp_next_promotion_node(int nid)\n+{\n+\treturn NUMA_NO_NODE;\n+}\n #endif\t/* CONFIG_NUMA */\n #endif /* _LINUX_MEMORY_TIERS_H */\ndiff --git a/include/linux/numa.h b/include/linux/numa.h\nindex e6baaf6051bcf..4f2a0c344122a 100644\n--- a/include/linux/numa.h\n+++ b/include/linux/numa.h\n@@ -33,6 +33,8 @@ int numa_nearest_node(int node, unsigned int state);\n \n int nearest_node_nodemask(int node, nodemask_t *mask);\n \n+int nearest_nodes_nodemask(int node, const nodemask_t *mask, nodemask_t *out);\n+\n #ifndef memory_add_physaddr_to_nid\n int memory_add_physaddr_to_nid(u64 start);\n #endif\n@@ -54,6 +56,15 @@ static inline int nearest_node_nodemask(int node, nodemask_t *mask)\n \treturn NUMA_NO_NODE;\n }\n \n+static inline int nearest_nodes_nodemask(int node, const nodemask_t *mask,\n+\t\t\t\t\t nodemask_t *out)\n+{\n+\tif (!out)\n+\t\treturn -EINVAL;\n+\tnodes_clear(*out);\n+\treturn 0;\n+}\n+\n static inline int memory_add_physaddr_to_nid(u64 start)\n {\n \treturn 0;\ndiff --git a/mm/memory-tiers.c b/mm/memory-tiers.c\nindex 54851d8a195b0..5932df315604a 100644\n--- a/mm/memory-tiers.c\n+++ b/mm/memory-tiers.c\n@@ -1,4 +1,5 @@\n // SPDX-License-Identifier: GPL-2.0\n+#include \u003clinux/cleanup.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/lockdep.h\u003e\n #include \u003clinux/sysfs.h\u003e\n@@ -51,6 +52,11 @@ static const struct bus_type memory_tier_subsys = {\n \t.dev_name = \"memory_tier\",\n };\n \n+static const struct bus_type package_subsys = {\n+\t.name = \"package\",\n+\t.dev_name = \"package\",\n+};\n+\n #ifdef CONFIG_NUMA_BALANCING\n /**\n * folio_use_access_time - check if a folio reuses cpupid for page access time\n@@ -1007,3 +1013,1006 @@ static int __init numa_init_sysfs(void)\n subsys_initcall(numa_init_sysfs);\n #endif /* CONFIG_SYSFS */\n #endif\n+\n+/**\n+ * enum mp_nodes_type - Selector for which subset of a package to return\n+ * @MP_NODES_ALL: All NUMA nodes that belong to the package.\n+ * @MP_NODES_CPU: Only CPU nodes in the package.\n+ * @MP_NODES_MEM_ONLY: Only memory-only nodes (e.g. CXL/HBM) in the package.\n+ *\n+ * Used internally to choose which nodemask to expose for a given package.\n+ */\n+enum mp_nodes_type {\n+\tMP_NODES_ALL,\n+\tMP_NODES_CPU,\n+\tMP_NODES_MEM_ONLY\n+};\n+\n+/**\n+ * struct memory_package - Per-physical-package container\n+ * @package_id: Physical package id (from topology).\n+ * @nodes: Nodemask of all member nodes in this package.\n+ * @cpu_nodes: Nodemask of CPU nodes in this package.\n+ * @memory_only_nodes: Nodemask of memory-only nodes in this package.\n+ * @cpu_list: List head of CPU-type members.\n+ * @memory_only_list: List head of memory-only members.\n+ * @list: Linkage on the global @memory_packages list.\n+ * @dev: sysfs device for this package.\n+ *\n+ * A memory_package groups NUMA nodes that share the same physical CPU package.\n+ * The masks are used to implement package-local placement/demotion/promotion.\n+ */\n+struct memory_package {\n+\tint package_id;\n+\tnodemask_t nodes;\n+\tnodemask_t cpu_nodes;\n+\tnodemask_t memory_only_nodes;\n+\tstruct list_head cpu_list;\n+\tstruct list_head memory_only_list;\n+\tstruct list_head list;\n+\tstruct device dev;\n+};\n+\n+/**\n+ * enum mpn_source_flags - Source used to resolve a node's package membership\n+ * @MPN_SRC_UNKNOWN: Unknown/unspecified.\n+ * @MPN_SRC_CPU: Directly resolved from a CPU node (1:1).\n+ * @MPN_SRC_INITIATOR: Resolved via an initiator CPU node provided by a driver.\n+ * @MPN_SRC_SLIT: Resolved via SLIT/nearest-node.\n+ *\n+ * These flags are informational; they describe how a given node was bound to\n+ * its package and help with policy decisions later.\n+ */\n+enum mpn_source_flags {\n+\tMPN_SRC_UNKNOWN\t\t= 0,\n+\tMPN_SRC_CPU\t\t= BIT(1),\n+\tMPN_SRC_INITIATOR\t= BIT(2),\n+\tMPN_SRC_SLIT\t\t= BIT(3)\n+};\n+\n+/**\n+ * struct memory_package_node - Per-node membership and preferences\n+ * @nid: NUMA node id for this entry.\n+ * @initiator_nid: CPU nid that served as the initiator when resolving @nid.\n+ * @package_id: Resolved package id that @nid belongs to.\n+ * @source_flags: One of \u0026enum mpn_source_flags describing the resolution.\n+ * @preferred: Opposite-type nearest candidates inside the same package.\n+ * @package: Pointer to the owning \u0026struct memory_package (NULL until bound).\n+ * @package_entry: Linkage on the owning package's type list.\n+ *\n+ * Each NUMA node that participates in package-aware policy gets a wrapper entry\n+ * that caches package membership and the precomputed set of preferred targets.\n+ */\n+struct memory_package_node {\n+\tint nid;\n+\tint initiator_nid;\n+\tint package_id;\n+\tint source_flags;\n+\tnodemask_t preferred;\n+\tstruct memory_package *package;\n+\tstruct list_head package_entry;\n+};\n+\n+#define node_is_memory_only(_nid) \\\n+\t(node_state((_nid), N_MEMORY) \u0026\u0026 !node_state((_nid), N_CPU))\n+\n+static BLOCKING_NOTIFIER_HEAD(mp_package_algorithms);\n+\n+static LIST_HEAD(memory_packages);\n+static struct memory_package_node *mpns[MAX_NUMNODES];\n+static DEFINE_MUTEX(memory_package_lock);\n+\n+/*\n+ * RCU snapshot of the package topology. The allocation path reads it\n+ * often, so it is published for lockless reads instead of locking on\n+ * every access.\n+ */\n+struct mp_snapshot {\n+\tstruct rcu_head rcu;\n+\tint nr_packages;\n+\tint pkg_of[MAX_NUMNODES];\n+\tstruct {\n+\t\tnodemask_t nodes;\n+\t\tnodemask_t cpu_nodes;\n+\t\tnodemask_t memory_only_nodes;\n+\t} pkg[];\n+};\n+\n+static struct mp_snapshot __rcu *mp_snapshot;\n+\n+/**\n+ * register_mp_package_notifier - Register a package resolution algorithm\n+ * @notifier: Notifier called with the nid to resolve (see mp_probe_package_id()).\n+ *\n+ * Drivers (e.g., CXL region/decoder code) register here to supply a package\n+ * hint for newly appearing nodes. The notifier is invoked during nid-\u003epackage\n+ * resolution.\n+ *\n+ * Return: 0 on success, negative errno on failure.\n+ */\n+int register_mp_package_notifier(struct notifier_block *notifier)\n+{\n+\treturn blocking_notifier_chain_register(\u0026mp_package_algorithms, notifier);\n+}\n+EXPORT_SYMBOL_GPL(register_mp_package_notifier);\n+\n+/**\n+ * unregister_mp_package_notifier - Unregister a package resolution algorithm\n+ * @notifier: Notifier previously registered with register_mp_package_notifier().\n+ */\n+void unregister_mp_package_notifier(struct notifier_block *notifier)\n+{\n+\tblocking_notifier_chain_unregister(\u0026mp_package_algorithms, notifier);\n+}\n+EXPORT_SYMBOL_GPL(unregister_mp_package_notifier);\n+\n+/**\n+ * mp_probe_package_id - Invoke registered notifiers to resolve a node's package\n+ * @nid: NUMA node id to resolve.\n+ *\n+ * Calls the blocking notifier chain to let subsystems provide an initiator or\n+ * package id for @nid.\n+ *\n+ * Return: Notifier return code (\u003e=0 typically); negative errno on failure.\n+ */\n+int mp_probe_package_id(int nid)\n+{\n+\treturn blocking_notifier_call_chain(\u0026mp_package_algorithms, nid, NULL);\n+}\n+EXPORT_SYMBOL_GPL(mp_probe_package_id);\n+\n+static int mp_node_to_package_id(int nid)\n+{\n+\tint package_id;\n+\tunsigned int first_cpu;\n+\tconst struct cpumask *cpu_mask;\n+\n+\tif (nid \u003c 0 || nid \u003e= MAX_NUMNODES)\n+\t\treturn -EINVAL;\n+\n+\tif (!node_state(nid, N_CPU))\n+\t\treturn -EINVAL;\n+\n+\tcpu_mask = cpumask_of_node(nid);\n+\tif (cpumask_empty(cpu_mask))\n+\t\treturn -EINVAL;\n+\n+\tfirst_cpu = cpumask_first(cpu_mask);\n+\tif (first_cpu \u003e= nr_cpu_ids)\n+\t\treturn -EINVAL;\n+\n+\tpackage_id = topology_physical_package_id(first_cpu);\n+\tif (package_id \u003c 0)\n+\t\treturn -EINVAL;\n+\n+\treturn package_id;\n+}\n+\n+static void update_package_preferred(struct memory_package *mp)\n+{\n+\tstruct memory_package_node *mpn;\n+\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\t/*\n+\t * For each CPU node, compute its preferred set as the nearest\n+\t * memory-only node(s) within the same package. If the package has\n+\t * no memory-only nodes, fall back to a self-reference so callers\n+\t * never see an empty preferred set.\n+\t */\n+\tlist_for_each_entry(mpn, \u0026mp-\u003ecpu_list, package_entry) {\n+\t\tnodes_clear(mpn-\u003epreferred);\n+\t\tif (!nodes_empty(mp-\u003ememory_only_nodes))\n+\t\t\tnearest_nodes_nodemask(mpn-\u003enid, \u0026mp-\u003ememory_only_nodes,\n+\t\t\t\t\t \u0026mpn-\u003epreferred);\n+\t\telse\n+\t\t\tnode_set(mpn-\u003enid, mpn-\u003epreferred);\n+\t}\n+\n+\t/*\n+\t * Symmetrically, for each memory-only node, compute its preferred set\n+\t * as the nearest CPU node(s) within the same package. If the package\n+\t * has no CPU nodes, fall back to a self-reference.\n+\t */\n+\tlist_for_each_entry(mpn, \u0026mp-\u003ememory_only_list, package_entry) {\n+\t\tnodes_clear(mpn-\u003epreferred);\n+\t\tif (!nodes_empty(mp-\u003ecpu_nodes))\n+\t\t\tnearest_nodes_nodemask(mpn-\u003enid, \u0026mp-\u003ecpu_nodes,\n+\t\t\t\t\t \u0026mpn-\u003epreferred);\n+\t\telse\n+\t\t\tnode_set(mpn-\u003enid, mpn-\u003epreferred);\n+\t}\n+}\n+\n+static inline bool memory_package_is_empty(struct memory_package *mp)\n+{\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\treturn (nodes_empty(mp-\u003ecpu_nodes) \u0026\u0026 nodes_empty(mp-\u003ememory_only_nodes));\n+}\n+\n+static inline bool package_node_is_valid(int nid)\n+{\n+\tif (!mpns[nid])\n+\t\treturn false;\n+\n+\tif (nodes_empty(mpns[nid]-\u003epreferred) || (mpns[nid]-\u003epackage == NULL))\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static const struct attribute_group *memory_package_groups[];\n+\n+/* Freed when the last reference to the package's sysfs device is dropped. */\n+static void memory_package_release(struct device *dev)\n+{\n+\tstruct memory_package *mp = container_of(dev, struct memory_package, dev);\n+\n+\tkfree(mp);\n+}\n+\n+static struct memory_package *create_memory_package(int package_id)\n+{\n+\tstruct memory_package *mempackage;\n+\tint ret;\n+\n+\tmempackage = kzalloc_obj(*mempackage);\n+\tif (!mempackage)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tmempackage-\u003epackage_id = package_id;\n+\tmempackage-\u003enodes = NODE_MASK_NONE;\n+\tmempackage-\u003ecpu_nodes = NODE_MASK_NONE;\n+\tmempackage-\u003ememory_only_nodes = NODE_MASK_NONE;\n+\tINIT_LIST_HEAD(\u0026mempackage-\u003ecpu_list);\n+\tINIT_LIST_HEAD(\u0026mempackage-\u003ememory_only_list);\n+\tINIT_LIST_HEAD(\u0026mempackage-\u003elist);\n+\tdevice_initialize(\u0026mempackage-\u003edev);\n+\tmempackage-\u003edev.release = memory_package_release;\n+\tdev_set_drvdata(\u0026mempackage-\u003edev, mempackage);\n+\tmempackage-\u003edev.bus = \u0026package_subsys;\n+\tmempackage-\u003edev.groups = memory_package_groups;\n+\tret = dev_set_name(\u0026mempackage-\u003edev, \"package%d\", package_id);\n+\tif (ret) {\n+\t\tput_device(\u0026mempackage-\u003edev);\n+\t\treturn ERR_PTR(ret);\n+\t}\n+\n+\treturn mempackage;\n+}\n+\n+static struct memory_package *find_create_memory_package(int package_id)\n+{\n+\tstruct memory_package *mempackage, *existing;\n+\tint ret;\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tlist_for_each_entry(mempackage, \u0026memory_packages, list) {\n+\t\tif (mempackage-\u003epackage_id == package_id) {\n+\t\t\tmutex_unlock(\u0026memory_package_lock);\n+\t\t\treturn mempackage;\n+\t\t}\n+\t}\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tmempackage = create_memory_package(package_id);\n+\tif (IS_ERR(mempackage))\n+\t\treturn mempackage;\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tlist_for_each_entry(existing, \u0026memory_packages, list) {\n+\t\tif (existing-\u003epackage_id == package_id) {\n+\t\t\tmutex_unlock(\u0026memory_package_lock);\n+\t\t\tput_device(\u0026mempackage-\u003edev);\n+\t\t\treturn existing;\n+\t\t}\n+\t}\n+\tlist_add(\u0026mempackage-\u003elist, \u0026memory_packages);\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tret = device_add(\u0026mempackage-\u003edev);\n+\tif (ret) {\n+\t\tmutex_lock(\u0026memory_package_lock);\n+\t\tlist_del(\u0026mempackage-\u003elist);\n+\t\tmutex_unlock(\u0026memory_package_lock);\n+\t\tput_device(\u0026mempackage-\u003edev);\n+\t\treturn ERR_PTR(ret);\n+\t}\n+\n+\treturn mempackage;\n+}\n+\n+static void mp_snapshot_rebuild(void)\n+{\n+\tstruct mp_snapshot *new, *old;\n+\tstruct memory_package *mp;\n+\tint nr = 0, i = 0, nid;\n+\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\tlist_for_each_entry(mp, \u0026memory_packages, list)\n+\t\tnr++;\n+\n+\tnew = kvzalloc(struct_size(new, pkg, nr), GFP_KERNEL);\n+\tif (!new)\n+\t\treturn;\n+\n+\tmemset(new-\u003epkg_of, 0xff, sizeof(new-\u003epkg_of));\n+\n+\tlist_for_each_entry(mp, \u0026memory_packages, list) {\n+\t\tnew-\u003epkg[i].nodes = mp-\u003enodes;\n+\t\tnew-\u003epkg[i].cpu_nodes = mp-\u003ecpu_nodes;\n+\t\tnew-\u003epkg[i].memory_only_nodes = mp-\u003ememory_only_nodes;\n+\t\tfor_each_node_mask(nid, mp-\u003enodes)\n+\t\t\tnew-\u003epkg_of[nid] = i;\n+\t\ti++;\n+\t}\n+\tnew-\u003enr_packages = nr;\n+\n+\told = rcu_replace_pointer(mp_snapshot, new,\n+\t\t\t\t lockdep_is_held(\u0026memory_package_lock));\n+\tif (old)\n+\t\tkvfree_rcu(old, rcu);\n+}\n+\n+static int bind_node_to_package(int nid)\n+{\n+\tint package_id, pkg_id;\n+\tstruct memory_package *mp;\n+\tnodemask_t nodes, cpu, mem;\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tif (!mpns[nid]) {\n+\t\tmutex_unlock(\u0026memory_package_lock);\n+\t\treturn -EINVAL;\n+\t}\n+\tpackage_id = mpns[nid]-\u003epackage_id;\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tmp = find_create_memory_package(package_id);\n+\tif (IS_ERR(mp))\n+\t\treturn PTR_ERR(mp);\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tif (!mpns[nid]) {\n+\t\tmutex_unlock(\u0026memory_package_lock);\n+\t\treturn -ENOENT;\n+\t}\n+\tmpns[nid]-\u003epackage = mp;\n+\tnode_set(mpns[nid]-\u003enid, mp-\u003enodes);\n+\tif (node_is_memory_only(mpns[nid]-\u003enid)) {\n+\t\tnode_set(mpns[nid]-\u003enid, mp-\u003ememory_only_nodes);\n+\t\tlist_add(\u0026mpns[nid]-\u003epackage_entry, \u0026mp-\u003ememory_only_list);\n+\t} else {\n+\t\tnode_set(mpns[nid]-\u003enid, mp-\u003ecpu_nodes);\n+\t\tlist_add(\u0026mpns[nid]-\u003epackage_entry, \u0026mp-\u003ecpu_list);\n+\t}\n+\tupdate_package_preferred(mp);\n+\tmp_snapshot_rebuild();\n+\tpkg_id = mp-\u003epackage_id;\n+\tnodes = mp-\u003enodes;\n+\tcpu = mp-\u003ecpu_nodes;\n+\tmem = mp-\u003ememory_only_nodes;\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tpr_info(\"memory_package %d: nodes=%*pbl cpu=%*pbl memory_only=%*pbl\\n\",\n+\t\tpkg_id, nodemask_pr_args(\u0026nodes),\n+\t\tnodemask_pr_args(\u0026cpu), nodemask_pr_args(\u0026mem));\n+\n+\treturn 0;\n+}\n+\n+static void unbind_node_to_package(struct memory_package *mp, int nid)\n+{\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\tnode_clear(nid, mp-\u003enodes);\n+\tif (node_state(nid, N_CPU))\n+\t\tnode_clear(nid, mp-\u003ecpu_nodes);\n+\telse\n+\t\tnode_clear(nid, mp-\u003ememory_only_nodes);\n+\n+\tif (mpns[nid])\n+\t\tlist_del(\u0026mpns[nid]-\u003epackage_entry);\n+\n+\tupdate_package_preferred(mp);\n+}\n+\n+static struct memory_package_node *create_package_node(int nid, int initiator_nid)\n+{\n+\tint cpu_nid, package_id;\n+\tint source_flags;\n+\tstruct memory_package_node *mpn;\n+\n+\tif (node_state(nid, N_CPU)) {\n+\t\tcpu_nid = nid;\n+\t\tsource_flags = MPN_SRC_CPU;\n+\t} else {\n+\t\tif (initiator_nid \u003e= 0) {\n+\t\t\tcpu_nid = initiator_nid;\n+\t\t\tsource_flags = MPN_SRC_INITIATOR;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * No driver-supplied initiator: fall back to the\n+\t\t\t * nearest CPU node (via SLIT/numa_distance).\n+\t\t\t */\n+\t\t\tcpu_nid = numa_nearest_node(nid, N_CPU);\n+\t\t\tsource_flags = MPN_SRC_SLIT;\n+\t\t}\n+\t}\n+\n+\tpackage_id = mp_node_to_package_id(cpu_nid);\n+\tif (package_id \u003c 0)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tmpn = kzalloc_obj(*mpn);\n+\tif (!mpn)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tmpn-\u003enid = nid;\n+\tmpn-\u003einitiator_nid = cpu_nid;\n+\tmpn-\u003epackage_id = package_id;\n+\tmpn-\u003esource_flags = source_flags;\n+\tmpn-\u003epreferred = NODE_MASK_NONE;\n+\tmpn-\u003epackage = NULL;\n+\tINIT_LIST_HEAD(\u0026mpn-\u003epackage_entry);\n+\n+\treturn mpn;\n+}\n+\n+/*\n+ * Topology symmetry status\n+ * Indicates whether all packages have identical node structure\n+ * (same number of CPU nodes and memory-only nodes).\n+ */\n+static bool topology_symmetric;\n+\n+static void validate_topology_symmetry(void);\n+\n+static struct memory_package *__destroy_package_node(int nid)\n+{\n+\tstruct memory_package_node *mpn;\n+\tstruct memory_package *mp, *unreg_mp = NULL;\n+\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\tmpn = mpns[nid];\n+\tif (!mpn)\n+\t\treturn NULL;\n+\n+\tmp = mpn-\u003epackage;\n+\tif (mp) {\n+\t\tunbind_node_to_package(mp, nid);\n+\t\tmpn-\u003epackage = NULL;\n+\n+\t\tif (memory_package_is_empty(mp)) {\n+\t\t\tlist_del(\u0026mp-\u003elist);\n+\t\t\tunreg_mp = mp;\n+\t\t}\n+\t}\n+\n+\tmpns[nid] = NULL;\n+\tkfree(mpn);\n+\tmp_snapshot_rebuild();\n+\n+\treturn unreg_mp;\n+}\n+\n+static void destroy_package_node(int nid)\n+{\n+\tstruct memory_package *unreg_mp;\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tunreg_mp = __destroy_package_node(nid);\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tif (unreg_mp)\n+\t\tdevice_unregister(\u0026unreg_mp-\u003edev);\n+\n+\tvalidate_topology_symmetry();\n+}\n+\n+static int find_package_node(int nid, int initiator_nid)\n+{\n+\tstruct memory_package *unreg_mp = NULL;\n+\tint ret = nid;\n+\n+\tmutex_lock(\u0026memory_package_lock);\n+\tif (!mpns[nid]) {\n+\t\tret = NUMA_NO_NODE;\n+\t} else if (mpns[nid]-\u003esource_flags == MPN_SRC_SLIT \u0026\u0026 initiator_nid \u003e= 0) {\n+\t\t/*\n+\t\t * SLIT-derived entries are provisional; if a driver later\n+\t\t * provides an explicit initiator, drop the provisional\n+\t\t * entry and rebuild with the stronger hint.\n+\t\t */\n+\t\tunreg_mp = __destroy_package_node(nid);\n+\t\tret = NUMA_NO_NODE;\n+\t}\n+\tmutex_unlock(\u0026memory_package_lock);\n+\n+\tif (unreg_mp)\n+\t\tdevice_unregister(\u0026unreg_mp-\u003edev);\n+\n+\treturn ret;\n+}\n+\n+static int find_create_package_node(int nid, int initiator_nid)\n+{\n+\tint mpn_nid;\n+\tstruct memory_package_node *mpn;\n+\n+\tmpn_nid = find_package_node(nid, initiator_nid);\n+\tif (mpn_nid != NUMA_NO_NODE)\n+\t\treturn mpn_nid;\n+\n+\tmpn = create_package_node(nid, initiator_nid);\n+\tif (IS_ERR(mpn))\n+\t\treturn PTR_ERR(mpn);\n+\n+\tguard(mutex)(\u0026memory_package_lock);\n+\tif (mpns[nid]) {\n+\t\tkfree(mpn);\n+\t\treturn nid;\n+\t}\n+\tmpns[nid] = mpn;\n+\n+\treturn nid;\n+}\n+\n+static int create_node_with_package(int nid)\n+{\n+\tint ret;\n+\n+\tret = find_create_package_node(nid, NUMA_NO_NODE);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tret = bind_node_to_package(nid);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tvalidate_topology_symmetry();\n+\treturn 0;\n+}\n+\n+/**\n+ * mp_add_package_node_by_initiator - Add a node with an initiator\n+ * @nid: Target NUMA node to add.\n+ * @initiator_nid: CPU nid used to resolve @nid's package (\u003e=0).\n+ *\n+ * Ensures that a \u0026struct memory_package_node exists for @nid and that its\n+ * package_id is determined using @initiator_nid when provided. Binding to the\n+ * package is not performed here.\n+ *\n+ * Return: 0 on success; negative errno on failure.\n+ */\n+int mp_add_package_node_by_initiator(int nid, int initiator_nid)\n+{\n+\tint ret;\n+\n+\tret = find_create_package_node(nid, initiator_nid);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(mp_add_package_node_by_initiator);\n+\n+/**\n+ * mp_add_package_node - Add a node, resolving package automatically\n+ * @nid: Target NUMA node to add.\n+ *\n+ * Wrapper over mp_add_package_node_by_initiator() that requests automatic\n+ * initiator resolution (e.g., nearest CPU).\n+ *\n+ * Return: 0 on success; negative errno on failure.\n+ */\n+int mp_add_package_node(int nid)\n+{\n+\treturn mp_add_package_node_by_initiator(nid, NUMA_NO_NODE);\n+}\n+EXPORT_SYMBOL_GPL(mp_add_package_node);\n+\n+static int __mp_get_package_nodemask(int nid, enum mp_nodes_type node_type,\n+\t\t\t\t nodemask_t *out)\n+{\n+\tstruct mp_snapshot *snap;\n+\tint pkg;\n+\n+\tif (!out)\n+\t\treturn -EINVAL;\n+\n+\tnodes_clear(*out);\n+\n+\tif (nid \u003c 0 || nid \u003e= MAX_NUMNODES)\n+\t\treturn -EINVAL;\n+\n+\tguard(rcu)();\n+\n+\tsnap = rcu_dereference(mp_snapshot);\n+\tif (!snap)\n+\t\treturn -ENOENT;\n+\n+\tpkg = snap-\u003epkg_of[nid];\n+\tif (pkg \u003c 0)\n+\t\treturn -ENOENT;\n+\n+\tswitch (node_type) {\n+\tcase MP_NODES_ALL:\n+\t\tnodes_copy(*out, snap-\u003epkg[pkg].nodes);\n+\t\tbreak;\n+\tcase MP_NODES_CPU:\n+\t\tnodes_copy(*out, snap-\u003epkg[pkg].cpu_nodes);\n+\t\tbreak;\n+\tcase MP_NODES_MEM_ONLY:\n+\t\tnodes_copy(*out, snap-\u003epkg[pkg].memory_only_nodes);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+#ifdef CONFIG_NUMA_MIGRATION\n+static int __mp_get_preferred_nodemask(int nid, enum mp_nodes_type node_type,\n+\t\t\t\t nodemask_t *out)\n+{\n+\tint ret = 0;\n+\n+\t/* No hot-path callers: the mutex is fine here. */\n+\tguard(mutex)(\u0026memory_package_lock);\n+\n+\tif (!out) {\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\tnodes_clear(*out);\n+\n+\tif (nid \u003c 0 || nid \u003e= MAX_NUMNODES) {\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\tif (node_type == MP_NODES_CPU) {\n+\t\tif (node_is_memory_only(nid)) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t} else if (node_type == MP_NODES_MEM_ONLY) {\n+\t\tif (!node_is_memory_only(nid)) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t} else {\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\tif (!package_node_is_valid(nid)) {\n+\t\tret = -ENOENT;\n+\t\tgoto out;\n+\t}\n+\n+\tnodes_copy(*out, mpns[nid]-\u003epreferred);\n+\n+out:\n+\treturn ret;\n+}\n+\n+/**\n+ * mp_next_demotion_nodemask - Demotion candidates within a package\n+ * @nid: CPU node from which memory would be demoted.\n+ * @out: Output nodemask of nearest memory-only targets in the same package.\n+ *\n+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.\n+ */\n+int mp_next_demotion_nodemask(int nid, nodemask_t *out)\n+{\n+\treturn __mp_get_preferred_nodemask(nid, MP_NODES_CPU, out);\n+}\n+EXPORT_SYMBOL_GPL(mp_next_demotion_nodemask);\n+\n+/**\n+ * mp_next_demotion_node - Pick one demotion target\n+ * @nid: CPU node from which memory would be demoted.\n+ *\n+ * Picks one target (random among the nearest) from mp_next_demotion_nodemask().\n+ *\n+ * Return: target nid on success, or NUMA_NO_NODE if no candidate is available.\n+ */\n+int mp_next_demotion_node(int nid)\n+{\n+\tint target_nid;\n+\tnodemask_t target_nodemask;\n+\n+\tif (mp_next_demotion_nodemask(nid, \u0026target_nodemask))\n+\t\treturn NUMA_NO_NODE;\n+\tif (nodes_empty(target_nodemask))\n+\t\treturn NUMA_NO_NODE;\n+\n+\ttarget_nid = node_random(\u0026target_nodemask);\n+\n+\treturn target_nid;\n+}\n+EXPORT_SYMBOL_GPL(mp_next_demotion_node);\n+\n+/**\n+ * mp_next_promotion_nodemask - Promotion candidates within a package\n+ * @nid: Memory-only node towards which promotion seeks CPU locality.\n+ * @out: Output nodemask of nearest CPU targets in the same package.\n+ *\n+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.\n+ */\n+int mp_next_promotion_nodemask(int nid, nodemask_t *out)\n+{\n+\treturn __mp_get_preferred_nodemask(nid, MP_NODES_MEM_ONLY, out);\n+}\n+EXPORT_SYMBOL_GPL(mp_next_promotion_nodemask);\n+\n+/**\n+ * mp_next_promotion_node - Pick one promotion target\n+ * @nid: Memory-only node to be promoted towards CPUs.\n+ *\n+ * Picks one target (random among the nearest) from mp_next_promotion_nodemask().\n+ *\n+ * Return: target nid on success, or NUMA_NO_NODE if no candidate is available.\n+ */\n+int mp_next_promotion_node(int nid)\n+{\n+\tint target_nid;\n+\tnodemask_t target_nodemask;\n+\n+\tif (mp_next_promotion_nodemask(nid, \u0026target_nodemask))\n+\t\treturn NUMA_NO_NODE;\n+\tif (nodes_empty(target_nodemask))\n+\t\treturn NUMA_NO_NODE;\n+\n+\ttarget_nid = node_random(\u0026target_nodemask);\n+\n+\treturn target_nid;\n+}\n+EXPORT_SYMBOL_GPL(mp_next_promotion_node);\n+#endif /* CONFIG_NUMA_MIGRATION */\n+\n+/**\n+ * mp_get_package_nodes - Return all members of @nid's package\n+ * @nid: Any NUMA node in the package.\n+ * @out: Output nodemask to receive all members.\n+ *\n+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.\n+ */\n+int mp_get_package_nodes(int nid, nodemask_t *out)\n+{\n+\treturn __mp_get_package_nodemask(nid, MP_NODES_ALL, out);\n+}\n+EXPORT_SYMBOL_GPL(mp_get_package_nodes);\n+\n+/**\n+ * mp_get_package_cpu_nodes - Return CPU members of @nid's package\n+ * @nid: Any NUMA node in the package.\n+ * @out: Output nodemask to receive CPU members.\n+ *\n+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.\n+ */\n+int mp_get_package_cpu_nodes(int nid, nodemask_t *out)\n+{\n+\treturn __mp_get_package_nodemask(nid, MP_NODES_CPU, out);\n+}\n+EXPORT_SYMBOL_GPL(mp_get_package_cpu_nodes);\n+\n+/**\n+ * mp_get_package_memory_only_nodes - Return memory-only members of @nid's package\n+ * @nid: Any NUMA node in the package.\n+ * @out: Output nodemask to receive memory-only members.\n+ *\n+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.\n+ */\n+int mp_get_package_memory_only_nodes(int nid, nodemask_t *out)\n+{\n+\treturn __mp_get_package_nodemask(nid, MP_NODES_MEM_ONLY, out);\n+}\n+EXPORT_SYMBOL_GPL(mp_get_package_memory_only_nodes);\n+\n+static int __meminit mp_hotplug_callback(struct notifier_block *nb,\n+\t\tunsigned long action, void *_arg)\n+{\n+\tint nid;\n+\tstruct node_notify *nn = _arg;\n+\n+\tnid = nn-\u003enid;\n+\tif (nid \u003c 0)\n+\t\treturn notifier_from_errno(0);\n+\n+\tswitch (action) {\n+\tcase NODE_REMOVED_LAST_MEMORY:\n+\t\tdestroy_package_node(nid);\n+\t\tbreak;\n+\n+\tcase NODE_ADDED_FIRST_MEMORY:\n+\t\tcreate_node_with_package(nid);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn notifier_from_errno(0);\n+}\n+\n+/*\n+ * sysfs interface for memory_package topology\n+ * Read-only attributes:\n+ * - package_nodes: All NUMA nodes in this package (node mask)\n+ * - package_cpu_nodes: CPU nodes in this package (node mask)\n+ * - package_mem_only_nodes: Memory-only nodes in this package (node mask)\n+ * - physical_package_id: Physical package ID\n+ */\n+\n+static ssize_t package_nodes_show(struct device *dev,\n+\t\t\t\t struct device_attribute *attr, char *buf)\n+{\n+\tstruct memory_package *mp = dev_get_drvdata(dev);\n+\n+\tguard(mutex)(\u0026memory_package_lock);\n+\treturn sysfs_emit(buf, \"%*pbl\\n\", nodemask_pr_args(\u0026mp-\u003enodes));\n+}\n+static DEVICE_ATTR_RO(package_nodes);\n+\n+static ssize_t package_cpu_nodes_show(struct device *dev,\n+\t\t\t\t struct device_attribute *attr, char *buf)\n+{\n+\tstruct memory_package *mp = dev_get_drvdata(dev);\n+\n+\tguard(mutex)(\u0026memory_package_lock);\n+\treturn sysfs_emit(buf, \"%*pbl\\n\", nodemask_pr_args(\u0026mp-\u003ecpu_nodes));\n+}\n+static DEVICE_ATTR_RO(package_cpu_nodes);\n+\n+static ssize_t package_mem_only_nodes_show(struct device *dev,\n+\t\t\t\t\t struct device_attribute *attr, char *buf)\n+{\n+\tstruct memory_package *mp = dev_get_drvdata(dev);\n+\n+\tguard(mutex)(\u0026memory_package_lock);\n+\treturn sysfs_emit(buf, \"%*pbl\\n\", nodemask_pr_args(\u0026mp-\u003ememory_only_nodes));\n+}\n+static DEVICE_ATTR_RO(package_mem_only_nodes);\n+\n+static ssize_t physical_package_id_show(struct device *dev,\n+\t\t\t\t\tstruct device_attribute *attr, char *buf)\n+{\n+\tstruct memory_package *mp = dev_get_drvdata(dev);\n+\n+\treturn sysfs_emit(buf, \"%d\\n\", mp-\u003epackage_id);\n+}\n+static DEVICE_ATTR_RO(physical_package_id);\n+\n+static struct attribute *memory_package_attrs[] = {\n+\t\u0026dev_attr_package_nodes.attr,\n+\t\u0026dev_attr_package_cpu_nodes.attr,\n+\t\u0026dev_attr_package_mem_only_nodes.attr,\n+\t\u0026dev_attr_physical_package_id.attr,\n+\tNULL,\n+};\n+\n+static const struct attribute_group memory_package_group = {\n+\t.attrs = memory_package_attrs,\n+};\n+\n+static const struct attribute_group *memory_package_groups[] = {\n+\t\u0026memory_package_group,\n+\tNULL,\n+};\n+\n+static int __init memory_package_sysfs_init(void)\n+{\n+\tint ret;\n+\n+\tret = subsys_system_register(\u0026package_subsys, NULL);\n+\tif (ret) {\n+\t\tpr_err(\"memory_package subsys_system_register failed: %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+core_initcall(memory_package_sysfs_init);\n+\n+/**\n+ * mp_is_topology_symmetric - Check if all packages have identical node structure\n+ *\n+ * Returns true if all memory packages have the same number of CPU nodes\n+ * and memory-only nodes, indicating a symmetric topology.\n+ *\n+ * The topology_symmetric variable is protected by memory_package_lock for\n+ * writes (in validate_topology_symmetry).\n+ */\n+bool mp_is_topology_symmetric(void)\n+{\n+\treturn READ_ONCE(topology_symmetric);\n+}\n+EXPORT_SYMBOL_GPL(mp_is_topology_symmetric);\n+\n+/* Walk the package list and compare node counts; the caller holds the lock. */\n+static bool __check_topology_symmetry(void)\n+{\n+\tstruct memory_package *mp;\n+\tint ref_cpu_node_count = 0, ref_memory_only_node_count = 0;\n+\tint package_count = 0;\n+\n+\tlockdep_assert_held(\u0026memory_package_lock);\n+\n+\tlist_for_each_entry(mp, \u0026memory_packages, list) {\n+\t\tint cpu_node_count = nodes_weight(mp-\u003ecpu_nodes);\n+\t\tint memory_only_node_count = nodes_weight(mp-\u003ememory_only_nodes);\n+\t\tint total_node_count = cpu_node_count + memory_only_node_count;\n+\n+\t\tpackage_count++;\n+\n+\t\t/* Each package must have at least 2 nodes */\n+\t\tif (total_node_count \u003c 2)\n+\t\t\treturn false;\n+\n+\t\tif (package_count == 1) {\n+\t\t\tref_cpu_node_count = cpu_node_count;\n+\t\t\tref_memory_only_node_count = memory_only_node_count;\n+\t\t} else if (cpu_node_count != ref_cpu_node_count ||\n+\t\t\t memory_only_node_count != ref_memory_only_node_count) {\n+\t\t\treturn false;\n+\t\t}\n+\t}\n+\n+\tif (package_count \u003c 2)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+/**\n+ * validate_topology_symmetry - Validate topology for package-aware features\n+ *\n+ * Validates topology symmetry by checking all memory packages have identical\n+ * node structure. Updates the global topology_symmetric variable.\n+ *\n+ * Topology is valid only when all conditions are met:\n+ * 1. At least 2 packages exist (multi-package system)\n+ * 2. All packages have identical cpu_node and memory_only_node counts\n+ * 3. Each package has at least 2 total nodes (cpu_node + memory_only_node \u003e= 2)\n+ *\n+ * Valid configurations per package:\n+ * - 1 cpu_node + 1 memory_only_node = 2 nodes (valid)\n+ * - 2 cpu_nodes + 0 memory_only_node = 2 nodes (valid)\n+ * - 0 cpu_node + 2 memory_only_nodes = 2 nodes (valid)\n+ * - 1 cpu_node + 0 memory_only_node = 1 node (invalid - single node package)\n+ */\n+static void validate_topology_symmetry(void)\n+{\n+\tguard(mutex)(\u0026memory_package_lock);\n+\n+\tWRITE_ONCE(topology_symmetric, __check_topology_symmetry());\n+}\n+\n+static int __init memory_package_init(void)\n+{\n+\tint ret = 0, nid;\n+\n+\tfor_each_online_node(nid) {\n+\t\tif (!node_state(nid, N_MEMORY))\n+\t\t\tcontinue;\n+\n+\t\tret = create_node_with_package(nid);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\thotplug_node_notifier(mp_hotplug_callback, MEMTIER_HOTPLUG_PRI);\n+\n+\tvalidate_topology_symmetry();\n+\n+out:\n+\treturn ret;\n+}\n+late_initcall(memory_package_init);\ndiff --git a/mm/mempolicy.c b/mm/mempolicy.c\nindex 4e4421b22b59f..66bccb9a0a191 100644\n--- a/mm/mempolicy.c\n+++ b/mm/mempolicy.c\n@@ -117,6 +117,7 @@\n #include \u003casm/tlb.h\u003e\n #include \u003clinux/uaccess.h\u003e\n #include \u003clinux/memory.h\u003e\n+#include \u003clinux/memory-tiers.h\u003e\n \n #include \"internal.h\"\n \n@@ -167,6 +168,8 @@ static unsigned int *node_bw_table;\n */\n static DEFINE_MUTEX(wi_state_lock);\n \n+static bool package_mode_enabled;\n+\n static u8 get_il_weight(int node)\n {\n \tstruct weighted_interleave_state *state;\n@@ -180,6 +183,11 @@ static u8 get_il_weight(int node)\n \treturn weight;\n }\n \n+static bool wi_package_mode_enabled(void)\n+{\n+\treturn READ_ONCE(package_mode_enabled) \u0026\u0026 mp_is_topology_symmetric();\n+}\n+\n /*\n * Convert bandwidth values into weighted interleave weights.\n * Call with wi_state_lock.\n@@ -337,6 +345,47 @@ int nearest_node_nodemask(int node, nodemask_t *mask)\n }\n EXPORT_SYMBOL_GPL(nearest_node_nodemask);\n \n+/**\n+ * nearest_nodes_nodemask - Find all nodes in @mask that are nearest to @node\n+ * @node: The reference node ID to measure distance from\n+ * @mask: The set of candidate nodes to compare against\n+ * @out: Pointer to a nodemask that will store the nearest node(s)\n+ *\n+ * This function iterates over all nodes in @mask and measures the distance\n+ * between each candidate node and the given @node using node_distance().\n+ * It finds the minimum distance and then records all nodes in @mask that\n+ * share that same minimum distance into the output mask @out.\n+ *\n+ * For example, if multiple nodes have equal minimal distance to @node, all\n+ * of them are included in @out.\n+ *\n+ * Return: 0 on success, or -EINVAL if @out is NULL.\n+ */\n+int nearest_nodes_nodemask(int node, const nodemask_t *mask, nodemask_t *out)\n+{\n+\tint dist, n, min_dist = INT_MAX;\n+\n+\tif (!out)\n+\t\treturn -EINVAL;\n+\n+\tnodes_clear(*out);\n+\n+\tfor_each_node_mask(n, *mask) {\n+\t\tdist = node_distance(node, n);\n+\n+\t\tif (dist \u003c min_dist) {\n+\t\t\tmin_dist = dist;\n+\t\t\tnodes_clear(*out);\n+\t\t\tnode_set(n, *out);\n+\t\t} else if (dist == min_dist) {\n+\t\t\tnode_set(n, *out);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(nearest_nodes_nodemask);\n+\n struct mempolicy *get_task_policy(struct task_struct *p)\n {\n \tstruct mempolicy *pol = p-\u003emempolicy;\n@@ -2097,17 +2146,97 @@ bool apply_policy_zone(struct mempolicy *policy, enum zone_type zone)\n \treturn zone \u003e= dynamic_policy_zone;\n }\n \n+/**\n+ * policy_resolve_package_nodes - Restrict policy nodes to the current package\n+ * @policy: Target mempolicy whose user-selected nodes are in @policy-\u003enodes.\n+ * @mask: Output nodemask. On success, contains policy-\u003enodes limited to\n+ * the package that should be used for the allocation.\n+ *\n+ * This helper combines two constraints to decide where within a package\n+ * memory may be allocated:\n+ *\n+ * 1) The caller's package: derived via mp_get_package_nodes(numa_node_id()).\n+ * 2) The user's preselected set @policy-\u003enodes (cpusets/mempolicy).\n+ *\n+ * The function obtains the nodemask of the current CPU's package and\n+ * intersects it with @policy-\u003enodes. If the intersection is empty (e.g. the\n+ * user excluded every node of the current package), it falls back to the\n+ * node in @policy-\u003enodes, derives that node's package, and intersects\n+ * again. If the fallback also yields an empty set, @mask stays empty and a\n+ * non-zero error is returned.\n+ *\n+ * Examples (packages: P0={CPU:0, MEM:2}, P1={CPU:1, MEM:3}):\n+ * - policy-\u003enodes = {0,1,2,3}\n+ * on P0: mask = {0,2}; on P1: mask = {1,3}.\n+ * - policy-\u003enodes = {0,1,3}\n+ * on P0: mask = {0} (only node 0 from P0 is allowed).\n+ * - policy-\u003enodes = {1,2,3}\n+ * on P0: mask = {2} (only node 2 from P0 is allowed).\n+ * - policy-\u003enodes = {1,3}\n+ * on P0: current package (P0) \u0026 policy = NULL -\u003e fallback to policy=1,\n+ * package(1)=P1, mask = {1,3}. (User effectively opted out of P0.)\n+ *\n+ * If the selected node is low on memory, the allocation may use another node.\n+ *\n+ * Return:\n+ * 0 on success with @mask set as above;\n+ * -EINVAL if @policy/@mask is NULL;\n+ * -ENOENT if even the fallback intersection is empty;\n+ * Propagated error from mp_get_package_nodes() on failure.\n+ */\n+static int policy_resolve_package_nodes(struct mempolicy *policy, nodemask_t *mask)\n+{\n+\tnodemask_t package_mask;\n+\tint node, ret;\n+\n+\tif (!policy || !mask)\n+\t\treturn -EINVAL;\n+\n+\tnodes_clear(*mask);\n+\n+\tnode = numa_node_id();\n+\tret = mp_get_package_nodes(node, \u0026package_mask);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tnodes_and(*mask, package_mask, policy-\u003enodes);\n+\tif (!nodes_empty(*mask))\n+\t\treturn 0;\n+\n+\t/*\n+\t * The user's nodemask excludes every node of the current package;\n+\t * fall back to the package spanned by the user's own first node.\n+\t */\n+\tnode = first_node(policy-\u003enodes);\n+\tret = mp_get_package_nodes(node, \u0026package_mask);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tnodes_and(*mask, package_mask, policy-\u003enodes);\n+\tif (nodes_empty(*mask))\n+\t\treturn -ENOENT;\n+\n+\treturn 0;\n+}\n+\n static unsigned int weighted_interleave_nodes(struct mempolicy *policy)\n {\n \tunsigned int node;\n \tunsigned int cpuset_mems_cookie;\n+\tnodemask_t mask;\n \n retry:\n \t/* to prevent miscount use tsk-\u003emems_allowed_seq to detect rebind */\n \tcpuset_mems_cookie = read_mems_allowed_begin();\n \tnode = current-\u003eil_prev;\n-\tif (!current-\u003eil_weight || !node_isset(node, policy-\u003enodes)) {\n-\t\tnode = next_node_in(node, policy-\u003enodes);\n+\n+\t/* Package mode off or unresolved: fall back to the full policy nodemask. */\n+\tif (!wi_package_mode_enabled() ||\n+\t policy_resolve_package_nodes(policy, \u0026mask))\n+\t\tmask = policy-\u003enodes;\n+\n+\tif (!current-\u003eil_weight || !node_isset(node, mask)) {\n+\t\tnode = next_node_in(node, mask);\n \t\tif (read_mems_allowed_retry(cpuset_mems_cookie))\n \t\t\tgoto retry;\n \t\tif (node == MAX_NUMNODES)\n@@ -2200,6 +2329,30 @@ static unsigned int read_once_policy_nodemask(struct mempolicy *pol,\n \treturn nodes_weight(*mask);\n }\n \n+/*\n+ * Package-aware counterpart of read_once_policy_nodemask(): resolve the\n+ * current package's nodes intersected with the policy, falling back to the\n+ * full policy nodemask when package mode is off or resolution fails.\n+ */\n+static unsigned int read_once_policy_package_nodemask(struct mempolicy *pol,\n+\t\t\t\t\t\t nodemask_t *mask)\n+{\n+\tnodemask_t package_mask;\n+\n+\tbarrier();\n+\tif (!wi_package_mode_enabled()) {\n+\t\tmemcpy(mask, \u0026pol-\u003enodes, sizeof(nodemask_t));\n+\t\treturn nodes_weight(*mask);\n+\t}\n+\tif (policy_resolve_package_nodes(pol, \u0026package_mask))\n+\t\tmemcpy(mask, \u0026pol-\u003enodes, sizeof(nodemask_t));\n+\telse\n+\t\tmemcpy(mask, \u0026package_mask, sizeof(nodemask_t));\n+\tbarrier();\n+\n+\treturn nodes_weight(*mask);\n+}\n+\n static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)\n {\n \tstruct weighted_interleave_state *state;\n@@ -2210,7 +2363,7 @@ static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)\n \tu8 weight;\n \tint nid = 0;\n \n-\tnr_nodes = read_once_policy_nodemask(pol, \u0026nodemask);\n+\tnr_nodes = read_once_policy_package_nodemask(pol, \u0026nodemask);\n \tif (!nr_nodes)\n \t\treturn numa_node_id();\n \n@@ -2654,7 +2807,7 @@ static unsigned long alloc_pages_bulk_weighted_interleave(gfp_t gfp,\n \t/* read the nodes onto the stack, retry if done during rebind */\n \tdo {\n \t\tcpuset_mems_cookie = read_mems_allowed_begin();\n-\t\tnnodes = read_once_policy_nodemask(pol, \u0026nodes);\n+\t\tnnodes = read_once_policy_package_nodemask(pol, \u0026nodes);\n \t} while (read_mems_allowed_retry(cpuset_mems_cookie));\n \n \t/* if the nodemask has become invalid, we cannot do anything */\n@@ -3794,7 +3947,42 @@ static struct kobj_attribute wi_auto_attr = {\n \t.store = weighted_interleave_auto_store,\n };\n \n+static ssize_t package_mode_show(struct kobject *kobj,\n+\t\tstruct kobj_attribute *attr, char *buf)\n+{\n+\treturn sysfs_emit(buf, \"%s\\n\", str_true_false(READ_ONCE(package_mode_enabled)));\n+}\n+\n+static ssize_t package_mode_store(struct kobject *kobj,\n+\t\tstruct kobj_attribute *attr, const char *buf, size_t count)\n+{\n+\tbool input;\n+\tint err;\n+\n+\terr = kstrtobool(buf, \u0026input);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/*\n+\t * Disable package-aware weighted interleave on non-symmetric topologies.\n+\t * Non-symmetric topology (e.g., asymmetric CXL memory attachment) can\n+\t * cause performance degradation if package-aware allocation is used.\n+\t * Reject enable request if topology is not symmetric.\n+\t */\n+\tif (input \u0026\u0026 !mp_is_topology_symmetric()) {\n+\t\tpr_warn(\"package_mode cannot be enabled on non-symmetric topology\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tWRITE_ONCE(package_mode_enabled, input);\n+\treturn count;\n+}\n+\n+static struct kobj_attribute wi_package_mode_attr =\n+\t__ATTR(package_mode, 0664, package_mode_show, package_mode_store);\n+\n static void wi_cleanup(void) {\n+\tsysfs_remove_file(\u0026wi_group-\u003ewi_kobj, \u0026wi_package_mode_attr.attr);\n \tsysfs_remove_file(\u0026wi_group-\u003ewi_kobj, \u0026wi_auto_attr.attr);\n \tsysfs_wi_node_delete_all();\n \twi_state_free();\n@@ -3900,6 +4088,10 @@ static int __init add_weighted_interleave_group(struct kobject *mempolicy_kobj)\n \tif (err)\n \t\tgoto err_put_kobj;\n \n+\terr = sysfs_create_file(\u0026wi_group-\u003ewi_kobj, \u0026wi_package_mode_attr.attr);\n+\tif (err)\n+\t\tgoto err_cleanup_kobj;\n+\n \tfor_each_online_node(nid) {\n \t\tif (!node_state(nid, N_MEMORY))\n \t\t\tcontinue;\n"
} |
| 2/1 | 2026/08/06 08:28 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"NUMA",
"NUMA_MIGRATION",
"CXL_BUS",
"CXL_REGION",
"DEV_DAX",
"DEV_DAX_KMEM",
"MEMORY_HOTPLUG"
],
"FocusSymbols": [
"mp_add_package_node",
"mp_add_package_node_by_initiator",
"mp_get_package_nodes",
"mp_get_package_cpu_nodes",
"mp_get_package_memory_only_nodes",
"mp_next_demotion_nodemask",
"mp_next_demotion_node",
"mp_next_promotion_nodemask",
"mp_next_promotion_node",
"mp_is_topology_symmetric",
"nearest_nodes_nodemask",
"mp_probe_package_id",
"register_mp_package_notifier",
"unregister_mp_package_notifier"
],
"Reasoning": "The patch introduces a new memory package topology concept for NUMA nodes, modifying core memory tiering, mempolicy, and CXL region handling. It adds new sysfs interfaces and a new mempolicy weighted interleave mode, which are reachable from userspace and should be fuzzed.",
"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). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. 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. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 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. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. 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 25a2a2aac649bcab6f40c1dc6ffaa35110edab7a
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Aug 6 08:28:25 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/ABI/testing/sysfs-devices-system-package b/Documentation/ABI/testing/sysfs-devices-system-package
new file mode 100644
index 0000000000000..6500f9e5ff19e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-system-package
@@ -0,0 +1,35 @@
+What: /sys/devices/system/package/
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: Memory package topology
+
+ A "memory package" groups the NUMA nodes associated with one
+ physical CPU package (socket): the nodes that have CPUs and
+ the nodes that only have memory (e.g. CXL or HBM).
+
+ All attributes are read-only; the topology cannot be
+ overridden from user space.
+
+What: /sys/devices/system/package/packageN/package_nodes
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: All NUMA nodes in this package, in nodelist format
+ (e.g. "0,2").
+
+What: /sys/devices/system/package/packageN/package_cpu_nodes
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: The nodes that have CPUs in this package, in nodelist
+ format.
+
+What: /sys/devices/system/package/packageN/package_mem_only_nodes
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: The nodes that only have memory (e.g. CXL/HBM) in this
+ package, in nodelist format.
+
+What: /sys/devices/system/package/packageN/physical_package_id
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: The physical package id this group corresponds to, as
+ reported by CPU topology.
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave b/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave
index 649c0e9b895c4..d2ccba171c5ea 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave
@@ -52,3 +52,20 @@ Description: Auto-weighting configuration interface
Writing a new weight to a node directly via the nodeN interface
will also automatically switch the system to manual mode.
+
+What: /sys/kernel/mm/mempolicy/weighted_interleave/package_mode
+Date: August 2026
+Contact: Linux memory management mailing list <linux-mm@kvack.org>
+Description: Package-aware weighted interleave toggle
+
+ 'true' restricts weighted interleave node selection to the
+ NUMA nodes of the package (CPU socket) the allocating task
+ is running on. 'false' (the default) uses the existing
+ weighted interleave behavior.
+
+ Enabling is rejected with -EINVAL while the package topology
+ is not symmetric.
+
+ Writing any true value string (e.g. Y or 1) enables the
+ restriction, any false value string (e.g. N or 0) disables
+ it. All other strings return -EINVAL.
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index e50dc716d4e82..af66e2e06c624 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -2673,6 +2673,55 @@ static int cxl_region_calculate_adistance(struct notifier_block *nb,
return NOTIFY_STOP;
}
+/*
+ * Find a NUMA node to act as the initiator for this region: scan the
+ * region's endpoint targets and return the first one that resolves to a
+ * valid NUMA node.
+ */
+static int cxl_region_find_nearest_node(struct cxl_region *cxlr)
+{
+ struct cxl_region_params *p = &cxlr->params;
+ struct cxl_endpoint_decoder *cxled = NULL;
+ struct cxl_memdev *cxlmd = NULL;
+ int i, numa_node;
+
+ for (i = 0; i < p->nr_targets; i++) {
+ cxled = p->targets[i];
+ cxlmd = cxled_to_memdev(cxled);
+ numa_node = dev_to_node(&cxlmd->dev);
+ if (numa_node != NUMA_NO_NODE)
+ return numa_node;
+ }
+ return NUMA_NO_NODE;
+}
+
+/*
+ * Package notifier callback: when a new memory node is onlined via dax
+ * kmem, bind the node this CXL region backs to its memory package, using
+ * the nearest region target as the initiator. Notifications for other
+ * nodes are ignored.
+ */
+static int cxl_region_add_package_node(struct notifier_block *nb,
+ unsigned long dax_nid, void *data)
+{
+ int region_nid, nearest_nid, ret;
+ struct cxl_region *cxlr = container_of(nb, struct cxl_region, package_notifier);
+
+ region_nid = phys_to_target_node(cxlr->params.res->start);
+ if (region_nid != dax_nid)
+ return NOTIFY_DONE;
+
+ nearest_nid = cxl_region_find_nearest_node(cxlr);
+ if (nearest_nid == NUMA_NO_NODE)
+ return NOTIFY_DONE;
+
+ ret = mp_add_package_node_by_initiator(dax_nid, nearest_nid);
+ if (ret)
+ return NOTIFY_DONE;
+
+ return NOTIFY_OK;
+}
+
/**
* devm_cxl_add_region - Adds a region to a decoder
* @cxlrd: root decoder
@@ -3852,6 +3901,7 @@ static void shutdown_notifiers(void *_cxlr)
unregister_node_notifier(&cxlr->node_notifier);
unregister_mt_adistance_algorithm(&cxlr->adist_notifier);
+ unregister_mp_package_notifier(&cxlr->package_notifier);
}
static void remove_debugfs(void *dentry)
@@ -4066,6 +4116,10 @@ static int cxl_region_probe(struct device *dev)
cxlr->adist_notifier.priority = 100;
register_mt_adistance_algorithm(&cxlr->adist_notifier);
+ cxlr->package_notifier.notifier_call = cxl_region_add_package_node;
+ cxlr->package_notifier.priority = 100;
+ register_mp_package_notifier(&cxlr->package_notifier);
+
rc = devm_add_action_or_reset(&cxlr->dev, shutdown_notifiers, cxlr);
if (rc)
return rc;
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 1297594beaec3..9281ca3a5f0f8 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -477,6 +477,7 @@ struct cxl_region {
struct access_coordinate coord[ACCESS_COORDINATE_MAX];
struct notifier_block node_notifier;
struct notifier_block adist_notifier;
+ struct notifier_block package_notifier;
};
struct cxl_nvdimm_bridge {
diff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c
index 2cc8749bc8711..1de23f1963542 100644
--- a/drivers/dax/kmem.c
+++ b/drivers/dax/kmem.c
@@ -94,6 +94,9 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
if (IS_ERR(mtype))
return PTR_ERR(mtype);
+ /* Resolve the memory package for this newly onlined kmem node. */
+ mp_probe_package_id(numa_node);
+
for (i = 0; i < dev_dax->nr_range; i++) {
struct range range;
diff --git a/include/linux/memory-tiers.h b/include/linux/memory-tiers.h
index 7999c58629eeb..f8778c43429fb 100644
--- a/include/linux/memory-tiers.h
+++ b/include/linux/memory-tiers.h
@@ -52,10 +52,25 @@ int mt_perf_to_adistance(struct access_coordinate *perf, int *adist);
struct memory_dev_type *mt_find_alloc_memory_type(int adist,
struct list_head *memory_types);
void mt_put_memory_types(struct list_head *memory_types);
+
+int register_mp_package_notifier(struct notifier_block *notifier);
+void unregister_mp_package_notifier(struct notifier_block *notifier);
+int mp_probe_package_id(int nid);
+int mp_add_package_node_by_initiator(int nid, int initiator_nid);
+int mp_add_package_node(int nid);
+int mp_get_package_nodes(int nid, nodemask_t *out);
+int mp_get_package_cpu_nodes(int nid, nodemask_t *out);
+int mp_get_package_memory_only_nodes(int nid, nodemask_t *out);
+bool mp_is_topology_symmetric(void);
#ifdef CONFIG_NUMA_MIGRATION
int next_demotion_node(int node, const nodemask_t *allowed_mask);
void node_get_allowed_targets(pg_data_t *pgdat, nodemask_t *targets);
bool node_is_toptier(int node);
+
+int mp_next_demotion_nodemask(int nid, nodemask_t *out);
+int mp_next_demotion_node(int nid);
+int mp_next_promotion_nodemask(int nid, nodemask_t *out);
+int mp_next_promotion_node(int nid);
#else
static inline int next_demotion_node(int node, const nodemask_t *allowed_mask)
{
@@ -71,6 +86,30 @@ static inline bool node_is_toptier(int node)
{
return true;
}
+
+static inline int mp_next_demotion_nodemask(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_next_demotion_node(int nid)
+{
+ return NUMA_NO_NODE;
+}
+
+static inline int mp_next_promotion_nodemask(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_next_promotion_node(int nid)
+{
+ return NUMA_NO_NODE;
+}
#endif
#else
@@ -151,5 +190,79 @@ static inline struct memory_dev_type *mt_find_alloc_memory_type(int adist,
static inline void mt_put_memory_types(struct list_head *memory_types)
{
}
+
+static inline int register_mp_package_notifier(struct notifier_block *notifier)
+{
+ return 0;
+}
+
+static inline void unregister_mp_package_notifier(struct notifier_block *notifier)
+{
+}
+
+static inline int mp_probe_package_id(int nid)
+{
+ return NOTIFY_DONE;
+}
+
+static inline int mp_add_package_node_by_initiator(int nid, int initiator_nid)
+{
+ return 0;
+}
+
+static inline int mp_add_package_node(int nid)
+{
+ return 0;
+}
+
+static inline int mp_get_package_nodes(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_get_package_cpu_nodes(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_get_package_memory_only_nodes(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline bool mp_is_topology_symmetric(void)
+{
+ return false;
+}
+
+static inline int mp_next_demotion_nodemask(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_next_demotion_node(int nid)
+{
+ return NUMA_NO_NODE;
+}
+
+static inline int mp_next_promotion_nodemask(int nid, nodemask_t *out)
+{
+ if (out)
+ nodes_clear(*out);
+ return -ENOENT;
+}
+
+static inline int mp_next_promotion_node(int nid)
+{
+ return NUMA_NO_NODE;
+}
#endif /* CONFIG_NUMA */
#endif /* _LINUX_MEMORY_TIERS_H */
diff --git a/include/linux/numa.h b/include/linux/numa.h
index e6baaf6051bcf..4f2a0c344122a 100644
--- a/include/linux/numa.h
+++ b/include/linux/numa.h
@@ -33,6 +33,8 @@ int numa_nearest_node(int node, unsigned int state);
int nearest_node_nodemask(int node, nodemask_t *mask);
+int nearest_nodes_nodemask(int node, const nodemask_t *mask, nodemask_t *out);
+
#ifndef memory_add_physaddr_to_nid
int memory_add_physaddr_to_nid(u64 start);
#endif
@@ -54,6 +56,15 @@ static inline int nearest_node_nodemask(int node, nodemask_t *mask)
return NUMA_NO_NODE;
}
+static inline int nearest_nodes_nodemask(int node, const nodemask_t *mask,
+ nodemask_t *out)
+{
+ if (!out)
+ return -EINVAL;
+ nodes_clear(*out);
+ return 0;
+}
+
static inline int memory_add_physaddr_to_nid(u64 start)
{
return 0;
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 54851d8a195b0..5932df315604a 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/lockdep.h>
#include <linux/sysfs.h>
@@ -51,6 +52,11 @@ static const struct bus_type memory_tier_subsys = {
.dev_name = "memory_tier",
};
+static const struct bus_type package_subsys = {
+ .name = "package",
+ .dev_name = "package",
+};
+
#ifdef CONFIG_NUMA_BALANCING
/**
* folio_use_access_time - check if a folio reuses cpupid for page access time
@@ -1007,3 +1013,1006 @@ static int __init numa_init_sysfs(void)
subsys_initcall(numa_init_sysfs);
#endif /* CONFIG_SYSFS */
#endif
+
+/**
+ * enum mp_nodes_type - Selector for which subset of a package to return
+ * @MP_NODES_ALL: All NUMA nodes that belong to the package.
+ * @MP_NODES_CPU: Only CPU nodes in the package.
+ * @MP_NODES_MEM_ONLY: Only memory-only nodes (e.g. CXL/HBM) in the package.
+ *
+ * Used internally to choose which nodemask to expose for a given package.
+ */
+enum mp_nodes_type {
+ MP_NODES_ALL,
+ MP_NODES_CPU,
+ MP_NODES_MEM_ONLY
+};
+
+/**
+ * struct memory_package - Per-physical-package container
+ * @package_id: Physical package id (from topology).
+ * @nodes: Nodemask of all member nodes in this package.
+ * @cpu_nodes: Nodemask of CPU nodes in this package.
+ * @memory_only_nodes: Nodemask of memory-only nodes in this package.
+ * @cpu_list: List head of CPU-type members.
+ * @memory_only_list: List head of memory-only members.
+ * @list: Linkage on the global @memory_packages list.
+ * @dev: sysfs device for this package.
+ *
+ * A memory_package groups NUMA nodes that share the same physical CPU package.
+ * The masks are used to implement package-local placement/demotion/promotion.
+ */
+struct memory_package {
+ int package_id;
+ nodemask_t nodes;
+ nodemask_t cpu_nodes;
+ nodemask_t memory_only_nodes;
+ struct list_head cpu_list;
+ struct list_head memory_only_list;
+ struct list_head list;
+ struct device dev;
+};
+
+/**
+ * enum mpn_source_flags - Source used to resolve a node's package membership
+ * @MPN_SRC_UNKNOWN: Unknown/unspecified.
+ * @MPN_SRC_CPU: Directly resolved from a CPU node (1:1).
+ * @MPN_SRC_INITIATOR: Resolved via an initiator CPU node provided by a driver.
+ * @MPN_SRC_SLIT: Resolved via SLIT/nearest-node.
+ *
+ * These flags are informational; they describe how a given node was bound to
+ * its package and help with policy decisions later.
+ */
+enum mpn_source_flags {
+ MPN_SRC_UNKNOWN = 0,
+ MPN_SRC_CPU = BIT(1),
+ MPN_SRC_INITIATOR = BIT(2),
+ MPN_SRC_SLIT = BIT(3)
+};
+
+/**
+ * struct memory_package_node - Per-node membership and preferences
+ * @nid: NUMA node id for this entry.
+ * @initiator_nid: CPU nid that served as the initiator when resolving @nid.
+ * @package_id: Resolved package id that @nid belongs to.
+ * @source_flags: One of &enum mpn_source_flags describing the resolution.
+ * @preferred: Opposite-type nearest candidates inside the same package.
+ * @package: Pointer to the owning &struct memory_package (NULL until bound).
+ * @package_entry: Linkage on the owning package's type list.
+ *
+ * Each NUMA node that participates in package-aware policy gets a wrapper entry
+ * that caches package membership and the precomputed set of preferred targets.
+ */
+struct memory_package_node {
+ int nid;
+ int initiator_nid;
+ int package_id;
+ int source_flags;
+ nodemask_t preferred;
+ struct memory_package *package;
+ struct list_head package_entry;
+};
+
+#define node_is_memory_only(_nid) \
+ (node_state((_nid), N_MEMORY) && !node_state((_nid), N_CPU))
+
+static BLOCKING_NOTIFIER_HEAD(mp_package_algorithms);
+
+static LIST_HEAD(memory_packages);
+static struct memory_package_node *mpns[MAX_NUMNODES];
+static DEFINE_MUTEX(memory_package_lock);
+
+/*
+ * RCU snapshot of the package topology. The allocation path reads it
+ * often, so it is published for lockless reads instead of locking on
+ * every access.
+ */
+struct mp_snapshot {
+ struct rcu_head rcu;
+ int nr_packages;
+ int pkg_of[MAX_NUMNODES];
+ struct {
+ nodemask_t nodes;
+ nodemask_t cpu_nodes;
+ nodemask_t memory_only_nodes;
+ } pkg[];
+};
+
+static struct mp_snapshot __rcu *mp_snapshot;
+
+/**
+ * register_mp_package_notifier - Register a package resolution algorithm
+ * @notifier: Notifier called with the nid to resolve (see mp_probe_package_id()).
+ *
+ * Drivers (e.g., CXL region/decoder code) register here to supply a package
+ * hint for newly appearing nodes. The notifier is invoked during nid->package
+ * resolution.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int register_mp_package_notifier(struct notifier_block *notifier)
+{
+ return blocking_notifier_chain_register(&mp_package_algorithms, notifier);
+}
+EXPORT_SYMBOL_GPL(register_mp_package_notifier);
+
+/**
+ * unregister_mp_package_notifier - Unregister a package resolution algorithm
+ * @notifier: Notifier previously registered with register_mp_package_notifier().
+ */
+void unregister_mp_package_notifier(struct notifier_block *notifier)
+{
+ blocking_notifier_chain_unregister(&mp_package_algorithms, notifier);
+}
+EXPORT_SYMBOL_GPL(unregister_mp_package_notifier);
+
+/**
+ * mp_probe_package_id - Invoke registered notifiers to resolve a node's package
+ * @nid: NUMA node id to resolve.
+ *
+ * Calls the blocking notifier chain to let subsystems provide an initiator or
+ * package id for @nid.
+ *
+ * Return: Notifier return code (>=0 typically); negative errno on failure.
+ */
+int mp_probe_package_id(int nid)
+{
+ return blocking_notifier_call_chain(&mp_package_algorithms, nid, NULL);
+}
+EXPORT_SYMBOL_GPL(mp_probe_package_id);
+
+static int mp_node_to_package_id(int nid)
+{
+ int package_id;
+ unsigned int first_cpu;
+ const struct cpumask *cpu_mask;
+
+ if (nid < 0 || nid >= MAX_NUMNODES)
+ return -EINVAL;
+
+ if (!node_state(nid, N_CPU))
+ return -EINVAL;
+
+ cpu_mask = cpumask_of_node(nid);
+ if (cpumask_empty(cpu_mask))
+ return -EINVAL;
+
+ first_cpu = cpumask_first(cpu_mask);
+ if (first_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ package_id = topology_physical_package_id(first_cpu);
+ if (package_id < 0)
+ return -EINVAL;
+
+ return package_id;
+}
+
+static void update_package_preferred(struct memory_package *mp)
+{
+ struct memory_package_node *mpn;
+
+ lockdep_assert_held(&memory_package_lock);
+
+ /*
+ * For each CPU node, compute its preferred set as the nearest
+ * memory-only node(s) within the same package. If the package has
+ * no memory-only nodes, fall back to a self-reference so callers
+ * never see an empty preferred set.
+ */
+ list_for_each_entry(mpn, &mp->cpu_list, package_entry) {
+ nodes_clear(mpn->preferred);
+ if (!nodes_empty(mp->memory_only_nodes))
+ nearest_nodes_nodemask(mpn->nid, &mp->memory_only_nodes,
+ &mpn->preferred);
+ else
+ node_set(mpn->nid, mpn->preferred);
+ }
+
+ /*
+ * Symmetrically, for each memory-only node, compute its preferred set
+ * as the nearest CPU node(s) within the same package. If the package
+ * has no CPU nodes, fall back to a self-reference.
+ */
+ list_for_each_entry(mpn, &mp->memory_only_list, package_entry) {
+ nodes_clear(mpn->preferred);
+ if (!nodes_empty(mp->cpu_nodes))
+ nearest_nodes_nodemask(mpn->nid, &mp->cpu_nodes,
+ &mpn->preferred);
+ else
+ node_set(mpn->nid, mpn->preferred);
+ }
+}
+
+static inline bool memory_package_is_empty(struct memory_package *mp)
+{
+ lockdep_assert_held(&memory_package_lock);
+
+ return (nodes_empty(mp->cpu_nodes) && nodes_empty(mp->memory_only_nodes));
+}
+
+static inline bool package_node_is_valid(int nid)
+{
+ if (!mpns[nid])
+ return false;
+
+ if (nodes_empty(mpns[nid]->preferred) || (mpns[nid]->package == NULL))
+ return false;
+
+ return true;
+}
+
+static const struct attribute_group *memory_package_groups[];
+
+/* Freed when the last reference to the package's sysfs device is dropped. */
+static void memory_package_release(struct device *dev)
+{
+ struct memory_package *mp = container_of(dev, struct memory_package, dev);
+
+ kfree(mp);
+}
+
+static struct memory_package *create_memory_package(int package_id)
+{
+ struct memory_package *mempackage;
+ int ret;
+
+ mempackage = kzalloc_obj(*mempackage);
+ if (!mempackage)
+ return ERR_PTR(-ENOMEM);
+
+ mempackage->package_id = package_id;
+ mempackage->nodes = NODE_MASK_NONE;
+ mempackage->cpu_nodes = NODE_MASK_NONE;
+ mempackage->memory_only_nodes = NODE_MASK_NONE;
+ INIT_LIST_HEAD(&mempackage->cpu_list);
+ INIT_LIST_HEAD(&mempackage->memory_only_list);
+ INIT_LIST_HEAD(&mempackage->list);
+ device_initialize(&mempackage->dev);
+ mempackage->dev.release = memory_package_release;
+ dev_set_drvdata(&mempackage->dev, mempackage);
+ mempackage->dev.bus = &package_subsys;
+ mempackage->dev.groups = memory_package_groups;
+ ret = dev_set_name(&mempackage->dev, "package%d", package_id);
+ if (ret) {
+ put_device(&mempackage->dev);
+ return ERR_PTR(ret);
+ }
+
+ return mempackage;
+}
+
+static struct memory_package *find_create_memory_package(int package_id)
+{
+ struct memory_package *mempackage, *existing;
+ int ret;
+
+ mutex_lock(&memory_package_lock);
+ list_for_each_entry(mempackage, &memory_packages, list) {
+ if (mempackage->package_id == package_id) {
+ mutex_unlock(&memory_package_lock);
+ return mempackage;
+ }
+ }
+ mutex_unlock(&memory_package_lock);
+
+ mempackage = create_memory_package(package_id);
+ if (IS_ERR(mempackage))
+ return mempackage;
+
+ mutex_lock(&memory_package_lock);
+ list_for_each_entry(existing, &memory_packages, list) {
+ if (existing->package_id == package_id) {
+ mutex_unlock(&memory_package_lock);
+ put_device(&mempackage->dev);
+ return existing;
+ }
+ }
+ list_add(&mempackage->list, &memory_packages);
+ mutex_unlock(&memory_package_lock);
+
+ ret = device_add(&mempackage->dev);
+ if (ret) {
+ mutex_lock(&memory_package_lock);
+ list_del(&mempackage->list);
+ mutex_unlock(&memory_package_lock);
+ put_device(&mempackage->dev);
+ return ERR_PTR(ret);
+ }
+
+ return mempackage;
+}
+
+static void mp_snapshot_rebuild(void)
+{
+ struct mp_snapshot *new, *old;
+ struct memory_package *mp;
+ int nr = 0, i = 0, nid;
+
+ lockdep_assert_held(&memory_package_lock);
+
+ list_for_each_entry(mp, &memory_packages, list)
+ nr++;
+
+ new = kvzalloc(struct_size(new, pkg, nr), GFP_KERNEL);
+ if (!new)
+ return;
+
+ memset(new->pkg_of, 0xff, sizeof(new->pkg_of));
+
+ list_for_each_entry(mp, &memory_packages, list) {
+ new->pkg[i].nodes = mp->nodes;
+ new->pkg[i].cpu_nodes = mp->cpu_nodes;
+ new->pkg[i].memory_only_nodes = mp->memory_only_nodes;
+ for_each_node_mask(nid, mp->nodes)
+ new->pkg_of[nid] = i;
+ i++;
+ }
+ new->nr_packages = nr;
+
+ old = rcu_replace_pointer(mp_snapshot, new,
+ lockdep_is_held(&memory_package_lock));
+ if (old)
+ kvfree_rcu(old, rcu);
+}
+
+static int bind_node_to_package(int nid)
+{
+ int package_id, pkg_id;
+ struct memory_package *mp;
+ nodemask_t nodes, cpu, mem;
+
+ mutex_lock(&memory_package_lock);
+ if (!mpns[nid]) {
+ mutex_unlock(&memory_package_lock);
+ return -EINVAL;
+ }
+ package_id = mpns[nid]->package_id;
+ mutex_unlock(&memory_package_lock);
+
+ mp = find_create_memory_package(package_id);
+ if (IS_ERR(mp))
+ return PTR_ERR(mp);
+
+ mutex_lock(&memory_package_lock);
+ if (!mpns[nid]) {
+ mutex_unlock(&memory_package_lock);
+ return -ENOENT;
+ }
+ mpns[nid]->package = mp;
+ node_set(mpns[nid]->nid, mp->nodes);
+ if (node_is_memory_only(mpns[nid]->nid)) {
+ node_set(mpns[nid]->nid, mp->memory_only_nodes);
+ list_add(&mpns[nid]->package_entry, &mp->memory_only_list);
+ } else {
+ node_set(mpns[nid]->nid, mp->cpu_nodes);
+ list_add(&mpns[nid]->package_entry, &mp->cpu_list);
+ }
+ update_package_preferred(mp);
+ mp_snapshot_rebuild();
+ pkg_id = mp->package_id;
+ nodes = mp->nodes;
+ cpu = mp->cpu_nodes;
+ mem = mp->memory_only_nodes;
+ mutex_unlock(&memory_package_lock);
+
+ pr_info("memory_package %d: nodes=%*pbl cpu=%*pbl memory_only=%*pbl\n",
+ pkg_id, nodemask_pr_args(&nodes),
+ nodemask_pr_args(&cpu), nodemask_pr_args(&mem));
+
+ return 0;
+}
+
+static void unbind_node_to_package(struct memory_package *mp, int nid)
+{
+ lockdep_assert_held(&memory_package_lock);
+
+ node_clear(nid, mp->nodes);
+ if (node_state(nid, N_CPU))
+ node_clear(nid, mp->cpu_nodes);
+ else
+ node_clear(nid, mp->memory_only_nodes);
+
+ if (mpns[nid])
+ list_del(&mpns[nid]->package_entry);
+
+ update_package_preferred(mp);
+}
+
+static struct memory_package_node *create_package_node(int nid, int initiator_nid)
+{
+ int cpu_nid, package_id;
+ int source_flags;
+ struct memory_package_node *mpn;
+
+ if (node_state(nid, N_CPU)) {
+ cpu_nid = nid;
+ source_flags = MPN_SRC_CPU;
+ } else {
+ if (initiator_nid >= 0) {
+ cpu_nid = initiator_nid;
+ source_flags = MPN_SRC_INITIATOR;
+ } else {
+ /*
+ * No driver-supplied initiator: fall back to the
+ * nearest CPU node (via SLIT/numa_distance).
+ */
+ cpu_nid = numa_nearest_node(nid, N_CPU);
+ source_flags = MPN_SRC_SLIT;
+ }
+ }
+
+ package_id = mp_node_to_package_id(cpu_nid);
+ if (package_id < 0)
+ return ERR_PTR(-EINVAL);
+
+ mpn = kzalloc_obj(*mpn);
+ if (!mpn)
+ return ERR_PTR(-ENOMEM);
+
+ mpn->nid = nid;
+ mpn->initiator_nid = cpu_nid;
+ mpn->package_id = package_id;
+ mpn->source_flags = source_flags;
+ mpn->preferred = NODE_MASK_NONE;
+ mpn->package = NULL;
+ INIT_LIST_HEAD(&mpn->package_entry);
+
+ return mpn;
+}
+
+/*
+ * Topology symmetry status
+ * Indicates whether all packages have identical node structure
+ * (same number of CPU nodes and memory-only nodes).
+ */
+static bool topology_symmetric;
+
+static void validate_topology_symmetry(void);
+
+static struct memory_package *__destroy_package_node(int nid)
+{
+ struct memory_package_node *mpn;
+ struct memory_package *mp, *unreg_mp = NULL;
+
+ lockdep_assert_held(&memory_package_lock);
+
+ mpn = mpns[nid];
+ if (!mpn)
+ return NULL;
+
+ mp = mpn->package;
+ if (mp) {
+ unbind_node_to_package(mp, nid);
+ mpn->package = NULL;
+
+ if (memory_package_is_empty(mp)) {
+ list_del(&mp->list);
+ unreg_mp = mp;
+ }
+ }
+
+ mpns[nid] = NULL;
+ kfree(mpn);
+ mp_snapshot_rebuild();
+
+ return unreg_mp;
+}
+
+static void destroy_package_node(int nid)
+{
+ struct memory_package *unreg_mp;
+
+ mutex_lock(&memory_package_lock);
+ unreg_mp = __destroy_package_node(nid);
+ mutex_unlock(&memory_package_lock);
+
+ if (unreg_mp)
+ device_unregister(&unreg_mp->dev);
+
+ validate_topology_symmetry();
+}
+
+static int find_package_node(int nid, int initiator_nid)
+{
+ struct memory_package *unreg_mp = NULL;
+ int ret = nid;
+
+ mutex_lock(&memory_package_lock);
+ if (!mpns[nid]) {
+ ret = NUMA_NO_NODE;
+ } else if (mpns[nid]->source_flags == MPN_SRC_SLIT && initiator_nid >= 0) {
+ /*
+ * SLIT-derived entries are provisional; if a driver later
+ * provides an explicit initiator, drop the provisional
+ * entry and rebuild with the stronger hint.
+ */
+ unreg_mp = __destroy_package_node(nid);
+ ret = NUMA_NO_NODE;
+ }
+ mutex_unlock(&memory_package_lock);
+
+ if (unreg_mp)
+ device_unregister(&unreg_mp->dev);
+
+ return ret;
+}
+
+static int find_create_package_node(int nid, int initiator_nid)
+{
+ int mpn_nid;
+ struct memory_package_node *mpn;
+
+ mpn_nid = find_package_node(nid, initiator_nid);
+ if (mpn_nid != NUMA_NO_NODE)
+ return mpn_nid;
+
+ mpn = create_package_node(nid, initiator_nid);
+ if (IS_ERR(mpn))
+ return PTR_ERR(mpn);
+
+ guard(mutex)(&memory_package_lock);
+ if (mpns[nid]) {
+ kfree(mpn);
+ return nid;
+ }
+ mpns[nid] = mpn;
+
+ return nid;
+}
+
+static int create_node_with_package(int nid)
+{
+ int ret;
+
+ ret = find_create_package_node(nid, NUMA_NO_NODE);
+ if (ret < 0)
+ return ret;
+
+ ret = bind_node_to_package(nid);
+ if (ret)
+ return ret;
+
+ validate_topology_symmetry();
+ return 0;
+}
+
+/**
+ * mp_add_package_node_by_initiator - Add a node with an initiator
+ * @nid: Target NUMA node to add.
+ * @initiator_nid: CPU nid used to resolve @nid's package (>=0).
+ *
+ * Ensures that a &struct memory_package_node exists for @nid and that its
+ * package_id is determined using @initiator_nid when provided. Binding to the
+ * package is not performed here.
+ *
+ * Return: 0 on success; negative errno on failure.
+ */
+int mp_add_package_node_by_initiator(int nid, int initiator_nid)
+{
+ int ret;
+
+ ret = find_create_package_node(nid, initiator_nid);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mp_add_package_node_by_initiator);
+
+/**
+ * mp_add_package_node - Add a node, resolving package automatically
+ * @nid: Target NUMA node to add.
+ *
+ * Wrapper over mp_add_package_node_by_initiator() that requests automatic
+ * initiator resolution (e.g., nearest CPU).
+ *
+ * Return: 0 on success; negative errno on failure.
+ */
+int mp_add_package_node(int nid)
+{
+ return mp_add_package_node_by_initiator(nid, NUMA_NO_NODE);
+}
+EXPORT_SYMBOL_GPL(mp_add_package_node);
+
+static int __mp_get_package_nodemask(int nid, enum mp_nodes_type node_type,
+ nodemask_t *out)
+{
+ struct mp_snapshot *snap;
+ int pkg;
+
+ if (!out)
+ return -EINVAL;
+
+ nodes_clear(*out);
+
+ if (nid < 0 || nid >= MAX_NUMNODES)
+ return -EINVAL;
+
+ guard(rcu)();
+
+ snap = rcu_dereference(mp_snapshot);
+ if (!snap)
+ return -ENOENT;
+
+ pkg = snap->pkg_of[nid];
+ if (pkg < 0)
+ return -ENOENT;
+
+ switch (node_type) {
+ case MP_NODES_ALL:
+ nodes_copy(*out, snap->pkg[pkg].nodes);
+ break;
+ case MP_NODES_CPU:
+ nodes_copy(*out, snap->pkg[pkg].cpu_nodes);
+ break;
+ case MP_NODES_MEM_ONLY:
+ nodes_copy(*out, snap->pkg[pkg].memory_only_nodes);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_NUMA_MIGRATION
+static int __mp_get_preferred_nodemask(int nid, enum mp_nodes_type node_type,
+ nodemask_t *out)
+{
+ int ret = 0;
+
+ /* No hot-path callers: the mutex is fine here. */
+ guard(mutex)(&memory_package_lock);
+
+ if (!out) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ nodes_clear(*out);
+
+ if (nid < 0 || nid >= MAX_NUMNODES) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (node_type == MP_NODES_CPU) {
+ if (node_is_memory_only(nid)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else if (node_type == MP_NODES_MEM_ONLY) {
+ if (!node_is_memory_only(nid)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!package_node_is_valid(nid)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ nodes_copy(*out, mpns[nid]->preferred);
+
+out:
+ return ret;
+}
+
+/**
+ * mp_next_demotion_nodemask - Demotion candidates within a package
+ * @nid: CPU node from which memory would be demoted.
+ * @out: Output nodemask of nearest memory-only targets in the same package.
+ *
+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.
+ */
+int mp_next_demotion_nodemask(int nid, nodemask_t *out)
+{
+ return __mp_get_preferred_nodemask(nid, MP_NODES_CPU, out);
+}
+EXPORT_SYMBOL_GPL(mp_next_demotion_nodemask);
+
+/**
+ * mp_next_demotion_node - Pick one demotion target
+ * @nid: CPU node from which memory would be demoted.
+ *
+ * Picks one target (random among the nearest) from mp_next_demotion_nodemask().
+ *
+ * Return: target nid on success, or NUMA_NO_NODE if no candidate is available.
+ */
+int mp_next_demotion_node(int nid)
+{
+ int target_nid;
+ nodemask_t target_nodemask;
+
+ if (mp_next_demotion_nodemask(nid, &target_nodemask))
+ return NUMA_NO_NODE;
+ if (nodes_empty(target_nodemask))
+ return NUMA_NO_NODE;
+
+ target_nid = node_random(&target_nodemask);
+
+ return target_nid;
+}
+EXPORT_SYMBOL_GPL(mp_next_demotion_node);
+
+/**
+ * mp_next_promotion_nodemask - Promotion candidates within a package
+ * @nid: Memory-only node towards which promotion seeks CPU locality.
+ * @out: Output nodemask of nearest CPU targets in the same package.
+ *
+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.
+ */
+int mp_next_promotion_nodemask(int nid, nodemask_t *out)
+{
+ return __mp_get_preferred_nodemask(nid, MP_NODES_MEM_ONLY, out);
+}
+EXPORT_SYMBOL_GPL(mp_next_promotion_nodemask);
+
+/**
+ * mp_next_promotion_node - Pick one promotion target
+ * @nid: Memory-only node to be promoted towards CPUs.
+ *
+ * Picks one target (random among the nearest) from mp_next_promotion_nodemask().
+ *
+ * Return: target nid on success, or NUMA_NO_NODE if no candidate is available.
+ */
+int mp_next_promotion_node(int nid)
+{
+ int target_nid;
+ nodemask_t target_nodemask;
+
+ if (mp_next_promotion_nodemask(nid, &target_nodemask))
+ return NUMA_NO_NODE;
+ if (nodes_empty(target_nodemask))
+ return NUMA_NO_NODE;
+
+ target_nid = node_random(&target_nodemask);
+
+ return target_nid;
+}
+EXPORT_SYMBOL_GPL(mp_next_promotion_node);
+#endif /* CONFIG_NUMA_MIGRATION */
+
+/**
+ * mp_get_package_nodes - Return all members of @nid's package
+ * @nid: Any NUMA node in the package.
+ * @out: Output nodemask to receive all members.
+ *
+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.
+ */
+int mp_get_package_nodes(int nid, nodemask_t *out)
+{
+ return __mp_get_package_nodemask(nid, MP_NODES_ALL, out);
+}
+EXPORT_SYMBOL_GPL(mp_get_package_nodes);
+
+/**
+ * mp_get_package_cpu_nodes - Return CPU members of @nid's package
+ * @nid: Any NUMA node in the package.
+ * @out: Output nodemask to receive CPU members.
+ *
+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.
+ */
+int mp_get_package_cpu_nodes(int nid, nodemask_t *out)
+{
+ return __mp_get_package_nodemask(nid, MP_NODES_CPU, out);
+}
+EXPORT_SYMBOL_GPL(mp_get_package_cpu_nodes);
+
+/**
+ * mp_get_package_memory_only_nodes - Return memory-only members of @nid's package
+ * @nid: Any NUMA node in the package.
+ * @out: Output nodemask to receive memory-only members.
+ *
+ * Return: 0 on success; negative errno if @nid is invalid or not initialized.
+ */
+int mp_get_package_memory_only_nodes(int nid, nodemask_t *out)
+{
+ return __mp_get_package_nodemask(nid, MP_NODES_MEM_ONLY, out);
+}
+EXPORT_SYMBOL_GPL(mp_get_package_memory_only_nodes);
+
+static int __meminit mp_hotplug_callback(struct notifier_block *nb,
+ unsigned long action, void *_arg)
+{
+ int nid;
+ struct node_notify *nn = _arg;
+
+ nid = nn->nid;
+ if (nid < 0)
+ return notifier_from_errno(0);
+
+ switch (action) {
+ case NODE_REMOVED_LAST_MEMORY:
+ destroy_package_node(nid);
+ break;
+
+ case NODE_ADDED_FIRST_MEMORY:
+ create_node_with_package(nid);
+ break;
+
+ default:
+ break;
+ }
+
+ return notifier_from_errno(0);
+}
+
+/*
+ * sysfs interface for memory_package topology
+ * Read-only attributes:
+ * - package_nodes: All NUMA nodes in this package (node mask)
+ * - package_cpu_nodes: CPU nodes in this package (node mask)
+ * - package_mem_only_nodes: Memory-only nodes in this package (node mask)
+ * - physical_package_id: Physical package ID
+ */
+
+static ssize_t package_nodes_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct memory_package *mp = dev_get_drvdata(dev);
+
+ guard(mutex)(&memory_package_lock);
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&mp->nodes));
+}
+static DEVICE_ATTR_RO(package_nodes);
+
+static ssize_t package_cpu_nodes_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct memory_package *mp = dev_get_drvdata(dev);
+
+ guard(mutex)(&memory_package_lock);
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&mp->cpu_nodes));
+}
+static DEVICE_ATTR_RO(package_cpu_nodes);
+
+static ssize_t package_mem_only_nodes_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct memory_package *mp = dev_get_drvdata(dev);
+
+ guard(mutex)(&memory_package_lock);
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&mp->memory_only_nodes));
+}
+static DEVICE_ATTR_RO(package_mem_only_nodes);
+
+static ssize_t physical_package_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct memory_package *mp = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", mp->package_id);
+}
+static DEVICE_ATTR_RO(physical_package_id);
+
+static struct attribute *memory_package_attrs[] = {
+ &dev_attr_package_nodes.attr,
+ &dev_attr_package_cpu_nodes.attr,
+ &dev_attr_package_mem_only_nodes.attr,
+ &dev_attr_physical_package_id.attr,
+ NULL,
+};
+
+static const struct attribute_group memory_package_group = {
+ .attrs = memory_package_attrs,
+};
+
+static const struct attribute_group *memory_package_groups[] = {
+ &memory_package_group,
+ NULL,
+};
+
+static int __init memory_package_sysfs_init(void)
+{
+ int ret;
+
+ ret = subsys_system_register(&package_subsys, NULL);
+ if (ret) {
+ pr_err("memory_package subsys_system_register failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+core_initcall(memory_package_sysfs_init);
+
+/**
+ * mp_is_topology_symmetric - Check if all packages have identical node structure
+ *
+ * Returns true if all memory packages have the same number of CPU nodes
+ * and memory-only nodes, indicating a symmetric topology.
+ *
+ * The topology_symmetric variable is protected by memory_package_lock for
+ * writes (in validate_topology_symmetry).
+ */
+bool mp_is_topology_symmetric(void)
+{
+ return READ_ONCE(topology_symmetric);
+}
+EXPORT_SYMBOL_GPL(mp_is_topology_symmetric);
+
+/* Walk the package list and compare node counts; the caller holds the lock. */
+static bool __check_topology_symmetry(void)
+{
+ struct memory_package *mp;
+ int ref_cpu_node_count = 0, ref_memory_only_node_count = 0;
+ int package_count = 0;
+
+ lockdep_assert_held(&memory_package_lock);
+
+ list_for_each_entry(mp, &memory_packages, list) {
+ int cpu_node_count = nodes_weight(mp->cpu_nodes);
+ int memory_only_node_count = nodes_weight(mp->memory_only_nodes);
+ int total_node_count = cpu_node_count + memory_only_node_count;
+
+ package_count++;
+
+ /* Each package must have at least 2 nodes */
+ if (total_node_count < 2)
+ return false;
+
+ if (package_count == 1) {
+ ref_cpu_node_count = cpu_node_count;
+ ref_memory_only_node_count = memory_only_node_count;
+ } else if (cpu_node_count != ref_cpu_node_count ||
+ memory_only_node_count != ref_memory_only_node_count) {
+ return false;
+ }
+ }
+
+ if (package_count < 2)
+ return false;
+
+ return true;
+}
+
+/**
+ * validate_topology_symmetry - Validate topology for package-aware features
+ *
+ * Validates topology symmetry by checking all memory packages have identical
+ * node structure. Updates the global topology_symmetric variable.
+ *
+ * Topology is valid only when all conditions are met:
+ * 1. At least 2 packages exist (multi-package system)
+ * 2. All packages have identical cpu_node and memory_only_node counts
+ * 3. Each package has at least 2 total nodes (cpu_node + memory_only_node >= 2)
+ *
+ * Valid configurations per package:
+ * - 1 cpu_node + 1 memory_only_node = 2 nodes (valid)
+ * - 2 cpu_nodes + 0 memory_only_node = 2 nodes (valid)
+ * - 0 cpu_node + 2 memory_only_nodes = 2 nodes (valid)
+ * - 1 cpu_node + 0 memory_only_node = 1 node (invalid - single node package)
+ */
+static void validate_topology_symmetry(void)
+{
+ guard(mutex)(&memory_package_lock);
+
+ WRITE_ONCE(topology_symmetric, __check_topology_symmetry());
+}
+
+static int __init memory_package_init(void)
+{
+ int ret = 0, nid;
+
+ for_each_online_node(nid) {
+ if (!node_state(nid, N_MEMORY))
+ continue;
+
+ ret = create_node_with_package(nid);
+ if (ret)
+ goto out;
+ }
+
+ hotplug_node_notifier(mp_hotplug_callback, MEMTIER_HOTPLUG_PRI);
+
+ validate_topology_symmetry();
+
+out:
+ return ret;
+}
+late_initcall(memory_package_init);
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 4e4421b22b59f..66bccb9a0a191 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -117,6 +117,7 @@
#include <asm/tlb.h>
#include <linux/uaccess.h>
#include <linux/memory.h>
+#include <linux/memory-tiers.h>
#include "internal.h"
@@ -167,6 +168,8 @@ static unsigned int *node_bw_table;
*/
static DEFINE_MUTEX(wi_state_lock);
+static bool package_mode_enabled;
+
static u8 get_il_weight(int node)
{
struct weighted_interleave_state *state;
@@ -180,6 +183,11 @@ static u8 get_il_weight(int node)
return weight;
}
+static bool wi_package_mode_enabled(void)
+{
+ return READ_ONCE(package_mode_enabled) && mp_is_topology_symmetric();
+}
+
/*
* Convert bandwidth values into weighted interleave weights.
* Call with wi_state_lock.
@@ -337,6 +345,47 @@ int nearest_node_nodemask(int node, nodemask_t *mask)
}
EXPORT_SYMBOL_GPL(nearest_node_nodemask);
+/**
+ * nearest_nodes_nodemask - Find all nodes in @mask that are nearest to @node
+ * @node: The reference node ID to measure distance from
+ * @mask: The set of candidate nodes to compare against
+ * @out: Pointer to a nodemask that will store the nearest node(s)
+ *
+ * This function iterates over all nodes in @mask and measures the distance
+ * between each candidate node and the given @node using node_distance().
+ * It finds the minimum distance and then records all nodes in @mask that
+ * share that same minimum distance into the output mask @out.
+ *
+ * For example, if multiple nodes have equal minimal distance to @node, all
+ * of them are included in @out.
+ *
+ * Return: 0 on success, or -EINVAL if @out is NULL.
+ */
+int nearest_nodes_nodemask(int node, const nodemask_t *mask, nodemask_t *out)
+{
+ int dist, n, min_dist = INT_MAX;
+
+ if (!out)
+ return -EINVAL;
+
+ nodes_clear(*out);
+
+ for_each_node_mask(n, *mask) {
+ dist = node_distance(node, n);
+
+ if (dist < min_dist) {
+ min_dist = dist;
+ nodes_clear(*out);
+ node_set(n, *out);
+ } else if (dist == min_dist) {
+ node_set(n, *out);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nearest_nodes_nodemask);
+
struct mempolicy *get_task_policy(struct task_struct *p)
{
struct mempolicy *pol = p->mempolicy;
@@ -2097,17 +2146,97 @@ bool apply_policy_zone(struct mempolicy *policy, enum zone_type zone)
return zone >= dynamic_policy_zone;
}
+/**
+ * policy_resolve_package_nodes - Restrict policy nodes to the current package
+ * @policy: Target mempolicy whose user-selected nodes are in @policy->nodes.
+ * @mask: Output nodemask. On success, contains policy->nodes limited to
+ * the package that should be used for the allocation.
+ *
+ * This helper combines two constraints to decide where within a package
+ * memory may be allocated:
+ *
+ * 1) The caller's package: derived via mp_get_package_nodes(numa_node_id()).
+ * 2) The user's preselected set @policy->nodes (cpusets/mempolicy).
+ *
+ * The function obtains the nodemask of the current CPU's package and
+ * intersects it with @policy->nodes. If the intersection is empty (e.g. the
+ * user excluded every node of the current package), it falls back to the
+ * node in @policy->nodes, derives that node's package, and intersects
+ * again. If the fallback also yields an empty set, @mask stays empty and a
+ * non-zero error is returned.
+ *
+ * Examples (packages: P0={CPU:0, MEM:2}, P1={CPU:1, MEM:3}):
+ * - policy->nodes = {0,1,2,3}
+ * on P0: mask = {0,2}; on P1: mask = {1,3}.
+ * - policy->nodes = {0,1,3}
+ * on P0: mask = {0} (only node 0 from P0 is allowed).
+ * - policy->nodes = {1,2,3}
+ * on P0: mask = {2} (only node 2 from P0 is allowed).
+ * - policy->nodes = {1,3}
+ * on P0: current package (P0) & policy = NULL -> fallback to policy=1,
+ * package(1)=P1, mask = {1,3}. (User effectively opted out of P0.)
+ *
+ * If the selected node is low on memory, the allocation may use another node.
+ *
+ * Return:
+ * 0 on success with @mask set as above;
+ * -EINVAL if @policy/@mask is NULL;
+ * -ENOENT if even the fallback intersection is empty;
+ * Propagated error from mp_get_package_nodes() on failure.
+ */
+static int policy_resolve_package_nodes(struct mempolicy *policy, nodemask_t *mask)
+{
+ nodemask_t package_mask;
+ int node, ret;
+
+ if (!policy || !mask)
+ return -EINVAL;
+
+ nodes_clear(*mask);
+
+ node = numa_node_id();
+ ret = mp_get_package_nodes(node, &package_mask);
+ if (ret)
+ return ret;
+
+ nodes_and(*mask, package_mask, policy->nodes);
+ if (!nodes_empty(*mask))
+ return 0;
+
+ /*
+ * The user's nodemask excludes every node of the current package;
+ * fall back to the package spanned by the user's own first node.
+ */
+ node = first_node(policy->nodes);
+ ret = mp_get_package_nodes(node, &package_mask);
+ if (ret)
+ return ret;
+
+ nodes_and(*mask, package_mask, policy->nodes);
+ if (nodes_empty(*mask))
+ return -ENOENT;
+
+ return 0;
+}
+
static unsigned int weighted_interleave_nodes(struct mempolicy *policy)
{
unsigned int node;
unsigned int cpuset_mems_cookie;
+ nodemask_t mask;
retry:
/* to prevent miscount use tsk->mems_allowed_seq to detect rebind */
cpuset_mems_cookie = read_mems_allowed_begin();
node = current->il_prev;
- if (!current->il_weight || !node_isset(node, policy->nodes)) {
- node = next_node_in(node, policy->nodes);
+
+ /* Package mode off or unresolved: fall back to the full policy nodemask. */
+ if (!wi_package_mode_enabled() ||
+ policy_resolve_package_nodes(policy, &mask))
+ mask = policy->nodes;
+
+ if (!current->il_weight || !node_isset(node, mask)) {
+ node = next_node_in(node, mask);
if (read_mems_allowed_retry(cpuset_mems_cookie))
goto retry;
if (node == MAX_NUMNODES)
@@ -2200,6 +2329,30 @@ static unsigned int read_once_policy_nodemask(struct mempolicy *pol,
return nodes_weight(*mask);
}
+/*
+ * Package-aware counterpart of read_once_policy_nodemask(): resolve the
+ * current package's nodes intersected with the policy, falling back to the
+ * full policy nodemask when package mode is off or resolution fails.
+ */
+static unsigned int read_once_policy_package_nodemask(struct mempolicy *pol,
+ nodemask_t *mask)
+{
+ nodemask_t package_mask;
+
+ barrier();
+ if (!wi_package_mode_enabled()) {
+ memcpy(mask, &pol->nodes, sizeof(nodemask_t));
+ return nodes_weight(*mask);
+ }
+ if (policy_resolve_package_nodes(pol, &package_mask))
+ memcpy(mask, &pol->nodes, sizeof(nodemask_t));
+ else
+ memcpy(mask, &package_mask, sizeof(nodemask_t));
+ barrier();
+
+ return nodes_weight(*mask);
+}
+
static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)
{
struct weighted_interleave_state *state;
@@ -2210,7 +2363,7 @@ static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)
u8 weight;
int nid = 0;
- nr_nodes = read_once_policy_nodemask(pol, &nodemask);
+ nr_nodes = read_once_policy_package_nodemask(pol, &nodemask);
if (!nr_nodes)
return numa_node_id();
@@ -2654,7 +2807,7 @@ static unsigned long alloc_pages_bulk_weighted_interleave(gfp_t gfp,
/* read the nodes onto the stack, retry if done during rebind */
do {
cpuset_mems_cookie = read_mems_allowed_begin();
- nnodes = read_once_policy_nodemask(pol, &nodes);
+ nnodes = read_once_policy_package_nodemask(pol, &nodes);
} while (read_mems_allowed_retry(cpuset_mems_cookie));
/* if the nodemask has become invalid, we cannot do anything */
@@ -3794,7 +3947,42 @@ static struct kobj_attribute wi_auto_attr = {
.store = weighted_interleave_auto_store,
};
+static ssize_t package_mode_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", str_true_false(READ_ONCE(package_mode_enabled)));
+}
+
+static ssize_t package_mode_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ bool input;
+ int err;
+
+ err = kstrtobool(buf, &input);
+ if (err)
+ return err;
+
+ /*
+ * Disable package-aware weighted interleave on non-symmetric topologies.
+ * Non-symmetric topology (e.g., asymmetric CXL memory attachment) can
+ * cause performance degradation if package-aware allocation is used.
+ * Reject enable request if topology is not symmetric.
+ */
+ if (input && !mp_is_topology_symmetric()) {
+ pr_warn("package_mode cannot be enabled on non-symmetric topology\n");
+ return -EINVAL;
+ }
+
+ WRITE_ONCE(package_mode_enabled, input);
+ return count;
+}
+
+static struct kobj_attribute wi_package_mode_attr =
+ __ATTR(package_mode, 0664, package_mode_show, package_mode_store);
+
static void wi_cleanup(void) {
+ sysfs_remove_file(&wi_group->wi_kobj, &wi_package_mode_attr.attr);
sysfs_remove_file(&wi_group->wi_kobj, &wi_auto_attr.attr);
sysfs_wi_node_delete_all();
wi_state_free();
@@ -3900,6 +4088,10 @@ static int __init add_weighted_interleave_group(struct kobject *mempolicy_kobj)
if (err)
goto err_put_kobj;
+ err = sysfs_create_file(&wi_group->wi_kobj, &wi_package_mode_attr.attr);
+ if (err)
+ goto err_cleanup_kobj;
+
for_each_online_node(nid) {
if (!node_state(nid, N_MEMORY))
continue;
|
| 3/2 | 2026/08/06 08:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19816 output: 30 thoughts: 760 Thoughts: **Analyzing the Kernel Memory Package Patch** Okay, so I'm looking at this patch introducing a "memory package" topology concept within the kernel, primarily for NUMA nodes and, importantly, CXL/HBM memory configurations. It's touching some critical areas: `mm/memory-tiers.c`, `drivers/cxl/core/region.c`, `drivers/dax/kmem.c`, and `mm/mempolicy.c`. This is a pretty significant functional change – core memory management stuff here, dealing with NUMA memory tiers, CXL regions, and the mempolicy subsystem. It’s definitely touching reachable core kernel logic, making fuzzing a high priority. My initial thought is to dive into it and identify the key functions that are added or modified, focusing on potential areas of vulnerability or unexpected behavior. Let's see... the patch is introducing a sysfs interface for these memory packages under `/sys/devices/system/package/` and a new weighted interleave mode for mempolicy, accessible via `/sys/kernel/mm/mempolicy/weighted_interleave/package_mode`. I'm making a list of the specific functions impacted: `cxl_region_add_package_node`, `mp_probe_package_id`, the notifier functions, the package node manipulation functions (`mp_add_package_node`, `mp_get_package_nodes`, etc.), and the interleave and promotion/demotion functions. I see a lot of activity within `mm/memory-tiers.c` and `mm/mempolicy.c`, so I'll need to scrutinize those modules carefully to understand the call paths and dependencies. Now, my focus narrows to the exported and reachable functions within `mm/memory-tiers.c` and `mm/mempolicy.c`, namely: `mp_add_package_node_by_initiator`, `mp_add_package_node`, `mp_get_package_nodes`, `mp_get_package_cpu_nodes`, `mp_get_package_memory_only_nodes`, `mp_next_demotion_nodemask`, `mp_next_demotion_node`, `mp_next_promotion_nodemask`, `mp_next_promotion_node`, `mp_is_topology_symmetric` and `nearest_nodes_nodemask`. These are my primary targets. Given the nature of the changes, especially the NUMA focus, I need to check the configuration dependencies. Naturally, this code is heavily NUMA-dependent. Let's make sure `CONFIG_NUMA` is enabled. Further, I should see if `CONFIG_NUMA_BALANCING`, `CONFIG_NUMA_MIGRATION`, `CONFIG_MEMORY_TIERS`, or `CONFIG_CXL_BUS` are dependencies. I'll quickly grep for the most relevant ones. Okay, so that should give me a good handle on where to focus my initial fuzzing efforts. |
| 4/2 | 2026/08/06 08:28 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_NUMA_MIGRATION",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/memory-tiers.c=71=bool folio_use_access_time(struct folio *folio)\n--\nmm/memory-tiers.c-77-\nmm/memory-tiers.c:78:#ifdef CONFIG_NUMA_MIGRATION\nmm/memory-tiers.c-79-static int top_tier_adistance;\n--\nmm/memory-tiers.c=137=static struct demotion_nodes *node_demotion __read_mostly;\nmm/memory-tiers.c:138:#endif /* CONFIG_NUMA_MIGRATION */\nmm/memory-tiers.c-139-\n--\nmm/memory-tiers.c=266=static struct memory_tier *__node_get_memory_tier(int node)\n--\nmm/memory-tiers.c-281-\nmm/memory-tiers.c:282:#ifdef CONFIG_NUMA_MIGRATION\nmm/memory-tiers.c-283-bool node_is_toptier(int node)\n--\nmm/memory-tiers.c=527=static inline void establish_demotion_targets(void) {}\nmm/memory-tiers.c:528:#endif /* CONFIG_NUMA_MIGRATION */\nmm/memory-tiers.c-529-\n--\nmm/memory-tiers.c=912=static int __init memory_tier_init(void)\n--\nmm/memory-tiers.c-919-\nmm/memory-tiers.c:920:#ifdef CONFIG_NUMA_MIGRATION\nmm/memory-tiers.c-921-\tnode_demotion = kzalloc_objs(struct demotion_nodes, nr_node_ids);\n--\nmm/memory-tiers.c=945=bool numa_demotion_enabled = false;\nmm/memory-tiers.c-946-\nmm/memory-tiers.c:947:#ifdef CONFIG_NUMA_MIGRATION\nmm/memory-tiers.c-948-#ifdef CONFIG_SYSFS\n--\nmm/memory-tiers.c=1618=static int __mp_get_package_nodemask(int nid, enum mp_nodes_type node_type,\n--\nmm/memory-tiers.c-1658-\nmm/memory-tiers.c:1659:#ifdef CONFIG_NUMA_MIGRATION\nmm/memory-tiers.c-1660-static int __mp_get_preferred_nodemask(int nid, enum mp_nodes_type node_type,\n--\nmm/memory-tiers.c=1778=EXPORT_SYMBOL_GPL(mp_next_promotion_node);\nmm/memory-tiers.c:1779:#endif /* CONFIG_NUMA_MIGRATION */\nmm/memory-tiers.c-1780-\n--\nmm/mempolicy.c=1196=static long do_get_mempolicy(int *policy, nodemask_t *nmask,\n--\nmm/mempolicy.c-1296-\nmm/mempolicy.c:1297:#ifdef CONFIG_NUMA_MIGRATION\nmm/mempolicy.c-1298-static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,\n--\nmm/migrate.c=2188=struct folio *alloc_migration_target(struct folio *src, unsigned long private)\n--\nmm/migrate.c-2226-\nmm/migrate.c:2227:#ifdef CONFIG_NUMA_MIGRATION\nmm/migrate.c-2228-static int store_status(int __user *status, int start, int value, int nr)\n--\nmm/migrate.c=2619=SYSCALL_DEFINE6(move_pages, pid_t, pid, unsigned long, nr_pages,\n--\nmm/migrate.c-2625-}\nmm/migrate.c:2626:#endif /* CONFIG_NUMA_MIGRATION */\nmm/migrate.c-2627-\n"
} |
| 5/2 | 2026/08/06 08:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22227 output: 26 thoughts: 28 Thoughts: Okay, here's my take on those config options, thinking about them as a seasoned expert: **Investigating Memory Architecture Configuration** Right, let's dive into these configuration options. I'm immediately thinking about how they interplay and what architectural implications they suggest. First, `CONFIG_NUMA`. That's a fundamental given these days, but it's always worth verifying it's enabled and configured correctly for the target system. NUMA fundamentally impacts memory access latency, and understanding its implications is crucial for performance-sensitive applications. I need to keep in mind how the memory policies will be defined, how the scheduler will work, and how the OS will manage memory allocation across different NUMA nodes. Next, `CONFIG_MEMORY_TIERS`. This is where things get interesting. This suggests a multi-tiered memory architecture is in play – think DRAM, persistent memory, or even different types of DRAM with varying performance characteristics. If this is enabled, I immediately start considering the implications for memory management, data placement, and how the kernel handles the differences in access speed and persistence. The potential for cache-aware algorithms and NUMA-aware placement becomes extremely important. Finally, `CONFIG_CXL_BUS`. Now, this is exciting. CXL (Compute Express Link) opens up a whole new world of possibilities, enabling coherent memory sharing and acceleration between the CPU and devices. With CXL enabled, I have to think about how CXL devices will be integrated into the memory hierarchy. Are we talking about CXL-attached memory, accelerators, or a combination? This config option raises questions about cache coherency protocols, the implications for DMA, and how this will be presented to the system. It also means I need to carefully analyze the driver support and any relevant firmware considerations. This has some very deep implications for system design, and I need to be sure I understand the interaction between the CXL-based memory and the existing NUMA topology. This configuration requires a holistic understanding of the whole hardware and software stack. |
| 6/2 | 2026/08/06 08:28 | tool | grepper |
0mError:no matches Args: {
"Expression": "config MEMORY_TIERS",
"PathPrefix": ""
}Results: {
"error": "no matches"
} |
| 7/2 | 2026/08/06 08:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22711 output: 27 thoughts: 22 Thoughts: Here's a summary of my thought process: **Kconfig Dive for NUMA/MEMORY_TIERS** Okay, so I need to figure out how these NUMA and MEMORY_TIERS configurations are handled. Given my experience, the first place I always go in a kernel build is the `Kconfig` files. That's the central configuration hub, right? So, I'm thinking, "Let's check `mm/Kconfig`." That's the most logical starting point for memory management options. I'll search for both `NUMA` and `MEMORY_TIERS` to see what I can find. It's almost guaranteed the definitions and dependencies will be right there. I'm expecting to find the relevant symbols, descriptions, and any associated dependencies, like architecture-specific options or other memory management features that might be intertwined. This should give me a solid foundation for understanding how these features are enabled and managed within the kernel build system. From there, I can track down the source files they impact. |
| 8/2 | 2026/08/06 08:28 | tool | grepper |
0mArgs:{
"Expression": "config NUMA",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=442=config ARCH_KEEP_MEMBLOCK\n--\nmm/Kconfig-445-# Keep arch NUMA mapping infrastructure post-init.\nmm/Kconfig:446:config NUMA_KEEP_MEMINFO\nmm/Kconfig-447-\tbool\n--\nmm/Kconfig=624=config PAGE_REPORTING\n--\nmm/Kconfig-631-\nmm/Kconfig:632:config NUMA_MIGRATION\nmm/Kconfig-633-\tbool \"NUMA page migration\"\n--\nmm/Kconfig=1418=config EXECMEM\n--\nmm/Kconfig-1420-\nmm/Kconfig:1421:config NUMA_MEMBLKS\nmm/Kconfig-1422-\tbool\nmm/Kconfig-1423-\nmm/Kconfig:1424:config NUMA_EMU\nmm/Kconfig-1425-\tbool \"NUMA emulation\"\n"
} |
| 9/2 | 2026/08/06 08:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23228 output: 23 thoughts: 24 Thoughts: Here's my thought process, summarized as if I were considering this myself: **Kconfig Location Check** Okay, so I'm thinking about `CONFIG_NUMA`. That one should be pretty straightforward, but I need to quickly recall where it typically resides within the kernel source tree. My mental map usually places these architecture-specific configurations – particularly those related to NUMA – in either `arch/Kconfig` (under the relevant architecture directory, naturally) or, if it's more general initialization related, `init/Kconfig`. I'd start there, since that's where I'd expect it, given its function. No sense in wasting time searching other areas if the logical locations are known. |
| 10/2 | 2026/08/06 08:28 | tool | grepper |
0mArgs:{
"Expression": "config NUMA",
"PathPrefix": ""
}Results: {
"Output": "arch/arm64/Kconfig=1515=config HOTPLUG_CPU\n--\narch/arm64/Kconfig-1522-# Common NUMA Features\narch/arm64/Kconfig:1523:config NUMA\narch/arm64/Kconfig-1524-\tbool \"NUMA Memory Allocation and Scheduler Support\"\n--\narch/loongarch/Kconfig=543=config NR_CPUS\n--\narch/loongarch/Kconfig-551-\narch/loongarch/Kconfig:552:config NUMA\narch/loongarch/Kconfig-553-\tbool \"NUMA Support\"\n--\narch/mips/Kconfig=2609=config ARCH_SPARSEMEM_ENABLE\n--\narch/mips/Kconfig-2611-\narch/mips/Kconfig:2612:config NUMA\narch/mips/Kconfig-2613-\tbool \"NUMA Support\"\n--\narch/powerpc/Kconfig=755=config IRQ_ALL_CPUS\n--\narch/powerpc/Kconfig-763-\narch/powerpc/Kconfig:764:config NUMA\narch/powerpc/Kconfig-765-\tbool \"NUMA Memory Allocation and Scheduler Support\"\n--\narch/riscv/Kconfig=478=endchoice\n--\narch/riscv/Kconfig-480-# Common NUMA Features\narch/riscv/Kconfig:481:config NUMA\narch/riscv/Kconfig-482-\tbool \"NUMA Memory Allocation and Scheduler Support\"\n--\narch/s390/Kconfig=537=config HOTPLUG_CPU\n--\narch/s390/Kconfig-539-\narch/s390/Kconfig:540:config NUMA\narch/s390/Kconfig-541-\tbool \"NUMA support\"\n--\narch/sh/mm/Kconfig=102=config VSYSCALL\n--\narch/sh/mm/Kconfig-114-\narch/sh/mm/Kconfig:115:config NUMA\narch/sh/mm/Kconfig-116-\tbool \"Non-Uniform Memory Access (NUMA) Support\"\n--\narch/sparc/Kconfig=255=config GENERIC_LOCKBREAK\n--\narch/sparc/Kconfig-259-\narch/sparc/Kconfig:260:config NUMA\narch/sparc/Kconfig-261-\tbool \"NUMA support\"\n--\narch/x86/Kconfig=1501=config AMD_MEM_ENCRYPT\n--\narch/x86/Kconfig-1517-# Common NUMA Features\narch/x86/Kconfig:1518:config NUMA\narch/x86/Kconfig-1519-\tbool \"NUMA Memory Allocation and Scheduler Support\"\n--\ninit/Kconfig=1007=config ARCH_WANT_NUMA_VARIABLE_LOCALITY\n--\ninit/Kconfig-1009-\ninit/Kconfig:1010:config NUMA_BALANCING\ninit/Kconfig-1011-\tbool \"Memory placement aware NUMA scheduler\"\n--\ninit/Kconfig-1021-\ninit/Kconfig:1022:config NUMA_BALANCING_DEFAULT_ENABLED\ninit/Kconfig-1023-\tbool \"Automatically enable NUMA aware memory/task placement\"\n--\nmm/Kconfig=442=config ARCH_KEEP_MEMBLOCK\n--\nmm/Kconfig-445-# Keep arch NUMA mapping infrastructure post-init.\nmm/Kconfig:446:config NUMA_KEEP_MEMINFO\nmm/Kconfig-447-\tbool\n--\nmm/Kconfig=624=config PAGE_REPORTING\n--\nmm/Kconfig-631-\nmm/Kconfig:632:config NUMA_MIGRATION\nmm/Kconfig-633-\tbool \"NUMA page migration\"\n--\nmm/Kconfig=1418=config EXECMEM\n--\nmm/Kconfig-1420-\nmm/Kconfig:1421:config NUMA_MEMBLKS\nmm/Kconfig-1422-\tbool\nmm/Kconfig-1423-\nmm/Kconfig:1424:config NUMA_EMU\nmm/Kconfig-1425-\tbool \"NUMA emulation\"\n"
} |
| 11/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24532 output: 26 thoughts: 37 Thoughts: Okay, let's see. Here's how I'm thinking through this: **Checking NUMA Configuration Dependencies** Alright, first things first, I need to confirm the status of `CONFIG_NUMA` and `CONFIG_NUMA_MIGRATION`. Gotta get those base dependencies straight before I dig any deeper. These are fundamental for NUMA support, so I'm assuming that it's important that they're set to what I anticipate. Now, hold on a second... where does `mm/memory-tiers.c` actually come into play? When is that thing compiled? I need to understand its build-time context to make sure everything's lined up correctly with whatever NUMA features are in the mix. Understanding the compilation process is essential here. I need to be sure that the build configurations work as intended. |
| 12/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "memory-tiers",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=98=obj-$(CONFIG_MIGRATION) += migrate.o\nmm/Makefile:99:obj-$(CONFIG_NUMA) += memory-tiers.o\nmm/Makefile-100-obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o\n"
} |
| 13/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24852 output: 32 thoughts: 37 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Kernel Configuration Dependencies for CXL Region Driver Modifications** Alright, so the first thing that jumps out is the absolute requirement of `CONFIG_NUMA`. No surprises there. CXL, especially the memory semantics, is fundamentally intertwined with Non-Uniform Memory Access. That's a given. Now, let's drill down a bit deeper. My immediate thought is, what's the deal with `CONFIG_CXL_BUS`? The fact that the patch touches `drivers/cxl/core/region.c` is significant. It strongly suggests that these changes are either directly dependent on, or at the very least, heavily influenced by, whether or not the CXL bus driver is enabled. Region.c is the heart of CXL memory region management, so the configuration options are directly related. I need to understand whether this is a hard dependency, a conditional compilation scenario, or something else entirely. Further investigation is required to fully grasp the nuances of the interaction. |
| 14/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "config CXL_BUS",
"PathPrefix": "drivers/cxl/Kconfig"
}Results: {
"Output": "drivers/cxl/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/cxl/Kconfig:2:menuconfig CXL_BUS\ndrivers/cxl/Kconfig-3-\ttristate \"CXL (Compute Express Link) Devices Support\"\n"
} |
| 15/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25226 output: 35 thoughts: 15 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm speaking to a fellow expert: **Investigating DAX and Kernel Memory Modifications** Alright, so the initial focus seems to be on the `drivers/dax/kmem.c` file. My mind immediately starts running through the potential implications. Whenever I see DAX and kernel memory modifications in the same sentence, the alarm bells start ringing. I'm thinking about the direct access implications, the security considerations, and the potential performance enhancements or regressions. I wonder what specific changes are being made within that file. Are we talking about allocation strategies, caching behavior, mapping interactions, error handling related to persistent memory, or something else entirely? The `kmem.c` part suggests we're dealing with kernel memory management, which always demands a high level of scrutiny. I'll need to dig deeper into the actual code changes to understand the scope and the rationale behind them. What problem is this modification trying to address? What's the trade-off in terms of performance, security, and stability? The devil's always in the details, and I anticipate this will be no different. This seems like a critical piece of the puzzle, and I'll need to be ready to evaluate the change with a thorough understanding of the kernel's memory management subsystems. |
| 16/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"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"
} |
| 17/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25653 output: 31 thoughts: 59 Thoughts: Okay, let's see, my initial thinking is that I might need to enable a few kernel configurations to support this. I'm thinking specifically about `CONFIG_NUMA`, `CONFIG_CXL_BUS`, and also `CONFIG_DEV_DAX_KMEM`. These are the usual suspects when dealing with memory and new interconnect technologies, so they are the initial considerations. Now, hold on. I remember seeing something about CXL region management. Ah, yes! `cxl_region_add_package_node`... that function is defined within `drivers/cxl/core/region.c`. Excellent. That gives me a useful anchor for tracing through the relevant code. This also indicates that these functions are where I can begin. I can start my review of the situation from here. |
| 18/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "region.o",
"PathPrefix": "drivers/cxl/core/Makefile"
}Results: {
"Output": "drivers/cxl/core/Makefile=17=cxl_core-$(CONFIG_TRACING) += trace.o\ndrivers/cxl/core/Makefile:18:cxl_core-$(CONFIG_CXL_REGION) += region.o region_pmem.o region_dax.o\ndrivers/cxl/core/Makefile-19-cxl_core-$(CONFIG_CXL_MCE) += mce.o\n"
} |
| 19/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26025 output: 32 thoughts: 12 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **CXL Region Configuration: A Necessity** Alright, so I've been digging into this, and it's become crystal clear that we absolutely need `CONFIG_CXL_REGION` enabled. There's no way around it. From what I've seen in the architecture specifications, the region configuration is foundational for managing the CXL fabric itself. Without it, we're essentially dead in the water. We won't be able to properly discover, configure, and utilize the resources that the CXL devices are bringing to the table. We need this basic building block to even *start* building the higher-level functionality like memory pooling, cache coherency protocols, or device management. |
| 20/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "config CXL_REGION",
"PathPrefix": "drivers/cxl/Kconfig"
}Results: {
"Output": "drivers/cxl/Kconfig=193=config CXL_SUSPEND\n--\ndrivers/cxl/Kconfig-196-\ndrivers/cxl/Kconfig:197:config CXL_REGION\ndrivers/cxl/Kconfig-198-\tbool \"CXL: Region Support\"\n--\ndrivers/cxl/Kconfig-214-\ndrivers/cxl/Kconfig:215:config CXL_REGION_INVALIDATION_TEST\ndrivers/cxl/Kconfig-216-\tbool \"CXL: Region Cache Management Bypass (TEST)\"\n"
} |
| 21/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26381 output: 36 thoughts: 614 Thoughts: **My Analysis of Memory Policy and Multi-Package Functions** Okay, let's dive in. I need to get a handle on the relevant configurations and symbols related to multi-package and NUMA functionality. First, it's worth double-checking `CONFIG_NUMA_MIGRATION` because several functions, like `mp_next_demotion_nodemask`, are conditionally compiled under it. So, let's list the configs I'm interested in: `NUMA`, `NUMA_MIGRATION`, `CXL_BUS`, `CXL_REGION`, and `DEV_DAX_KMEM`. Now, let's turn our attention to the specific functions. I need to know the landscape, so here's what I'm focusing on: * `mp_add_package_node_by_initiator` * `mp_add_package_node` * `mp_get_package_nodes` * `mp_get_package_cpu_nodes` * `mp_get_package_memory_only_nodes` * `mp_next_demotion_nodemask` * `mp_next_demotion_node` * `mp_next_promotion_nodemask` * `mp_next_promotion_node` * `mp_is_topology_symmetric` * `nearest_nodes_nodemask` * `mp_probe_package_id` * `register_mp_package_notifier` * `unregister_mp_package_notifier` Next, I need to know if these symbols are exported. This is crucial for understanding their accessibility and potential use in other kernel modules. Thankfully, they are all exported using `EXPORT_SYMBOL_GPL`. That's good news – it tells me these functions are intended for use outside the immediate module, under the GPL license. Alright, what else might be important? I had a thought about `alloc_pages_bulk_weighted_interleave`—it's a core function. I have to be careful with my analysis of memory management. Wait, I remember that it's static in `mm/mempolicy.c`. I need to revisit that file to understand its purpose and how it fits into the broader picture. My next step is to explore `mm/mempolicy.c` more closely and assess its place within the memory management subsystem. |
| 22/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "alloc_pages_bulk_weighted_interleave",
"PathPrefix": "mm/mempolicy.c"
}Results: {
"Output": "mm/mempolicy.c=2748=static unsigned long alloc_pages_bulk_interleave(gfp_t gfp,\n--\nmm/mempolicy.c-2782-\nmm/mempolicy.c:2783:static unsigned long alloc_pages_bulk_weighted_interleave(gfp_t gfp,\nmm/mempolicy.c-2784-\t\tstruct mempolicy *pol, unsigned long nr_pages,\n--\nmm/mempolicy.c=2927=unsigned long alloc_pages_bulk_mempolicy_noprof(gfp_t gfp,\n--\nmm/mempolicy.c-2941-\tif (pol-\u003emode == MPOL_WEIGHTED_INTERLEAVE)\nmm/mempolicy.c:2942:\t\treturn alloc_pages_bulk_weighted_interleave(\nmm/mempolicy.c-2943-\t\t\t\t gfp, pol, nr_pages, page_array);\n"
} |
| 23/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27801 output: 32 thoughts: 58 Thoughts: Okay, let's break this down. **Initial Assessment of the Memory Allocation Workflow** Right, so I'm looking at the memory allocation path, and it looks like `alloc_pages_bulk_mempolicy_noprof` is the key entry point here. I need to understand how the system is making decisions when grabbing pages. And it looks like the function `weighted_interleave_nodes` is static, which is interesting. Static means it's likely only used within this compilation unit, which might give me some clues about its scope of responsibility. And, also `weighted_interleave_nid` is a static function. That further focuses my attention. Since they are static, I should first examine `weighted_interleave_nodes` to understand how the allocation strategy works. |
| 24/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "weighted_interleave_nodes",
"PathPrefix": "mm/mempolicy.c"
}Results: {
"Output": "mm/mempolicy.c=2187=static int policy_resolve_package_nodes(struct mempolicy *policy, nodemask_t *mask)\n--\nmm/mempolicy.c-2221-\nmm/mempolicy.c:2222:static unsigned int weighted_interleave_nodes(struct mempolicy *policy)\nmm/mempolicy.c-2223-{\n--\nmm/mempolicy.c=2272=unsigned int mempolicy_slab_node(void)\n--\nmm/mempolicy.c-2291-\tcase MPOL_WEIGHTED_INTERLEAVE:\nmm/mempolicy.c:2292:\t\treturn weighted_interleave_nodes(policy);\nmm/mempolicy.c-2293-\n--\nmm/mempolicy.c=2422=static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy *pol,\n--\nmm/mempolicy.c-2457-\t\t*nid = (ilx == NO_INTERLEAVE_INDEX) ?\nmm/mempolicy.c:2458:\t\t\tweighted_interleave_nodes(pol) :\nmm/mempolicy.c-2459-\t\t\tweighted_interleave_nid(pol, ilx);\n"
} |
| 25/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28376 output: 32 thoughts: 37 Thoughts: Okay, let's see... **Analyzing `mempolicy_slab_node` and Policy Interaction** Right, so I'm digging into this code, and it looks like `mempolicy_slab_node` and `policy_nodemask` are the key players calling this particular function. Interesting. Now, hold on a second... is `mempolicy_slab_node` exported? I'll need to double-check that. That's a crucial detail. Its export status will significantly change how I need to approach this analysis, and what assumptions I can make about its expected behavior and the dependencies it may have. Gotta get the full picture here. |
| 26/2 | 2026/08/06 08:29 | tool | grepper |
0mArgs:{
"Expression": "mempolicy_slab_node",
"PathPrefix": "mm/mempolicy.c"
}Results: {
"Output": "mm/mempolicy.c=2252=static unsigned int interleave_nodes(struct mempolicy *policy)\n--\nmm/mempolicy.c-2271- */\nmm/mempolicy.c:2272:unsigned int mempolicy_slab_node(void)\nmm/mempolicy.c-2273-{\n"
} |
| 27/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28676 output: 36 thoughts: 16 Thoughts: Okay, let's see. My first thought is: **Checking for Symbol Export**. I need to confirm if the symbol `mempolicy_slab_node` is actually exported. This is a crucial step before I consider using it. I'm likely trying to use it in some context where I need it to be publicly accessible, perhaps in a module I'm developing, or in a patch. Knowing if it's exported informs me on the intended public API of the kernel, and if it's safe to use the symbol or not. I'll need to check the symbol table directly, or perhaps use a tool to verify its export status. This is a fundamental preliminary step I always take. |
| 28/2 | 2026/08/06 08:29 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*mempolicy_slab_node",
"PathPrefix": "mm/mempolicy.c"
}Results: {
"error": "no matches"
} |
| 29/2 | 2026/08/06 08:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28888 output: 35 thoughts: 552 Thoughts: **My Assessment of API Entrypoints and Related Functions** Okay, so I'm digging into how slab allocation interfaces with the system, and I've hit a potential entrypoint that isn't directly exported. That's a flag, so I need to understand the wider landscape. The focus is on package node manipulation. I've already identified several exported functions that are directly relevant: `mp_add_package_node`, `mp_add_package_node_by_initiator`, `mp_get_package_nodes`, and so on, all the way to `unregister_mp_package_notifier`. This gives me a good starting point for mapping the API. Then there is that callback, `cxl_region_add_package_node`. It's a static callback, but it's important because it is registered via `register_mp_package_notifier`, so I need to factor it in. Are there any other callbacks registered? Beyond the exported functions, I also need to consider related system-level interfaces like `package_mode_store`, `package_nodes_show`, and similar sysfs entries that are essential for configuration and monitoring. These functions expose additional facets of the package node management. Let's consolidate the list of exposed functions from `mm/memory-tiers.c` and `mm/mempolicy.c` again, just to ensure I have a complete picture. It's the same list as before: `mp_add_package_node`, to `unregister_mp_package_notifier`. A clean summary is always essential. Lastly, I need to keep the configuration options in mind. I know these are relevant: `NUMA`, `NUMA_MIGRATION`, `CXL_BUS`, `CXL_REGION`, and `DEV_DAX_KMEM`. I'm going to double-check that `DEV_DAX_KMEM` is indeed the correct configuration name for that setting, just to be sure. |
| 30/2 | 2026/08/06 08:30 | tool | grepper |
0mArgs:{
"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"
} |
| 31/2 | 2026/08/06 08:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29992 output: 32 thoughts: 482 Thoughts: **Analyzing NUMA Balancing Implementation** Okay, so I need to assess whether `NUMA_BALANCING` is essential here. The patch introduces `mp_next_demotion_node` under `CONFIG_NUMA_MIGRATION`, which indicates that `NUMA_MIGRATION` is the critical configuration to focus on. Now, let's review the crucial symbols. I've got this JSON list: ```json [ "mp_add_package_node", "mp_add_package_node_by_initiator", "mp_get_package_nodes", "mp_get_package_cpu_nodes", "mp_get_package_memory_only_nodes", "mp_next_demotion_nodemask", "mp_next_demotion_node", "mp_next_promotion_nodemask", "mp_next_promotion_node", "mp_is_topology_symmetric", "nearest_nodes_nodemask", "mp_probe_package_id", "register_mp_package_notifier", "unregister_mp_package_notifier" ] ``` Aha! `mp_add_package_node` is called from `cxl_region_add_package_node`, which is a notifier callback. And I see that `mp_probe_package_id` is called from `dev_dax_kmem_probe`. So, both of these are definitely reachable. Memory policy changes are also reachable via `set_mempolicy` syscall and sysfs. Now, the sysfs store function `package_mode_store` seems relevant too. Wait, `package_mode_store` is static, but it's a sysfs callback, making it an entry point. Should I list it, or just stick to exported functions? Actually, just the exported functions *and* `package_mode_store` is the best approach. Okay, let's refine the list of relevant kernel configurations. I'm thinking: * `NUMA` * `NUMA_MIGRATION` * `CXL_BUS` * `CXL_REGION` * `DEV_DAX_KMEM` Now, I need to check if `CXL_REGION` has any dependencies that could impact this analysis. |
| 32/2 | 2026/08/06 08:30 | tool | grepper |
0mArgs:{
"Expression": "config CXL_REGION",
"PathPrefix": "drivers/cxl/Kconfig"
}Results: {
"Output": "drivers/cxl/Kconfig=193=config CXL_SUSPEND\n--\ndrivers/cxl/Kconfig-196-\ndrivers/cxl/Kconfig:197:config CXL_REGION\ndrivers/cxl/Kconfig-198-\tbool \"CXL: Region Support\"\n--\ndrivers/cxl/Kconfig-214-\ndrivers/cxl/Kconfig:215:config CXL_REGION_INVALIDATION_TEST\ndrivers/cxl/Kconfig-216-\tbool \"CXL: Region Cache Management Bypass (TEST)\"\n"
} |
| 33/2 | 2026/08/06 08:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31185 output: 35 thoughts: 20 Thoughts: Here's my analysis of the `drivers/cxl/Kconfig` file, specifically focusing on the `CXL_REGION` configuration option. **Diving into CXL Region Configuration** Okay, so I'm looking at `drivers/cxl/Kconfig`. This is where the core configuration for CXL devices and their memory regions is handled, naturally. My attention is drawn to `CXL_REGION`. It's a key configuration symbol, definitely a starting point to understanding how the kernel deals with CXL's memory architecture. I assume this will affect how the driver enables, and configures, the hardware functionality. I'll need to see how the other options around it relate. Are there dependencies? Any related defaults? Are there any sub-options for the configuration to configure the type of region? Or perhaps the size? I'll also have to see how this symbol is used throughout the rest of the CXL driver code. Where is it checked? What functions and data structures depend on it? I'm expecting this option to enable support for allocating and managing CXL memory regions, which is foundational to making CXL devices actually useful in the system. Essentially, I'm expecting `CXL_REGION` to be a gatekeeper - if it's not enabled, a lot of the CXL memory management functionality will likely be disabled as well. Time to start digging! |
| 34/2 | 2026/08/06 08:30 | tool | read-file |
0mArgs:{
"File": "drivers/cxl/Kconfig",
"FirstLine": 190,
"LineCount": 20
}Results: {
"Contents": " 190:\t\tdefault CXL_BUS\n 191:\t\ttristate\n 192:\t\n 193:\tconfig CXL_SUSPEND\n 194:\t\tdef_bool y\n 195:\t\tdepends on SUSPEND \u0026\u0026 CXL_MEM\n 196:\t\n 197:\tconfig CXL_REGION\n 198:\t\tbool \"CXL: Region Support\"\n 199:\t\tdefault CXL_BUS\n 200:\t\t# For MAX_PHYSMEM_BITS\n 201:\t\tdepends on SPARSEMEM\n 202:\t\tselect MEMREGION\n 203:\t\tselect GET_FREE_REGION\n 204:\t\thelp\n 205:\t\t Enable the CXL core to enumerate and provision CXL regions. A CXL\n 206:\t\t region is defined by one or more CXL expanders that decode a given\n 207:\t\t system-physical address range. For CXL regions established by\n 208:\t\t platform-firmware this option enables memory error handling to\n 209:\t\t identify the devices participating in a given interleaved memory\n"
} |
| 35/2 | 2026/08/06 08:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31823 output: 34 thoughts: 33 Thoughts: Okay, here's my interpretation of those dependencies, explained as if they are my own thoughts: **Dissecting the Kernel Configuration Landscape** Alright, let's break this down. From a kernel configuration perspective, it's clear that the module or feature flagged as `CXL_REGION` is fundamentally reliant on the foundation provided by `SPARSEMEM`. You can't have one without the other. It's like building a complex structure – you need a solid base before you can start layering on the more specialized components. Sparsemem provides that base and is integral. Similarly, I'm noting that the `DEV_DAX_KMEM` option has a direct dependency on `DEV_DAX`. This makes perfect sense; `DEV_DAX_KMEM` is a specific implementation or feature that leverages the capabilities of `DEV_DAX`. Think of `DEV_DAX` as the general framework, and `DEV_DAX_KMEM` as a particular application or extension within that framework. You wouldn't attempt to use the extension without the base framework existing. This modular approach is common for keeping a lean and extensible kernel. |
| 36/2 | 2026/08/06 08:30 | tool | read-file |
0mArgs:{
"File": "drivers/dax/Kconfig",
"FirstLine": 65,
"LineCount": 20
}Results: {
"Contents": " 65:\t\tdepends on DEV_DAX_HMEM \u0026\u0026 DAX\n 66:\t\tdef_bool y\n 67:\t\n 68:\tconfig DEV_DAX_FSDEV\n 69:\t\ttristate\n 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"
} |
| 37/2 | 2026/08/06 08:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32423 output: 31 thoughts: 108 Thoughts: **Kernel Configuration: Necessary Features** Okay, so I'm thinking, based on this, I absolutely need `DEV_DAX` and `MEMORY_HOTPLUG` enabled. No question about it. Let me just add `DEV_DAX` to the checklist I'm mentally assembling. Right, so to make sure I'm clear in my head, the essential kernel configurations involved here are: * `NUMA` * `NUMA_MIGRATION` * `CXL_BUS` * `CXL_REGION` * `DEV_DAX` (definitely needed) * `DEV_DAX_KMEM` * `MEMORY_HOTPLUG` (That's another crucial one to confirm.) Now, I should take a look at the `mm/Kconfig` file. I'm going to double-check the implications of `MEMORY_HOTPLUG` in relation to this whole setup. I need to make sure I understand the dependencies and interactions with the other options. |
| 38/2 | 2026/08/06 08:30 | tool | grepper |
0mArgs:{
"Expression": "config MEMORY_HOTPLUG",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=466=config ARCH_ENABLE_MEMORY_HOTPLUG\n--\nmm/Kconfig-469-# eventually, we can have this option just 'select SPARSEMEM'\nmm/Kconfig:470:menuconfig MEMORY_HOTPLUG\nmm/Kconfig-471-\tbool \"Memory hotplug\"\n"
} |
| 39/2 | 2026/08/06 08:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32883 output: 297 thoughts: 267 Thoughts: **My Assessment of the Memory Package Functionality** Okay, so `MEMORY_HOTPLUG` is confirmed, that's the starting point. Now, let's break down these core symbols. I need to get a handle on what's being exposed, because that's where the interesting attack surface likely lies. I see a concentrated set of symbols here that constitute the new memory package functionality: * `mp_add_package_node`, `mp_add_package_node_by_initiator`: Clearly, these are for adding nodes to the memory package topology, potentially dynamically, based on an initiator. * `mp_get_package_nodes`, `mp_get_package_cpu_nodes`, `mp_get_package_memory_only_nodes`: These are the access methods to get the list of the package's nodes, differentiated by type. * `mp_next_demotion_nodemask`, `mp_next_demotion_node`, `mp_next_promotion_nodemask`, `mp_next_promotion_node`: This is where things get interesting from a tiering perspective, especially around the masks. * `mp_is_topology_symmetric`: Checking for topological symmetry is crucial. This could be a vector. * `nearest_nodes_nodemask`: Determining proximity is critical for NUMA. * `mp_probe_package_id`: A probing function adds an element of risk. * `register_mp_package_notifier`, `unregister_mp_package_notifier`: The use of notifiers can create vulnerabilities in concurrent access. These are all exported, meaning they're the public interface. This tells me we're looking at the fundamental building blocks of this new memory package functionality, which, from what I'm reading, is altering core memory tiering, mempolicy, and how CXL regions are handled. Plus, there are new sysfs interfaces and a new weighted interleave mempolicy mode. All of this user-space accessible stuff? This screams for fuzzing. We've got interfaces for adding, querying, and manipulating the memory package topology. I'm going to have to test the boundary conditions, race conditions, and error handling of these functions very carefully, especially when combined with these new sysfs entries and the new mempolicy. Time to call `set-results`. |
| 40/2 | 2026/08/06 08:30 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"NUMA",
"NUMA_MIGRATION",
"CXL_BUS",
"CXL_REGION",
"DEV_DAX",
"DEV_DAX_KMEM",
"MEMORY_HOTPLUG"
],
"FocusSymbols": [
"mp_add_package_node",
"mp_add_package_node_by_initiator",
"mp_get_package_nodes",
"mp_get_package_cpu_nodes",
"mp_get_package_memory_only_nodes",
"mp_next_demotion_nodemask",
"mp_next_demotion_node",
"mp_next_promotion_nodemask",
"mp_next_promotion_node",
"mp_is_topology_symmetric",
"nearest_nodes_nodemask",
"mp_probe_package_id",
"register_mp_package_notifier",
"unregister_mp_package_notifier"
],
"Reasoning": "The patch introduces a new memory package topology concept for NUMA nodes, modifying core memory tiering, mempolicy, and CXL region handling. It adds new sysfs interfaces and a new mempolicy weighted interleave mode, which are reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"NUMA",
"NUMA_MIGRATION",
"CXL_BUS",
"CXL_REGION",
"DEV_DAX",
"DEV_DAX_KMEM",
"MEMORY_HOTPLUG"
],
"FocusSymbols": [
"mp_add_package_node",
"mp_add_package_node_by_initiator",
"mp_get_package_nodes",
"mp_get_package_cpu_nodes",
"mp_get_package_memory_only_nodes",
"mp_next_demotion_nodemask",
"mp_next_demotion_node",
"mp_next_promotion_nodemask",
"mp_next_promotion_node",
"mp_is_topology_symmetric",
"nearest_nodes_nodemask",
"mp_probe_package_id",
"register_mp_package_notifier",
"unregister_mp_package_notifier"
],
"Reasoning": "The patch introduces a new memory package topology concept for NUMA nodes, modifying core memory tiering, mempolicy, and CXL region handling. It adds new sysfs interfaces and a new mempolicy weighted interleave mode, which are reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|