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 24 static DEFINE_IDA(cxl_nvdimm_bridge_ida); 25 26 static 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 34 static const struct attribute_group *cxl_nvdimm_bridge_attribute_groups[] = { 35 &cxl_base_attribute_group, 36 NULL, 37 }; 38 39 const 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 45 struct 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 } 52 EXPORT_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 */ 59 struct 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 } 76 EXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge, "CXL"); 77 78 static struct lock_class_key cxl_nvdimm_bridge_key; 79 80 static 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_obj(*cxl_nvb); 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 106 err: 107 kfree(cxl_nvb); 108 return ERR_PTR(rc); 109 } 110 111 static 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 118 static 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 127 struct 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 161 err: 162 put_device(dev); 163 return ERR_PTR(rc); 164 } 165 EXPORT_SYMBOL_FOR_MODULES(__devm_cxl_add_nvdimm_bridge, "cxl_pmem"); 166 167 static 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 174 static const struct attribute_group *cxl_nvdimm_attribute_groups[] = { 175 &cxl_base_attribute_group, 176 NULL, 177 }; 178 179 const struct device_type cxl_nvdimm_type = { 180 .name = "cxl_nvdimm", 181 .release = cxl_nvdimm_release, 182 .groups = cxl_nvdimm_attribute_groups, 183 }; 184 185 bool is_cxl_nvdimm(struct device *dev) 186 { 187 return dev->type == &cxl_nvdimm_type; 188 } 189 EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, "CXL"); 190 191 struct 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 } 198 EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, "CXL"); 199 200 static struct lock_class_key cxl_nvdimm_key; 201 202 static 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_obj(*cxl_nvd); 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 * dev_id is the nvdimm dimm_id used for security key lookup. 223 * It must match id_show(), which emits the CXL serial as an 224 * unsigned decimal. A u64 decimal string is at most 20 digits 225 * plus NUL. 226 */ 227 BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 21 || 228 sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN); 229 sprintf(cxl_nvd->dev_id, "%llu", cxlmd->cxlds->serial); 230 231 return cxl_nvd; 232 } 233 234 static void cxlmd_release_nvdimm(void *_cxlmd) 235 { 236 struct cxl_memdev *cxlmd = _cxlmd; 237 struct cxl_nvdimm *cxl_nvd = cxlmd->cxl_nvd; 238 struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb; 239 240 cxl_nvd->cxlmd = NULL; 241 cxlmd->cxl_nvd = NULL; 242 cxlmd->cxl_nvb = NULL; 243 device_unregister(&cxl_nvd->dev); 244 put_device(&cxl_nvb->dev); 245 } 246 247 /** 248 * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm 249 * @host: host device for devm operations 250 * @port: any port in the CXL topology to find the nvdimm-bridge device 251 * @cxlmd: parent of the to be created cxl_nvdimm device 252 * 253 * Return: 0 on success negative error code on failure. 254 */ 255 int devm_cxl_add_nvdimm(struct device *host, struct cxl_port *port, 256 struct cxl_memdev *cxlmd) 257 { 258 struct cxl_nvdimm_bridge *cxl_nvb; 259 struct cxl_nvdimm *cxl_nvd; 260 struct device *dev; 261 int rc; 262 263 cxl_nvb = cxl_find_nvdimm_bridge(port); 264 if (!cxl_nvb) 265 return -ENODEV; 266 267 /* 268 * Take the uport_dev lock to guard against race of nvdimm_bus object. 269 * cxl_acpi_probe() registers the nvdimm_bus and is done under the 270 * root port uport_dev lock. 271 * 272 * Take the cxl_nvb device lock to ensure that cxl_nvb driver is in a 273 * consistent state. And the driver registers nvdimm_bus. 274 */ 275 guard(device)(cxl_nvb->port->uport_dev); 276 guard(device)(&cxl_nvb->dev); 277 if (!cxl_nvb->nvdimm_bus) { 278 rc = -ENODEV; 279 goto err_alloc; 280 } 281 282 cxl_nvd = cxl_nvdimm_alloc(cxl_nvb, cxlmd); 283 if (IS_ERR(cxl_nvd)) { 284 rc = PTR_ERR(cxl_nvd); 285 goto err_alloc; 286 } 287 cxlmd->cxl_nvb = cxl_nvb; 288 289 dev = &cxl_nvd->dev; 290 rc = dev_set_name(dev, "pmem%d", cxlmd->id); 291 if (rc) 292 goto err; 293 294 rc = device_add(dev); 295 if (rc) 296 goto err; 297 298 dev_dbg(host, "register %s\n", dev_name(dev)); 299 300 /* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */ 301 return devm_add_action_or_reset(host, cxlmd_release_nvdimm, cxlmd); 302 303 err: 304 put_device(dev); 305 err_alloc: 306 cxlmd->cxl_nvb = NULL; 307 cxlmd->cxl_nvd = NULL; 308 put_device(&cxl_nvb->dev); 309 310 return rc; 311 } 312 EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, "CXL"); 313