FILE
pmem.c
File preview with syntax highlighting.
linux-6.19.14/drivers/cxl/core/pmem.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* Copyright(c) 2020 Intel Corporation. */ 3#include <linux/device.h> 4#include <linux/slab.h> 5#include <linux/idr.h> 6#include <cxlmem.h> 7#include <cxl.h> 8#include "core.h" 9 10/** 11 * DOC: cxl pmem 12 * 13 * The core CXL PMEM infrastructure supports persistent memory 14 * provisioning and serves as a bridge to the LIBNVDIMM subsystem. A CXL 15 * 'bridge' device is added at the root of a CXL device topology if 16 * platform firmware advertises at least one persistent memory capable 17 * CXL window. That root-level bridge corresponds to a LIBNVDIMM 'bus' 18 * device. Then for each cxl_memdev in the CXL device topology a bridge 19 * device is added to host a LIBNVDIMM dimm object. When these bridges 20 * are registered native LIBNVDIMM uapis are translated to CXL 21 * operations, for example, namespace label access commands. 22 */ 23 24static DEFINE_IDA(cxl_nvdimm_bridge_ida); 25 26static void cxl_nvdimm_bridge_release(struct device *dev) 27{ 28 struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev); 29 30 ida_free(&cxl_nvdimm_bridge_ida, cxl_nvb->id); 31 kfree(cxl_nvb); 32} 33 34static const struct attribute_group *cxl_nvdimm_bridge_attribute_groups[] = { 35 &cxl_base_attribute_group, 36 NULL, 37}; 38 39const struct device_type cxl_nvdimm_bridge_type = { 40 .name = "cxl_nvdimm_bridge", 41 .release = cxl_nvdimm_bridge_release, 42 .groups = cxl_nvdimm_bridge_attribute_groups, 43}; 44 45struct cxl_nvdimm_bridge *to_cxl_nvdimm_bridge(struct device *dev) 46{ 47 if (dev_WARN_ONCE(dev, dev->type != &cxl_nvdimm_bridge_type, 48 "not a cxl_nvdimm_bridge device\n")) 49 return NULL; 50 return container_of(dev, struct cxl_nvdimm_bridge, dev); 51} 52EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm_bridge, "CXL"); 53 54/** 55 * cxl_find_nvdimm_bridge() - find a bridge device relative to a port 56 * @port: any descendant port of an nvdimm-bridge associated 57 * root-cxl-port 58 */ 59struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_port *port) 60{ 61 struct cxl_root *cxl_root __free(put_cxl_root) = find_cxl_root(port); 62 struct device *dev; 63 64 if (!cxl_root) 65 return NULL; 66 67 dev = device_find_child(&cxl_root->port.dev, 68 &cxl_nvdimm_bridge_type, 69 device_match_type); 70 71 if (!dev) 72 return NULL; 73 74 return to_cxl_nvdimm_bridge(dev); 75} 76EXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge, "CXL"); 77 78static struct lock_class_key cxl_nvdimm_bridge_key; 79 80static struct cxl_nvdimm_bridge *cxl_nvdimm_bridge_alloc(struct cxl_port *port) 81{ 82 struct cxl_nvdimm_bridge *cxl_nvb; 83 struct device *dev; 84 int rc; 85 86 cxl_nvb = kzalloc(sizeof(*cxl_nvb), GFP_KERNEL); 87 if (!cxl_nvb) 88 return ERR_PTR(-ENOMEM); 89 90 rc = ida_alloc(&cxl_nvdimm_bridge_ida, GFP_KERNEL); 91 if (rc < 0) 92 goto err; 93 cxl_nvb->id = rc; 94 95 dev = &cxl_nvb->dev; 96 cxl_nvb->port = port; 97 device_initialize(dev); 98 lockdep_set_class(&dev->mutex, &cxl_nvdimm_bridge_key); 99 device_set_pm_not_required(dev); 100 dev->parent = &port->dev; 101 dev->bus = &cxl_bus_type; 102 dev->type = &cxl_nvdimm_bridge_type; 103 104 return cxl_nvb; 105 106err: 107 kfree(cxl_nvb); 108 return ERR_PTR(rc); 109} 110 111static void unregister_nvb(void *_cxl_nvb) 112{ 113 struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb; 114 115 device_unregister(&cxl_nvb->dev); 116} 117 118static bool cxl_nvdimm_bridge_failed_attach(struct cxl_nvdimm_bridge *cxl_nvb) 119{ 120 struct device *dev = &cxl_nvb->dev; 121 122 guard(device)(dev); 123 /* If the device has no driver, then it failed to attach. */ 124 return dev->driver == NULL; 125} 126 127struct cxl_nvdimm_bridge *__devm_cxl_add_nvdimm_bridge(struct device *host, 128 struct cxl_port *port) 129{ 130 struct cxl_nvdimm_bridge *cxl_nvb; 131 struct device *dev; 132 int rc; 133 134 if (!IS_ENABLED(CONFIG_CXL_PMEM)) 135 return ERR_PTR(-ENXIO); 136 137 cxl_nvb = cxl_nvdimm_bridge_alloc(port); 138 if (IS_ERR(cxl_nvb)) 139 return cxl_nvb; 140 141 dev = &cxl_nvb->dev; 142 rc = dev_set_name(dev, "nvdimm-bridge%d", cxl_nvb->id); 143 if (rc) 144 goto err; 145 146 rc = device_add(dev); 147 if (rc) 148 goto err; 149 150 if (cxl_nvdimm_bridge_failed_attach(cxl_nvb)) { 151 unregister_nvb(cxl_nvb); 152 return ERR_PTR(-ENODEV); 153 } 154 155 rc = devm_add_action_or_reset(host, unregister_nvb, cxl_nvb); 156 if (rc) 157 return ERR_PTR(rc); 158 159 return cxl_nvb; 160 161err: 162 put_device(dev); 163 return ERR_PTR(rc); 164} 165EXPORT_SYMBOL_FOR_MODULES(__devm_cxl_add_nvdimm_bridge, "cxl_pmem"); 166 167static void cxl_nvdimm_release(struct device *dev) 168{ 169 struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev); 170 171 kfree(cxl_nvd); 172} 173 174static const struct attribute_group *cxl_nvdimm_attribute_groups[] = { 175 &cxl_base_attribute_group, 176 NULL, 177}; 178 179const struct device_type cxl_nvdimm_type = { 180 .name = "cxl_nvdimm", 181 .release = cxl_nvdimm_release, 182 .groups = cxl_nvdimm_attribute_groups, 183}; 184 185bool is_cxl_nvdimm(struct device *dev) 186{ 187 return dev->type == &cxl_nvdimm_type; 188} 189EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, "CXL"); 190 191struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev) 192{ 193 if (dev_WARN_ONCE(dev, !is_cxl_nvdimm(dev), 194 "not a cxl_nvdimm device\n")) 195 return NULL; 196 return container_of(dev, struct cxl_nvdimm, dev); 197} 198EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, "CXL"); 199 200static struct lock_class_key cxl_nvdimm_key; 201 202static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_nvdimm_bridge *cxl_nvb, 203 struct cxl_memdev *cxlmd) 204{ 205 struct cxl_nvdimm *cxl_nvd; 206 struct device *dev; 207 208 cxl_nvd = kzalloc(sizeof(*cxl_nvd), GFP_KERNEL); 209 if (!cxl_nvd) 210 return ERR_PTR(-ENOMEM); 211 212 dev = &cxl_nvd->dev; 213 cxl_nvd->cxlmd = cxlmd; 214 cxlmd->cxl_nvd = cxl_nvd; 215 device_initialize(dev); 216 lockdep_set_class(&dev->mutex, &cxl_nvdimm_key); 217 device_set_pm_not_required(dev); 218 dev->parent = &cxlmd->dev; 219 dev->bus = &cxl_bus_type; 220 dev->type = &cxl_nvdimm_type; 221 /* 222 * A "%llx" string is 17-bytes vs dimm_id that is max 223 * NVDIMM_KEY_DESC_LEN 224 */ 225 BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 17 || 226 sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN); 227 sprintf(cxl_nvd->dev_id, "%llx", cxlmd->cxlds->serial); 228 229 return cxl_nvd; 230} 231 232static void cxlmd_release_nvdimm(void *_cxlmd) 233{ 234 struct cxl_memdev *cxlmd = _cxlmd; 235 struct cxl_nvdimm *cxl_nvd = cxlmd->cxl_nvd; 236 struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb; 237 238 cxl_nvd->cxlmd = NULL; 239 cxlmd->cxl_nvd = NULL; 240 cxlmd->cxl_nvb = NULL; 241 device_unregister(&cxl_nvd->dev); 242 put_device(&cxl_nvb->dev); 243} 244 245/** 246 * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm 247 * @parent_port: parent port for the (to be added) @cxlmd endpoint port 248 * @cxlmd: cxl_memdev instance that will perform LIBNVDIMM operations 249 * 250 * Return: 0 on success negative error code on failure. 251 */ 252int devm_cxl_add_nvdimm(struct cxl_port *parent_port, 253 struct cxl_memdev *cxlmd) 254{ 255 struct cxl_nvdimm_bridge *cxl_nvb; 256 struct cxl_nvdimm *cxl_nvd; 257 struct device *dev; 258 int rc; 259 260 cxl_nvb = cxl_find_nvdimm_bridge(parent_port); 261 if (!cxl_nvb) 262 return -ENODEV; 263 264 /* 265 * Take the uport_dev lock to guard against race of nvdimm_bus object. 266 * cxl_acpi_probe() registers the nvdimm_bus and is done under the 267 * root port uport_dev lock. 268 * 269 * Take the cxl_nvb device lock to ensure that cxl_nvb driver is in a 270 * consistent state. And the driver registers nvdimm_bus. 271 */ 272 guard(device)(cxl_nvb->port->uport_dev); 273 guard(device)(&cxl_nvb->dev); 274 if (!cxl_nvb->nvdimm_bus) { 275 rc = -ENODEV; 276 goto err_alloc; 277 } 278 279 cxl_nvd = cxl_nvdimm_alloc(cxl_nvb, cxlmd); 280 if (IS_ERR(cxl_nvd)) { 281 rc = PTR_ERR(cxl_nvd); 282 goto err_alloc; 283 } 284 cxlmd->cxl_nvb = cxl_nvb; 285 286 dev = &cxl_nvd->dev; 287 rc = dev_set_name(dev, "pmem%d", cxlmd->id); 288 if (rc) 289 goto err; 290 291 rc = device_add(dev); 292 if (rc) 293 goto err; 294 295 dev_dbg(&cxlmd->dev, "register %s\n", dev_name(dev)); 296 297 /* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */ 298 return devm_add_action_or_reset(&cxlmd->dev, cxlmd_release_nvdimm, cxlmd); 299 300err: 301 put_device(dev); 302err_alloc: 303 cxlmd->cxl_nvb = NULL; 304 cxlmd->cxl_nvd = NULL; 305 put_device(&cxl_nvb->dev); 306 307 return rc; 308} 309EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, "CXL");
~/
Read-only file preview.