1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright(c) 2020 Intel Corporation. */
3 #include <linux/device.h>
4 #include <linux/slab.h>
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.
24 static DEFINE_IDA(cxl_nvdimm_bridge_ida
);
26 static void cxl_nvdimm_bridge_release(struct device
*dev
)
28 struct cxl_nvdimm_bridge
*cxl_nvb
= to_cxl_nvdimm_bridge(dev
);
30 ida_free(&cxl_nvdimm_bridge_ida
, cxl_nvb
->id
);
34 static const struct attribute_group
*cxl_nvdimm_bridge_attribute_groups
[] = {
35 &cxl_base_attribute_group
,
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
,
45 struct cxl_nvdimm_bridge
*to_cxl_nvdimm_bridge(struct device
*dev
)
47 if (dev_WARN_ONCE(dev
, dev
->type
!= &cxl_nvdimm_bridge_type
,
48 "not a cxl_nvdimm_bridge device\n"))
50 return container_of(dev
, struct cxl_nvdimm_bridge
, dev
);
52 EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm_bridge
, "CXL");
54 bool is_cxl_nvdimm_bridge(struct device
*dev
)
56 return dev
->type
== &cxl_nvdimm_bridge_type
;
58 EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm_bridge
, "CXL");
60 static int match_nvdimm_bridge(struct device
*dev
, void *data
)
62 return is_cxl_nvdimm_bridge(dev
);
66 * cxl_find_nvdimm_bridge() - find a bridge device relative to a port
67 * @port: any descendant port of an nvdimm-bridge associated
70 struct cxl_nvdimm_bridge
*cxl_find_nvdimm_bridge(struct cxl_port
*port
)
72 struct cxl_root
*cxl_root
__free(put_cxl_root
) = find_cxl_root(port
);
78 dev
= device_find_child(&cxl_root
->port
.dev
, NULL
, match_nvdimm_bridge
);
83 return to_cxl_nvdimm_bridge(dev
);
85 EXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge
, "CXL");
87 static struct lock_class_key cxl_nvdimm_bridge_key
;
89 static struct cxl_nvdimm_bridge
*cxl_nvdimm_bridge_alloc(struct cxl_port
*port
)
91 struct cxl_nvdimm_bridge
*cxl_nvb
;
95 cxl_nvb
= kzalloc(sizeof(*cxl_nvb
), GFP_KERNEL
);
97 return ERR_PTR(-ENOMEM
);
99 rc
= ida_alloc(&cxl_nvdimm_bridge_ida
, GFP_KERNEL
);
105 cxl_nvb
->port
= port
;
106 device_initialize(dev
);
107 lockdep_set_class(&dev
->mutex
, &cxl_nvdimm_bridge_key
);
108 device_set_pm_not_required(dev
);
109 dev
->parent
= &port
->dev
;
110 dev
->bus
= &cxl_bus_type
;
111 dev
->type
= &cxl_nvdimm_bridge_type
;
120 static void unregister_nvb(void *_cxl_nvb
)
122 struct cxl_nvdimm_bridge
*cxl_nvb
= _cxl_nvb
;
124 device_unregister(&cxl_nvb
->dev
);
128 * devm_cxl_add_nvdimm_bridge() - add the root of a LIBNVDIMM topology
129 * @host: platform firmware root device
130 * @port: CXL port at the root of a CXL topology
132 * Return: bridge device that can host cxl_nvdimm objects
134 struct cxl_nvdimm_bridge
*devm_cxl_add_nvdimm_bridge(struct device
*host
,
135 struct cxl_port
*port
)
137 struct cxl_nvdimm_bridge
*cxl_nvb
;
141 if (!IS_ENABLED(CONFIG_CXL_PMEM
))
142 return ERR_PTR(-ENXIO
);
144 cxl_nvb
= cxl_nvdimm_bridge_alloc(port
);
149 rc
= dev_set_name(dev
, "nvdimm-bridge%d", cxl_nvb
->id
);
153 rc
= device_add(dev
);
157 rc
= devm_add_action_or_reset(host
, unregister_nvb
, cxl_nvb
);
167 EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm_bridge
, "CXL");
169 static void cxl_nvdimm_release(struct device
*dev
)
171 struct cxl_nvdimm
*cxl_nvd
= to_cxl_nvdimm(dev
);
176 static const struct attribute_group
*cxl_nvdimm_attribute_groups
[] = {
177 &cxl_base_attribute_group
,
181 const struct device_type cxl_nvdimm_type
= {
182 .name
= "cxl_nvdimm",
183 .release
= cxl_nvdimm_release
,
184 .groups
= cxl_nvdimm_attribute_groups
,
187 bool is_cxl_nvdimm(struct device
*dev
)
189 return dev
->type
== &cxl_nvdimm_type
;
191 EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm
, "CXL");
193 struct cxl_nvdimm
*to_cxl_nvdimm(struct device
*dev
)
195 if (dev_WARN_ONCE(dev
, !is_cxl_nvdimm(dev
),
196 "not a cxl_nvdimm device\n"))
198 return container_of(dev
, struct cxl_nvdimm
, dev
);
200 EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm
, "CXL");
202 static struct lock_class_key cxl_nvdimm_key
;
204 static struct cxl_nvdimm
*cxl_nvdimm_alloc(struct cxl_nvdimm_bridge
*cxl_nvb
,
205 struct cxl_memdev
*cxlmd
)
207 struct cxl_nvdimm
*cxl_nvd
;
210 cxl_nvd
= kzalloc(sizeof(*cxl_nvd
), GFP_KERNEL
);
212 return ERR_PTR(-ENOMEM
);
215 cxl_nvd
->cxlmd
= cxlmd
;
216 cxlmd
->cxl_nvd
= cxl_nvd
;
217 device_initialize(dev
);
218 lockdep_set_class(&dev
->mutex
, &cxl_nvdimm_key
);
219 device_set_pm_not_required(dev
);
220 dev
->parent
= &cxlmd
->dev
;
221 dev
->bus
= &cxl_bus_type
;
222 dev
->type
= &cxl_nvdimm_type
;
224 * A "%llx" string is 17-bytes vs dimm_id that is max
225 * NVDIMM_KEY_DESC_LEN
227 BUILD_BUG_ON(sizeof(cxl_nvd
->dev_id
) < 17 ||
228 sizeof(cxl_nvd
->dev_id
) > NVDIMM_KEY_DESC_LEN
);
229 sprintf(cxl_nvd
->dev_id
, "%llx", cxlmd
->cxlds
->serial
);
234 static void cxlmd_release_nvdimm(void *_cxlmd
)
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
;
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
);
248 * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm
249 * @parent_port: parent port for the (to be added) @cxlmd endpoint port
250 * @cxlmd: cxl_memdev instance that will perform LIBNVDIMM operations
252 * Return: 0 on success negative error code on failure.
254 int devm_cxl_add_nvdimm(struct cxl_port
*parent_port
,
255 struct cxl_memdev
*cxlmd
)
257 struct cxl_nvdimm_bridge
*cxl_nvb
;
258 struct cxl_nvdimm
*cxl_nvd
;
262 cxl_nvb
= cxl_find_nvdimm_bridge(parent_port
);
266 cxl_nvd
= cxl_nvdimm_alloc(cxl_nvb
, cxlmd
);
267 if (IS_ERR(cxl_nvd
)) {
268 rc
= PTR_ERR(cxl_nvd
);
271 cxlmd
->cxl_nvb
= cxl_nvb
;
274 rc
= dev_set_name(dev
, "pmem%d", cxlmd
->id
);
278 rc
= device_add(dev
);
282 dev_dbg(&cxlmd
->dev
, "register %s\n", dev_name(dev
));
284 /* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */
285 return devm_add_action_or_reset(&cxlmd
->dev
, cxlmd_release_nvdimm
, cxlmd
);
290 cxlmd
->cxl_nvb
= NULL
;
291 cxlmd
->cxl_nvd
= NULL
;
292 put_device(&cxl_nvb
->dev
);
296 EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm
, "CXL");