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(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 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 /** 119 * devm_cxl_add_nvdimm_bridge() - add the root of a LIBNVDIMM topology 120 * @host: platform firmware root device 121 * @port: CXL port at the root of a CXL topology 122 * 123 * Return: bridge device that can host cxl_nvdimm objects 124 */ 125 struct cxl_nvdimm_bridge *devm_cxl_add_nvdimm_bridge(struct device *host, 126 struct cxl_port *port) 127 { 128 struct cxl_nvdimm_bridge *cxl_nvb; 129 struct device *dev; 130 int rc; 131 132 if (!IS_ENABLED(CONFIG_CXL_PMEM)) 133 return ERR_PTR(-ENXIO); 134 135 cxl_nvb = cxl_nvdimm_bridge_alloc(port); 136 if (IS_ERR(cxl_nvb)) 137 return cxl_nvb; 138 139 dev = &cxl_nvb->dev; 140 rc = dev_set_name(dev, "nvdimm-bridge%d", cxl_nvb->id); 141 if (rc) 142 goto err; 143 144 rc = device_add(dev); 145 if (rc) 146 goto err; 147 148 rc = devm_add_action_or_reset(host, unregister_nvb, cxl_nvb); 149 if (rc) 150 return ERR_PTR(rc); 151 152 return cxl_nvb; 153 154 err: 155 put_device(dev); 156 return ERR_PTR(rc); 157 } 158 EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm_bridge, "CXL"); 159 160 static void cxl_nvdimm_release(struct device *dev) 161 { 162 struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev); 163 164 kfree(cxl_nvd); 165 } 166 167 static const struct attribute_group *cxl_nvdimm_attribute_groups[] = { 168 &cxl_base_attribute_group, 169 NULL, 170 }; 171 172 const struct device_type cxl_nvdimm_type = { 173 .name = "cxl_nvdimm", 174 .release = cxl_nvdimm_release, 175 .groups = cxl_nvdimm_attribute_groups, 176 }; 177 178 bool is_cxl_nvdimm(struct device *dev) 179 { 180 return dev->type == &cxl_nvdimm_type; 181 } 182 EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, "CXL"); 183 184 struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev) 185 { 186 if (dev_WARN_ONCE(dev, !is_cxl_nvdimm(dev), 187 "not a cxl_nvdimm device\n")) 188 return NULL; 189 return container_of(dev, struct cxl_nvdimm, dev); 190 } 191 EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, "CXL"); 192 193 static struct lock_class_key cxl_nvdimm_key; 194 195 static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_nvdimm_bridge *cxl_nvb, 196 struct cxl_memdev *cxlmd) 197 { 198 struct cxl_nvdimm *cxl_nvd; 199 struct device *dev; 200 201 cxl_nvd = kzalloc(sizeof(*cxl_nvd), GFP_KERNEL); 202 if (!cxl_nvd) 203 return ERR_PTR(-ENOMEM); 204 205 dev = &cxl_nvd->dev; 206 cxl_nvd->cxlmd = cxlmd; 207 cxlmd->cxl_nvd = cxl_nvd; 208 device_initialize(dev); 209 lockdep_set_class(&dev->mutex, &cxl_nvdimm_key); 210 device_set_pm_not_required(dev); 211 dev->parent = &cxlmd->dev; 212 dev->bus = &cxl_bus_type; 213 dev->type = &cxl_nvdimm_type; 214 /* 215 * A "%llx" string is 17-bytes vs dimm_id that is max 216 * NVDIMM_KEY_DESC_LEN 217 */ 218 BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 17 || 219 sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN); 220 sprintf(cxl_nvd->dev_id, "%llx", cxlmd->cxlds->serial); 221 222 return cxl_nvd; 223 } 224 225 static void cxlmd_release_nvdimm(void *_cxlmd) 226 { 227 struct cxl_memdev *cxlmd = _cxlmd; 228 struct cxl_nvdimm *cxl_nvd = cxlmd->cxl_nvd; 229 struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb; 230 231 cxl_nvd->cxlmd = NULL; 232 cxlmd->cxl_nvd = NULL; 233 cxlmd->cxl_nvb = NULL; 234 device_unregister(&cxl_nvd->dev); 235 put_device(&cxl_nvb->dev); 236 } 237 238 /** 239 * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm 240 * @parent_port: parent port for the (to be added) @cxlmd endpoint port 241 * @cxlmd: cxl_memdev instance that will perform LIBNVDIMM operations 242 * 243 * Return: 0 on success negative error code on failure. 244 */ 245 int devm_cxl_add_nvdimm(struct cxl_port *parent_port, 246 struct cxl_memdev *cxlmd) 247 { 248 struct cxl_nvdimm_bridge *cxl_nvb; 249 struct cxl_nvdimm *cxl_nvd; 250 struct device *dev; 251 int rc; 252 253 cxl_nvb = cxl_find_nvdimm_bridge(parent_port); 254 if (!cxl_nvb) 255 return -ENODEV; 256 257 cxl_nvd = cxl_nvdimm_alloc(cxl_nvb, cxlmd); 258 if (IS_ERR(cxl_nvd)) { 259 rc = PTR_ERR(cxl_nvd); 260 goto err_alloc; 261 } 262 cxlmd->cxl_nvb = cxl_nvb; 263 264 dev = &cxl_nvd->dev; 265 rc = dev_set_name(dev, "pmem%d", cxlmd->id); 266 if (rc) 267 goto err; 268 269 rc = device_add(dev); 270 if (rc) 271 goto err; 272 273 dev_dbg(&cxlmd->dev, "register %s\n", dev_name(dev)); 274 275 /* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */ 276 return devm_add_action_or_reset(&cxlmd->dev, cxlmd_release_nvdimm, cxlmd); 277 278 err: 279 put_device(dev); 280 err_alloc: 281 cxlmd->cxl_nvb = NULL; 282 cxlmd->cxl_nvd = NULL; 283 put_device(&cxl_nvb->dev); 284 285 return rc; 286 } 287 EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, "CXL"); 288