Add a "private" knob to dax/kmem.  Only writable when state=unplugged.

Switches the device between onlining as N_MEMORY and N_MEMORY_PRIVATE.
This marks the node private and excludes it from every fallback zonelist.

Fails if the node already has online (non-private) memory or a if the
node has a different private owner.

The base plumbing for capability bit flags are added here, but no
capabilities are added until later commits.

A private node after this commit is solely accessible via mmap on
the dax character device (/dev/daxN.M).

Because a fully isolated private node cannot reclaim or compact, its
dax-file mappings default to base-page faults (VM_NOHUGEPAGE). This
makes allocation / failures more deterministic, otherwise a fault may
fail due to a lock of given order despite memory still being free.

When/if reclaim is supported (and the node opted-in), this can be relaxed.

Signed-off-by: Gregory Price <[email protected]>
---
 drivers/dax/kmem.c | 85 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 83 insertions(+), 2 deletions(-)

diff --git a/drivers/dax/kmem.c b/drivers/dax/kmem.c
index d9021cf148982..c4c18aeb43c69 100644
--- a/drivers/dax/kmem.c
+++ b/drivers/dax/kmem.c
@@ -14,6 +14,7 @@
 #include <linux/mempolicy.h>
 #include <linux/memory-tiers.h>
 #include <linux/memory_hotplug.h>
+#include <linux/node_private.h>
 #include <linux/string_helpers.h>
 #include "dax-private.h"
 #include "bus.h"
@@ -52,6 +53,9 @@ struct dax_kmem_data {
        struct mutex lock; /* protects hotplug state transitions and config */
        bool dax_file; /* when set, cdev allows mmap */
        struct mempolicy *policy; /* device-lifetime bind for dax-file mmap */
+       bool private; /* when set, memory is onlined as N_MEMORY_PRIVATE */
+       unsigned long caps; /* NODE_PRIVATE_CAP_* flags */
+       struct node_private np;
        struct resource *res[];
 };
 
@@ -108,6 +112,13 @@ static int kmem_anon_mmap(struct file *filp, struct 
vm_area_struct *vma)
                return -ENXIO;
 
        vma_set_anonymous(vma);
+       /*
+        * Default a private node's dax-file mappings to base-page faults.
+        * Without reclaim/compaction, high-order allocations can fail despite
+        * memory still being available. Can be relaxed with reclaim support.
+        */
+       if (data->private)
+               vm_flags_set(vma, VM_NOHUGEPAGE);
        mpol_get(data->policy);
        kmem_vma_set_policy(vma, data->policy);
        return 0;
@@ -187,6 +198,10 @@ static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,
        if (online_type < MMOP_OFFLINE || online_type > MMOP_ONLINE_MOVABLE)
                return -EINVAL;
 
+       /* Capabilities are stable as long as memory is online */
+       if (data->private)
+               data->np.caps = data->caps;
+
        for (i = 0; i < dev_dax->nr_range; i++) {
                struct range range;
 
@@ -215,7 +230,7 @@ static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,
                 */
                rc = __add_memory_driver_managed(data->mgid, range.start,
                                range_len(&range), kmem_name, mhp_flags,
-                               online_type, NULL);
+                               online_type, data->private ? &data->np : NULL);
 
                if (rc) {
                        dev_warn(dev, "mapping%d: %#llx-%#llx memory add 
failed\n",
@@ -480,6 +495,55 @@ static ssize_t state_store(struct device *dev, struct 
device_attribute *attr,
        return len;
 }
 
+static const struct attribute_group dax_kmem_private_group;
+
+static ssize_t private_show(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+{
+       struct dax_kmem_data *data = dev_get_drvdata(dev);
+
+       if (!data)
+               return -ENXIO;
+       return sysfs_emit(buf, "%d\n", data->private);
+}
+
+static ssize_t private_store(struct device *dev, struct device_attribute *attr,
+                            const char *buf, size_t len)
+{
+       struct dax_kmem_data *data = dev_get_drvdata(dev);
+       bool enable;
+       int rc;
+
+       if (!data)
+               return -ENXIO;
+
+       rc = kstrtobool(buf, &enable);
+       if (rc)
+               return rc;
+
+       guard(mutex)(&data->lock);
+
+       if (data->state != DAX_KMEM_UNPLUGGED)
+               return -EBUSY;
+
+       if (enable == data->private)
+               return len;
+
+       if (enable) {
+               /* Add the per-service opt-in attributes. */
+               rc = sysfs_create_group(&dev->kobj, &dax_kmem_private_group);
+               if (rc)
+                       return rc;
+               data->private = true;
+       } else {
+               sysfs_remove_group(&dev->kobj, &dax_kmem_private_group);
+               data->private = false;
+       }
+
+       return len;
+}
+static DEVICE_ATTR_RW(private);
+
 static ssize_t dax_file_show(struct device *dev, struct device_attribute *attr,
                             char *buf)
 {
@@ -531,6 +595,14 @@ static ssize_t dax_file_store(struct device *dev, struct 
device_attribute *attr,
 }
 static DEVICE_ATTR_RW(dax_file);
 
+/* Per-service opt-ins. Visibility toggled by 'private' control */
+static struct attribute *dax_kmem_private_attrs[] = {
+       NULL,
+};
+static const struct attribute_group dax_kmem_private_group = {
+       .attrs = dax_kmem_private_attrs,
+};
+
 static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
 {
        struct device *dev = &dev_dax->dev;
@@ -601,6 +673,7 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
        data->mgid = rc;
        data->numa_node = numa_node;
        data->state = DAX_KMEM_UNPLUGGED;
+       data->np.owner = data;
        mutex_init(&data->lock);
 
        dev_set_drvdata(dev, data);
@@ -668,6 +741,9 @@ static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
        struct device *dev = &dev_dax->dev;
        struct dax_kmem_data *data = dev_get_drvdata(dev);
 
+       if (data->private)
+               sysfs_remove_group(&dev->kobj, &dax_kmem_private_group);
+
        /* If enabled, clean up the dax-file char device configuration. */
        if (data->dax_file) {
                kmem_dax_file_cdev_del(dev_dax);
@@ -707,7 +783,11 @@ static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
 #else
 static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
 {
-       struct dax_kmem_data *data = dev_get_drvdata(&dev_dax->dev);
+       struct device *dev = &dev_dax->dev;
+       struct dax_kmem_data *data = dev_get_drvdata(dev);
+
+       if (data && data->private)
+               sysfs_remove_group(&dev->kobj, &dax_kmem_private_group);
 
        /* If enabled, clean up the dax-file char device configuration. */
        if (data && data->dax_file) {
@@ -732,6 +812,7 @@ static DEVICE_ATTR_RW(state);
 static struct attribute *dev_dax_kmem_attrs[] = {
        &dev_attr_state.attr,
        &dev_attr_dax_file.attr,
+       &dev_attr_private.attr,
        NULL,
 };
 ATTRIBUTE_GROUPS(dev_dax_kmem);
-- 
2.53.0-Meta


Reply via email to