162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Copyright(c) 2020 Intel Corporation. */ 362306a36Sopenharmony_ci#include <linux/device.h> 462306a36Sopenharmony_ci#include <linux/slab.h> 562306a36Sopenharmony_ci#include <linux/idr.h> 662306a36Sopenharmony_ci#include <cxlmem.h> 762306a36Sopenharmony_ci#include <cxl.h> 862306a36Sopenharmony_ci#include "core.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci/** 1162306a36Sopenharmony_ci * DOC: cxl pmem 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * The core CXL PMEM infrastructure supports persistent memory 1462306a36Sopenharmony_ci * provisioning and serves as a bridge to the LIBNVDIMM subsystem. A CXL 1562306a36Sopenharmony_ci * 'bridge' device is added at the root of a CXL device topology if 1662306a36Sopenharmony_ci * platform firmware advertises at least one persistent memory capable 1762306a36Sopenharmony_ci * CXL window. That root-level bridge corresponds to a LIBNVDIMM 'bus' 1862306a36Sopenharmony_ci * device. Then for each cxl_memdev in the CXL device topology a bridge 1962306a36Sopenharmony_ci * device is added to host a LIBNVDIMM dimm object. When these bridges 2062306a36Sopenharmony_ci * are registered native LIBNVDIMM uapis are translated to CXL 2162306a36Sopenharmony_ci * operations, for example, namespace label access commands. 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic DEFINE_IDA(cxl_nvdimm_bridge_ida); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic void cxl_nvdimm_bridge_release(struct device *dev) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci ida_free(&cxl_nvdimm_bridge_ida, cxl_nvb->id); 3162306a36Sopenharmony_ci kfree(cxl_nvb); 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic const struct attribute_group *cxl_nvdimm_bridge_attribute_groups[] = { 3562306a36Sopenharmony_ci &cxl_base_attribute_group, 3662306a36Sopenharmony_ci NULL, 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ciconst struct device_type cxl_nvdimm_bridge_type = { 4062306a36Sopenharmony_ci .name = "cxl_nvdimm_bridge", 4162306a36Sopenharmony_ci .release = cxl_nvdimm_bridge_release, 4262306a36Sopenharmony_ci .groups = cxl_nvdimm_bridge_attribute_groups, 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct cxl_nvdimm_bridge *to_cxl_nvdimm_bridge(struct device *dev) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci if (dev_WARN_ONCE(dev, dev->type != &cxl_nvdimm_bridge_type, 4862306a36Sopenharmony_ci "not a cxl_nvdimm_bridge device\n")) 4962306a36Sopenharmony_ci return NULL; 5062306a36Sopenharmony_ci return container_of(dev, struct cxl_nvdimm_bridge, dev); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm_bridge, CXL); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cibool is_cxl_nvdimm_bridge(struct device *dev) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci return dev->type == &cxl_nvdimm_bridge_type; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm_bridge, CXL); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int match_nvdimm_bridge(struct device *dev, void *data) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci return is_cxl_nvdimm_bridge(dev); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistruct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_memdev *cxlmd) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct cxl_port *port = find_cxl_root(cxlmd->endpoint); 6862306a36Sopenharmony_ci struct device *dev; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (!port) 7162306a36Sopenharmony_ci return NULL; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci dev = device_find_child(&port->dev, NULL, match_nvdimm_bridge); 7462306a36Sopenharmony_ci put_device(&port->dev); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (!dev) 7762306a36Sopenharmony_ci return NULL; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci return to_cxl_nvdimm_bridge(dev); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge, CXL); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic struct lock_class_key cxl_nvdimm_bridge_key; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic struct cxl_nvdimm_bridge *cxl_nvdimm_bridge_alloc(struct cxl_port *port) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb; 8862306a36Sopenharmony_ci struct device *dev; 8962306a36Sopenharmony_ci int rc; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci cxl_nvb = kzalloc(sizeof(*cxl_nvb), GFP_KERNEL); 9262306a36Sopenharmony_ci if (!cxl_nvb) 9362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci rc = ida_alloc(&cxl_nvdimm_bridge_ida, GFP_KERNEL); 9662306a36Sopenharmony_ci if (rc < 0) 9762306a36Sopenharmony_ci goto err; 9862306a36Sopenharmony_ci cxl_nvb->id = rc; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci dev = &cxl_nvb->dev; 10162306a36Sopenharmony_ci cxl_nvb->port = port; 10262306a36Sopenharmony_ci device_initialize(dev); 10362306a36Sopenharmony_ci lockdep_set_class(&dev->mutex, &cxl_nvdimm_bridge_key); 10462306a36Sopenharmony_ci device_set_pm_not_required(dev); 10562306a36Sopenharmony_ci dev->parent = &port->dev; 10662306a36Sopenharmony_ci dev->bus = &cxl_bus_type; 10762306a36Sopenharmony_ci dev->type = &cxl_nvdimm_bridge_type; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return cxl_nvb; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cierr: 11262306a36Sopenharmony_ci kfree(cxl_nvb); 11362306a36Sopenharmony_ci return ERR_PTR(rc); 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic void unregister_nvb(void *_cxl_nvb) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci device_unregister(&cxl_nvb->dev); 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * devm_cxl_add_nvdimm_bridge() - add the root of a LIBNVDIMM topology 12562306a36Sopenharmony_ci * @host: platform firmware root device 12662306a36Sopenharmony_ci * @port: CXL port at the root of a CXL topology 12762306a36Sopenharmony_ci * 12862306a36Sopenharmony_ci * Return: bridge device that can host cxl_nvdimm objects 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_cistruct cxl_nvdimm_bridge *devm_cxl_add_nvdimm_bridge(struct device *host, 13162306a36Sopenharmony_ci struct cxl_port *port) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb; 13462306a36Sopenharmony_ci struct device *dev; 13562306a36Sopenharmony_ci int rc; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_CXL_PMEM)) 13862306a36Sopenharmony_ci return ERR_PTR(-ENXIO); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci cxl_nvb = cxl_nvdimm_bridge_alloc(port); 14162306a36Sopenharmony_ci if (IS_ERR(cxl_nvb)) 14262306a36Sopenharmony_ci return cxl_nvb; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci dev = &cxl_nvb->dev; 14562306a36Sopenharmony_ci rc = dev_set_name(dev, "nvdimm-bridge%d", cxl_nvb->id); 14662306a36Sopenharmony_ci if (rc) 14762306a36Sopenharmony_ci goto err; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci rc = device_add(dev); 15062306a36Sopenharmony_ci if (rc) 15162306a36Sopenharmony_ci goto err; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci rc = devm_add_action_or_reset(host, unregister_nvb, cxl_nvb); 15462306a36Sopenharmony_ci if (rc) 15562306a36Sopenharmony_ci return ERR_PTR(rc); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return cxl_nvb; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cierr: 16062306a36Sopenharmony_ci put_device(dev); 16162306a36Sopenharmony_ci return ERR_PTR(rc); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm_bridge, CXL); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic void cxl_nvdimm_release(struct device *dev) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci kfree(cxl_nvd); 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic const struct attribute_group *cxl_nvdimm_attribute_groups[] = { 17362306a36Sopenharmony_ci &cxl_base_attribute_group, 17462306a36Sopenharmony_ci NULL, 17562306a36Sopenharmony_ci}; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ciconst struct device_type cxl_nvdimm_type = { 17862306a36Sopenharmony_ci .name = "cxl_nvdimm", 17962306a36Sopenharmony_ci .release = cxl_nvdimm_release, 18062306a36Sopenharmony_ci .groups = cxl_nvdimm_attribute_groups, 18162306a36Sopenharmony_ci}; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cibool is_cxl_nvdimm(struct device *dev) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci return dev->type == &cxl_nvdimm_type; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, CXL); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistruct cxl_nvdimm *to_cxl_nvdimm(struct device *dev) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci if (dev_WARN_ONCE(dev, !is_cxl_nvdimm(dev), 19262306a36Sopenharmony_ci "not a cxl_nvdimm device\n")) 19362306a36Sopenharmony_ci return NULL; 19462306a36Sopenharmony_ci return container_of(dev, struct cxl_nvdimm, dev); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, CXL); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic struct lock_class_key cxl_nvdimm_key; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_nvdimm_bridge *cxl_nvb, 20162306a36Sopenharmony_ci struct cxl_memdev *cxlmd) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct cxl_nvdimm *cxl_nvd; 20462306a36Sopenharmony_ci struct device *dev; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci cxl_nvd = kzalloc(sizeof(*cxl_nvd), GFP_KERNEL); 20762306a36Sopenharmony_ci if (!cxl_nvd) 20862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci dev = &cxl_nvd->dev; 21162306a36Sopenharmony_ci cxl_nvd->cxlmd = cxlmd; 21262306a36Sopenharmony_ci cxlmd->cxl_nvd = cxl_nvd; 21362306a36Sopenharmony_ci device_initialize(dev); 21462306a36Sopenharmony_ci lockdep_set_class(&dev->mutex, &cxl_nvdimm_key); 21562306a36Sopenharmony_ci device_set_pm_not_required(dev); 21662306a36Sopenharmony_ci dev->parent = &cxlmd->dev; 21762306a36Sopenharmony_ci dev->bus = &cxl_bus_type; 21862306a36Sopenharmony_ci dev->type = &cxl_nvdimm_type; 21962306a36Sopenharmony_ci /* 22062306a36Sopenharmony_ci * A "%llx" string is 17-bytes vs dimm_id that is max 22162306a36Sopenharmony_ci * NVDIMM_KEY_DESC_LEN 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 17 || 22462306a36Sopenharmony_ci sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN); 22562306a36Sopenharmony_ci sprintf(cxl_nvd->dev_id, "%llx", cxlmd->cxlds->serial); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return cxl_nvd; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic void cxlmd_release_nvdimm(void *_cxlmd) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct cxl_memdev *cxlmd = _cxlmd; 23362306a36Sopenharmony_ci struct cxl_nvdimm *cxl_nvd = cxlmd->cxl_nvd; 23462306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci cxl_nvd->cxlmd = NULL; 23762306a36Sopenharmony_ci cxlmd->cxl_nvd = NULL; 23862306a36Sopenharmony_ci cxlmd->cxl_nvb = NULL; 23962306a36Sopenharmony_ci device_unregister(&cxl_nvd->dev); 24062306a36Sopenharmony_ci put_device(&cxl_nvb->dev); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci/** 24462306a36Sopenharmony_ci * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm 24562306a36Sopenharmony_ci * @cxlmd: cxl_memdev instance that will perform LIBNVDIMM operations 24662306a36Sopenharmony_ci * 24762306a36Sopenharmony_ci * Return: 0 on success negative error code on failure. 24862306a36Sopenharmony_ci */ 24962306a36Sopenharmony_ciint devm_cxl_add_nvdimm(struct cxl_memdev *cxlmd) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci struct cxl_nvdimm_bridge *cxl_nvb; 25262306a36Sopenharmony_ci struct cxl_nvdimm *cxl_nvd; 25362306a36Sopenharmony_ci struct device *dev; 25462306a36Sopenharmony_ci int rc; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci cxl_nvb = cxl_find_nvdimm_bridge(cxlmd); 25762306a36Sopenharmony_ci if (!cxl_nvb) 25862306a36Sopenharmony_ci return -ENODEV; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci cxl_nvd = cxl_nvdimm_alloc(cxl_nvb, cxlmd); 26162306a36Sopenharmony_ci if (IS_ERR(cxl_nvd)) { 26262306a36Sopenharmony_ci rc = PTR_ERR(cxl_nvd); 26362306a36Sopenharmony_ci goto err_alloc; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci cxlmd->cxl_nvb = cxl_nvb; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci dev = &cxl_nvd->dev; 26862306a36Sopenharmony_ci rc = dev_set_name(dev, "pmem%d", cxlmd->id); 26962306a36Sopenharmony_ci if (rc) 27062306a36Sopenharmony_ci goto err; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci rc = device_add(dev); 27362306a36Sopenharmony_ci if (rc) 27462306a36Sopenharmony_ci goto err; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci dev_dbg(&cxlmd->dev, "register %s\n", dev_name(dev)); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci /* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */ 27962306a36Sopenharmony_ci return devm_add_action_or_reset(&cxlmd->dev, cxlmd_release_nvdimm, cxlmd); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cierr: 28262306a36Sopenharmony_ci put_device(dev); 28362306a36Sopenharmony_cierr_alloc: 28462306a36Sopenharmony_ci cxlmd->cxl_nvb = NULL; 28562306a36Sopenharmony_ci cxlmd->cxl_nvd = NULL; 28662306a36Sopenharmony_ci put_device(&cxl_nvb->dev); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci return rc; 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, CXL); 291