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 |
Definitions
- cxl_nvdimm_bridge_ida
- cxl_nvdimm_bridge_release
- cxl_nvdimm_bridge_attribute_groups
- cxl_nvdimm_bridge_type
- to_cxl_nvdimm_bridge
- cxl_find_nvdimm_bridge
- cxl_nvdimm_bridge_key
- cxl_nvdimm_bridge_alloc
- unregister_nvb
- devm_cxl_add_nvdimm_bridge
- cxl_nvdimm_release
- cxl_nvdimm_attribute_groups
- cxl_nvdimm_type
- is_cxl_nvdimm
- to_cxl_nvdimm
- cxl_nvdimm_key
- cxl_nvdimm_alloc
- cxlmd_release_nvdimm
Improve your Profiling and Debugging skills
Find out more