Add a design overview of private memory nodes:
  - the isolation model (structural zonelist exclusion)
  - ZONELIST_PRIVATE
  - driver provisioning API
  - capability opt-in model and its dependency rules
  - observability surfaces

Signed-off-by: Gregory Price <[email protected]>
---
 Documentation/mm/index.rst              |   1 +
 Documentation/mm/numa_private_nodes.rst | 160 ++++++++++++++++++++++++
 2 files changed, 161 insertions(+)
 create mode 100644 Documentation/mm/numa_private_nodes.rst

diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
index 13a79f5d092c0..f60704df6104c 100644
--- a/Documentation/mm/index.rst
+++ b/Documentation/mm/index.rst
@@ -65,6 +65,7 @@ documentation, or deleted if it has served its purpose.
    mmu_notifier
    multigen_lru
    numa
+   numa_private_nodes
    overcommit-accounting
    page_migration
    page_frags
diff --git a/Documentation/mm/numa_private_nodes.rst 
b/Documentation/mm/numa_private_nodes.rst
new file mode 100644
index 0000000000000..3b27a2e24b086
--- /dev/null
+++ b/Documentation/mm/numa_private_nodes.rst
@@ -0,0 +1,160 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+Private memory nodes
+====================
+
+A *private memory node* is a NUMA node whose memory is hotplugged by a driver
+and deliberately hidden from the kernel's normal memory management.  Such a
+node is marked ``N_MEMORY_PRIVATE`` instead of ``N_MEMORY``; the two states
+are mutually exclusive, so a private node is never considered by the page
+allocator's normal or fallback paths.
+
+The intent is to give a driver a block of NUMA-addressable memory that the rest
+of the kernel will not allocate from on its own, while still letting that 
memory
+be mapped into processes as ordinary, struct-page, LRU-managed folios -- and to
+let the driver re-enable individual mm services it is capable of allowing.
+
+Preconditions
+=============
+
+``N_MEMORY_PRIVATE`` and ``N_MEMORY`` are mutually exclusive, so the backing
+memory must come up on a node that has no DRAM of its own (otherwise the node
+would already be ``N_MEMORY``).
+
+In practice the memory is provided by a device driver or a DAX device whose
+target node has no other memory, and usually no CPUs.
+
+Isolation model
+===============
+
+Isolation is *opt-in by exclusion* and is **structural**: by default nothing in
+the kernel can place memory on a private node because the node is absent from 
the
+zonelists an ordinary allocation walks.
+
+Zonelist exclusion
+    The kernel page allocator depends on the ``FALLBACK`` and ``NOFALLBACK``
+    zonelists to allocate memory.  A normal ``N_MEMORY`` node's zones (except
+    ``ZONE_DEVICE``) appear in these lists and allow allocations to fall-back
+    to less preferable locations if the preferred location is pressured.
+
+    ``__GFP_THISNODE`` is used during normal operation to switch between
+    ``FALLBACK`` and ``NOFALLBACK``, where ``NOFALLBACK`` only contains the
+    zonelists of the preferred node.
+
+    ``N_MEMORY_PRIVATE`` nodes are **excluded** from both ``FALLBACK`` and
+    ``NOFALLBACK`` zonelists.  Instead they are added to ``ZONELIST_PRIVATE``,
+    which includes both ``N_MEMORY`` and ``N_MEMORY_PRIVATE`` nodes.  This is
+    the only zonelist that contains private-node zones, and so the only way
+    to acquire private node allocations is to explicitly request that zonelist.
+
+    Even an allocation carrying ``__GFP_THISNODE`` cannot access the node's
+    memory without also explicitly passing the private zonelist.  This prevents
+    incidental allocation of private memory by users of possible/online
+    nodelists.
+
+    When ``CONFIG_NUMA`` is disabled ``ZONELIST_PRIVATE`` aliases
+    ``ZONELIST_FALLBACK`` and is never selected.
+
+The user_numa path
+
+    ``MPOL_F_PRIVATE`` is an internal user_numa flag (never accepted from
+    userspace) marking that a mempolicy has a private node in its nodemask.
+
+    When ``CAP_USER_NUMA`` for a private node is set, user-sourced mempolicy
+    (``set_mempolicy(2)``) and migration (``move_pages(2)``) operations are
+    allowed to include that node in nodemasks and targets respectively.
+
+    ``mbind(MPOL_MF_MOVE)`` is both a mempolicy and a migration operation,
+    so placement and migration share the same capability.
+
+    The mempolicy component uses ``MPOL_F_PRIVATE`` at fault-time to select
+    ``ZONELIST_PRIVATE`` and makes the node's memory available for allocation.
+    It is otherwise an ordinary, relaxable mempolicy: an unsatisfiable request
+    (an unmovable allocation on a movable-only private node) simply falls back.
+
+
+cpuset interaction
+==================
+
+cpuset.mems does **not** partition private nodes.  cpuset neither grants nor
+denies access, and rebinding cpuset.mems nodemasks do not affect a private 
node's
+residency in any nodemask.
+
+Likewise, a private node's inclusion in a nodemask does not affect cpuset.mems'
+filtering of any ``N_MEMORY`` - they remain partitioned according to cpuset.
+
+
+Provisioning
+============
+
+A driver brings memory up as private with::
+
+  add_private_memory_driver_managed(nid, start, size, resource_name,
+                                    mhp_flags, online_type, np)
+
+which onlines the range and registers the driver-owned ``struct node_private``
+(``np``) describing the node, including its capability bitmap (see below).
+
+Only one driver/service may register a ``struct node_private``, which
+heavily implies a "one-node-per-device" design of the system.
+
+The node leaves ``N_MEMORY_PRIVATE`` only when the last range is offlined.
+
+.. kernel-doc:: mm/memory_hotplug.c
+   :identifiers: __add_memory_driver_managed
+
+.. kernel-doc:: drivers/base/node.c
+   :identifiers: node_private_register node_private_unregister
+
+Capabilities (per-service opt-ins)
+==================================
+
+Because the default is "no mm service touches the node", each service a driver
+wants back is requested explicitly through a capability bit in
+``np->caps``.  The mm side checks the matching ``node_allows_*()`` /
+``folio_allows_*()`` predicate before acting:
+
+.. list-table::
+   :header-rows: 1
+   :widths: 35 65
+
+   * - Capability
+     - Re-enables
+   * - ``NODE_PRIVATE_CAP_RECLAIM``
+     - reclaim of the node's folios, by the mm and by userspace
+       ``MADV_COLD`` / ``PAGEOUT`` / ``FREE`` (userland-driven reclaim)
+   * - ``NODE_PRIVATE_CAP_USER_NUMA``
+     - all userspace-directed placement and migration: ``mbind()`` /
+       ``set_mempolicy()`` / home node, and ``move_pages()`` /
+       ``migrate_pages()`` to/from the node
+   * - ``NODE_PRIVATE_CAP_HOTUNPLUG``
+     - hot-unplug via migration
+   * - ``NODE_PRIVATE_CAP_DEMOTION``
+     - reclaim-driven tiering demotion onto the node (the node joins the
+       demotion hierarchy)
+   * - ``NODE_PRIVATE_CAP_NUMA_BALANCING``
+     - access-based NUMA balancing scan/migration of the node's folios
+   * - ``NODE_PRIVATE_CAP_LTPIN``
+     - ``FOLL_LONGTERM`` GUP pins
+
+khugepaged never operates on private-node folios (like ZONE_DEVICE), and DAMON
+does not act on them; ``MADV_COLLAPSE`` is covered by ``CAP_USER_NUMA``.
+
+Dependencies between capabilities are enforced **once**, by
+``node_private_register()`` at hotplug, rather than by whatever sets the bits:
+
+* ``DEMOTION`` requires ``RECLAIM`` (a demotion target accumulates demoted
+  pages, so without reclaim as a safety valve it would just fill up).
+
+Capability flags are expected to be stable at runtime.
+
+Observability
+=============
+
+A private node is reported through:
+
+* ``/sys/devices/system/node/has_private_memory``
+* ``/proc/<pid>/numa_maps`` -- per-node residency includes private nodes
+* ``/proc/kcore`` -- private-node RAM appears in the kcore RAM map
+* memcg per-node statistics account private-node memory.
-- 
2.53.0-Meta


Reply via email to