cxl/dax: Create dax devices for CXL RAM regions
authorDan Williams <dan.j.williams@intel.com>
Fri, 10 Feb 2023 09:07:19 +0000 (01:07 -0800)
committerDan Williams <dan.j.williams@intel.com>
Sat, 11 Feb 2023 01:33:45 +0000 (17:33 -0800)
While platform firmware takes some responsibility for mapping the RAM
capacity of CXL devices present at boot, the OS is responsible for
mapping the remainder and hot-added devices. Platform firmware is also
responsible for identifying the platform general purpose memory pool,
typically DDR attached DRAM, and arranging for the remainder to be 'Soft
Reserved'. That reservation allows the CXL subsystem to route the memory
to core-mm via memory-hotplug (dax_kmem), or leave it for dedicated
access (device-dax).

The new 'struct cxl_dax_region' object allows for a CXL memory resource
(region) to be published, but also allow for udev and module policy to
act on that event. It also prevents cxl_core.ko from having a module
loading dependency on any drivers/dax/ modules.

Tested-by: Fan Ni <fan.ni@samsung.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Link: https://lore.kernel.org/r/167602003896.1924368.10335442077318970468.stgit@dwillia2-xfh.jf.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
MAINTAINERS
drivers/cxl/acpi.c
drivers/cxl/core/core.h
drivers/cxl/core/port.c
drivers/cxl/core/region.c
drivers/cxl/cxl.h
drivers/dax/Kconfig
drivers/dax/Makefile
drivers/dax/cxl.c [new file with mode: 0644]
drivers/dax/hmem/hmem.c

index 7f86d02..73a9f34 100644 (file)
@@ -6035,6 +6035,7 @@ M:        Dan Williams <dan.j.williams@intel.com>
 M:     Vishal Verma <vishal.l.verma@intel.com>
 M:     Dave Jiang <dave.jiang@intel.com>
 L:     nvdimm@lists.linux.dev
+L:     linux-cxl@vger.kernel.org
 S:     Supported
 F:     drivers/dax/
 
index ad0849a..8ebb9a7 100644 (file)
@@ -731,7 +731,8 @@ static void __exit cxl_acpi_exit(void)
        cxl_bus_drain();
 }
 
-module_init(cxl_acpi_init);
+/* load before dax_hmem sees 'Soft Reserved' CXL ranges */
+subsys_initcall(cxl_acpi_init);
 module_exit(cxl_acpi_exit);
 MODULE_LICENSE("GPL v2");
 MODULE_IMPORT_NS(CXL);
index 479f01d..cde475e 100644 (file)
@@ -15,12 +15,14 @@ extern struct device_attribute dev_attr_create_ram_region;
 extern struct device_attribute dev_attr_delete_region;
 extern struct device_attribute dev_attr_region;
 extern const struct device_type cxl_pmem_region_type;
+extern const struct device_type cxl_dax_region_type;
 extern const struct device_type cxl_region_type;
 void cxl_decoder_kill_region(struct cxl_endpoint_decoder *cxled);
 #define CXL_REGION_ATTR(x) (&dev_attr_##x.attr)
 #define CXL_REGION_TYPE(x) (&cxl_region_type)
 #define SET_CXL_REGION_ATTR(x) (&dev_attr_##x.attr),
 #define CXL_PMEM_REGION_TYPE(x) (&cxl_pmem_region_type)
+#define CXL_DAX_REGION_TYPE(x) (&cxl_dax_region_type)
 int cxl_region_init(void);
 void cxl_region_exit(void);
 #else
@@ -38,6 +40,7 @@ static inline void cxl_region_exit(void)
 #define CXL_REGION_TYPE(x) NULL
 #define SET_CXL_REGION_ATTR(x)
 #define CXL_PMEM_REGION_TYPE(x) NULL
+#define CXL_DAX_REGION_TYPE(x) NULL
 #endif
 
 struct cxl_send_command;
index b45d279..0bb7a5f 100644 (file)
@@ -46,6 +46,8 @@ static int cxl_device_id(struct device *dev)
                return CXL_DEVICE_NVDIMM;
        if (dev->type == CXL_PMEM_REGION_TYPE())
                return CXL_DEVICE_PMEM_REGION;
+       if (dev->type == CXL_DAX_REGION_TYPE())
+               return CXL_DEVICE_DAX_REGION;
        if (is_cxl_port(dev)) {
                if (is_cxl_root(to_cxl_port(dev)))
                        return CXL_DEVICE_ROOT;
@@ -2015,6 +2017,6 @@ static void cxl_core_exit(void)
        debugfs_remove_recursive(cxl_debugfs);
 }
 
-module_init(cxl_core_init);
+subsys_initcall(cxl_core_init);
 module_exit(cxl_core_exit);
 MODULE_LICENSE("GPL v2");
index 8c29204..91bb9ac 100644 (file)
@@ -2278,6 +2278,75 @@ out:
        return cxlr_pmem;
 }
 
+static void cxl_dax_region_release(struct device *dev)
+{
+       struct cxl_dax_region *cxlr_dax = to_cxl_dax_region(dev);
+
+       kfree(cxlr_dax);
+}
+
+static const struct attribute_group *cxl_dax_region_attribute_groups[] = {
+       &cxl_base_attribute_group,
+       NULL,
+};
+
+const struct device_type cxl_dax_region_type = {
+       .name = "cxl_dax_region",
+       .release = cxl_dax_region_release,
+       .groups = cxl_dax_region_attribute_groups,
+};
+
+static bool is_cxl_dax_region(struct device *dev)
+{
+       return dev->type == &cxl_dax_region_type;
+}
+
+struct cxl_dax_region *to_cxl_dax_region(struct device *dev)
+{
+       if (dev_WARN_ONCE(dev, !is_cxl_dax_region(dev),
+                         "not a cxl_dax_region device\n"))
+               return NULL;
+       return container_of(dev, struct cxl_dax_region, dev);
+}
+EXPORT_SYMBOL_NS_GPL(to_cxl_dax_region, CXL);
+
+static struct lock_class_key cxl_dax_region_key;
+
+static struct cxl_dax_region *cxl_dax_region_alloc(struct cxl_region *cxlr)
+{
+       struct cxl_region_params *p = &cxlr->params;
+       struct cxl_dax_region *cxlr_dax;
+       struct device *dev;
+
+       down_read(&cxl_region_rwsem);
+       if (p->state != CXL_CONFIG_COMMIT) {
+               cxlr_dax = ERR_PTR(-ENXIO);
+               goto out;
+       }
+
+       cxlr_dax = kzalloc(sizeof(*cxlr_dax), GFP_KERNEL);
+       if (!cxlr_dax) {
+               cxlr_dax = ERR_PTR(-ENOMEM);
+               goto out;
+       }
+
+       cxlr_dax->hpa_range.start = p->res->start;
+       cxlr_dax->hpa_range.end = p->res->end;
+
+       dev = &cxlr_dax->dev;
+       cxlr_dax->cxlr = cxlr;
+       device_initialize(dev);
+       lockdep_set_class(&dev->mutex, &cxl_dax_region_key);
+       device_set_pm_not_required(dev);
+       dev->parent = &cxlr->dev;
+       dev->bus = &cxl_bus_type;
+       dev->type = &cxl_dax_region_type;
+out:
+       up_read(&cxl_region_rwsem);
+
+       return cxlr_dax;
+}
+
 static void cxlr_pmem_unregister(void *_cxlr_pmem)
 {
        struct cxl_pmem_region *cxlr_pmem = _cxlr_pmem;
@@ -2362,6 +2431,42 @@ err_bridge:
        return rc;
 }
 
+static void cxlr_dax_unregister(void *_cxlr_dax)
+{
+       struct cxl_dax_region *cxlr_dax = _cxlr_dax;
+
+       device_unregister(&cxlr_dax->dev);
+}
+
+static int devm_cxl_add_dax_region(struct cxl_region *cxlr)
+{
+       struct cxl_dax_region *cxlr_dax;
+       struct device *dev;
+       int rc;
+
+       cxlr_dax = cxl_dax_region_alloc(cxlr);
+       if (IS_ERR(cxlr_dax))
+               return PTR_ERR(cxlr_dax);
+
+       dev = &cxlr_dax->dev;
+       rc = dev_set_name(dev, "dax_region%d", cxlr->id);
+       if (rc)
+               goto err;
+
+       rc = device_add(dev);
+       if (rc)
+               goto err;
+
+       dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent),
+               dev_name(dev));
+
+       return devm_add_action_or_reset(&cxlr->dev, cxlr_dax_unregister,
+                                       cxlr_dax);
+err:
+       put_device(dev);
+       return rc;
+}
+
 static int match_decoder_by_range(struct device *dev, void *data)
 {
        struct range *r1, *r2 = data;
@@ -2624,8 +2729,7 @@ out:
                                        p->res->start, p->res->end, cxlr,
                                        is_system_ram) > 0)
                        return 0;
-               dev_dbg(dev, "TODO: hookup devdax\n");
-               return 0;
+               return devm_cxl_add_dax_region(cxlr);
        default:
                dev_dbg(&cxlr->dev, "unsupported region mode: %d\n",
                        cxlr->mode);
index 2ac3442..b1395c4 100644 (file)
@@ -513,6 +513,12 @@ struct cxl_pmem_region {
        struct cxl_pmem_region_mapping mapping[];
 };
 
+struct cxl_dax_region {
+       struct device dev;
+       struct cxl_region *cxlr;
+       struct range hpa_range;
+};
+
 /**
  * struct cxl_port - logical collection of upstream port devices and
  *                  downstream port devices to construct a CXL memory
@@ -707,6 +713,7 @@ void cxl_driver_unregister(struct cxl_driver *cxl_drv);
 #define CXL_DEVICE_MEMORY_EXPANDER     5
 #define CXL_DEVICE_REGION              6
 #define CXL_DEVICE_PMEM_REGION         7
+#define CXL_DEVICE_DAX_REGION          8
 
 #define MODULE_ALIAS_CXL(type) MODULE_ALIAS("cxl:t" __stringify(type) "*")
 #define CXL_MODALIAS_FMT "cxl:t%d"
@@ -725,6 +732,7 @@ bool is_cxl_pmem_region(struct device *dev);
 struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev);
 int cxl_add_to_region(struct cxl_port *root,
                      struct cxl_endpoint_decoder *cxled);
+struct cxl_dax_region *to_cxl_dax_region(struct device *dev);
 #else
 static inline bool is_cxl_pmem_region(struct device *dev)
 {
@@ -739,6 +747,10 @@ static inline int cxl_add_to_region(struct cxl_port *root,
 {
        return 0;
 }
+static inline struct cxl_dax_region *to_cxl_dax_region(struct device *dev)
+{
+       return NULL;
+}
 #endif
 
 /*
index 1163eb6..bd06e16 100644 (file)
@@ -45,6 +45,19 @@ config DEV_DAX_HMEM
 
          Say M if unsure.
 
+config DEV_DAX_CXL
+       tristate "CXL DAX: direct access to CXL RAM regions"
+       depends on CXL_REGION && DEV_DAX
+       default CXL_REGION && DEV_DAX
+       help
+         CXL RAM regions are either mapped by platform-firmware
+         and published in the initial system-memory map as "System RAM", mapped
+         by platform-firmware as "Soft Reserved", or dynamically provisioned
+         after boot by the CXL driver. In the latter two cases a device-dax
+         instance is created to access that unmapped-by-default address range.
+         Per usual it can remain as dedicated access via a device interface, or
+         converted to "System RAM" via the dax_kmem facility.
+
 config DEV_DAX_HMEM_DEVICES
        depends on DEV_DAX_HMEM && DAX
        def_bool y
index 90a56ca..5ed5c39 100644 (file)
@@ -3,10 +3,12 @@ obj-$(CONFIG_DAX) += dax.o
 obj-$(CONFIG_DEV_DAX) += device_dax.o
 obj-$(CONFIG_DEV_DAX_KMEM) += kmem.o
 obj-$(CONFIG_DEV_DAX_PMEM) += dax_pmem.o
+obj-$(CONFIG_DEV_DAX_CXL) += dax_cxl.o
 
 dax-y := super.o
 dax-y += bus.o
 device_dax-y := device.o
 dax_pmem-y := pmem.o
+dax_cxl-y := cxl.o
 
 obj-y += hmem/
diff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c
new file mode 100644 (file)
index 0000000..ccdf8de
--- /dev/null
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2023 Intel Corporation. All rights reserved. */
+#include <linux/module.h>
+#include <linux/dax.h>
+
+#include "../cxl/cxl.h"
+#include "bus.h"
+
+static int cxl_dax_region_probe(struct device *dev)
+{
+       struct cxl_dax_region *cxlr_dax = to_cxl_dax_region(dev);
+       int nid = phys_to_target_node(cxlr_dax->hpa_range.start);
+       struct cxl_region *cxlr = cxlr_dax->cxlr;
+       struct dax_region *dax_region;
+       struct dev_dax_data data;
+       struct dev_dax *dev_dax;
+
+       if (nid == NUMA_NO_NODE)
+               nid = memory_add_physaddr_to_nid(cxlr_dax->hpa_range.start);
+
+       dax_region = alloc_dax_region(dev, cxlr->id, &cxlr_dax->hpa_range, nid,
+                                     PMD_SIZE, IORESOURCE_DAX_KMEM);
+       if (!dax_region)
+               return -ENOMEM;
+
+       data = (struct dev_dax_data) {
+               .dax_region = dax_region,
+               .id = -1,
+               .size = range_len(&cxlr_dax->hpa_range),
+       };
+       dev_dax = devm_create_dev_dax(&data);
+       if (IS_ERR(dev_dax))
+               return PTR_ERR(dev_dax);
+
+       /* child dev_dax instances now own the lifetime of the dax_region */
+       dax_region_put(dax_region);
+       return 0;
+}
+
+static struct cxl_driver cxl_dax_region_driver = {
+       .name = "cxl_dax_region",
+       .probe = cxl_dax_region_probe,
+       .id = CXL_DEVICE_DAX_REGION,
+       .drv = {
+               .suppress_bind_attrs = true,
+       },
+};
+
+module_cxl_driver(cxl_dax_region_driver);
+MODULE_ALIAS_CXL(CXL_DEVICE_DAX_REGION);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Intel Corporation");
+MODULE_IMPORT_NS(CXL);
index 5ec08f9..e5fe8b3 100644 (file)
@@ -72,6 +72,13 @@ static int hmem_register_device(struct device *host, int target_nid,
        long id;
        int rc;
 
+       if (IS_ENABLED(CONFIG_CXL_REGION) &&
+           region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
+                             IORES_DESC_CXL) != REGION_DISJOINT) {
+               dev_dbg(host, "deferring range to CXL: %pr\n", res);
+               return 0;
+       }
+
        rc = region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
                               IORES_DESC_SOFT_RESERVED);
        if (rc != REGION_INTERSECTS)
@@ -157,6 +164,13 @@ static __exit void dax_hmem_exit(void)
 module_init(dax_hmem_init);
 module_exit(dax_hmem_exit);
 
+/* Allow for CXL to define its own dax regions */
+#if IS_ENABLED(CONFIG_CXL_REGION)
+#if IS_MODULE(CONFIG_CXL_ACPI)
+MODULE_SOFTDEP("pre: cxl_acpi");
+#endif
+#endif
+
 MODULE_ALIAS("platform:hmem*");
 MODULE_ALIAS("platform:hmem_platform*");
 MODULE_LICENSE("GPL v2");