| 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, id: cxl_nvb->id); |
| 31 | kfree(objp: 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(parent: &cxl_root->port.dev, |
| 68 | data: &cxl_nvdimm_bridge_type, |
| 69 | match: 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(error: -ENOMEM); |
| 89 | |
| 90 | rc = ida_alloc(ida: &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(objp: cxl_nvb); |
| 108 | return ERR_PTR(error: 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(dev: &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(error: -ENXIO); |
| 134 | |
| 135 | cxl_nvb = cxl_nvdimm_bridge_alloc(port); |
| 136 | if (IS_ERR(ptr: cxl_nvb)) |
| 137 | return cxl_nvb; |
| 138 | |
| 139 | dev = &cxl_nvb->dev; |
| 140 | rc = dev_set_name(dev, name: "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(error: rc); |
| 151 | |
| 152 | return cxl_nvb; |
| 153 | |
| 154 | err: |
| 155 | put_device(dev); |
| 156 | return ERR_PTR(error: 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(objp: 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(error: -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(buf: cxl_nvd->dev_id, fmt: "%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(dev: &cxl_nvd->dev); |
| 235 | put_device(dev: &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(ptr: cxl_nvd)) { |
| 259 | rc = PTR_ERR(ptr: 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, name: "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(dev: &cxl_nvb->dev); |
| 284 | |
| 285 | return rc; |
| 286 | } |
| 287 | EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, "CXL" ); |
| 288 | |