1 /*
2 * virnvme.c: helper APIs for managing NVMe devices
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see
16 * <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20
21 #include "virnvme.h"
22 #include "virobject.h"
23 #include "virpci.h"
24 #include "viralloc.h"
25 #include "virlog.h"
26 #include "virstring.h"
27
28 VIR_LOG_INIT("util.nvme");
29 #define VIR_FROM_THIS VIR_FROM_NONE
30
31 struct _virNVMeDevice {
32 virPCIDeviceAddress address; /* PCI address of controller */
33 unsigned int namespace; /* Namespace ID */
34 bool managed;
35
36 char *drvname;
37 char *domname;
38 };
39
40
41 struct _virNVMeDeviceList {
42 virObjectLockable parent;
43
44 size_t count;
45 virNVMeDevice **devs;
46 };
47
48
49 static virClass *virNVMeDeviceListClass;
50
51 static void virNVMeDeviceListDispose(void *obj);
52
53 static int
virNVMeOnceInit(void)54 virNVMeOnceInit(void)
55 {
56 if (!VIR_CLASS_NEW(virNVMeDeviceList, virClassForObjectLockable()))
57 return -1;
58
59 return 0;
60 }
61
62 VIR_ONCE_GLOBAL_INIT(virNVMe);
63
64
65 virNVMeDevice *
virNVMeDeviceNew(const virPCIDeviceAddress * address,unsigned long namespace,bool managed)66 virNVMeDeviceNew(const virPCIDeviceAddress *address,
67 unsigned long namespace,
68 bool managed)
69 {
70 virNVMeDevice *dev = NULL;
71
72 dev = g_new0(virNVMeDevice, 1);
73
74 virPCIDeviceAddressCopy(&dev->address, address);
75 dev->namespace = namespace;
76 dev->managed = managed;
77
78 return dev;
79 }
80
81
82 void
virNVMeDeviceFree(virNVMeDevice * dev)83 virNVMeDeviceFree(virNVMeDevice *dev)
84 {
85 if (!dev)
86 return;
87
88 virNVMeDeviceUsedByClear(dev);
89 g_free(dev);
90 }
91
92
93 virNVMeDevice *
virNVMeDeviceCopy(const virNVMeDevice * dev)94 virNVMeDeviceCopy(const virNVMeDevice *dev)
95 {
96 virNVMeDevice *copy = NULL;
97
98 copy = g_new0(virNVMeDevice, 1);
99 copy->drvname = g_strdup(dev->drvname);
100 copy->domname = g_strdup(dev->domname);
101
102 virPCIDeviceAddressCopy(©->address, &dev->address);
103 copy->namespace = dev->namespace;
104 copy->managed = dev->managed;
105
106 return copy;
107 }
108
109
110 const virPCIDeviceAddress *
virNVMeDeviceAddressGet(const virNVMeDevice * dev)111 virNVMeDeviceAddressGet(const virNVMeDevice *dev)
112 {
113 return &dev->address;
114 }
115
116
117 void
virNVMeDeviceUsedByClear(virNVMeDevice * dev)118 virNVMeDeviceUsedByClear(virNVMeDevice *dev)
119 {
120 VIR_FREE(dev->drvname);
121 VIR_FREE(dev->domname);
122 }
123
124
125 void
virNVMeDeviceUsedByGet(const virNVMeDevice * dev,const char ** drv,const char ** dom)126 virNVMeDeviceUsedByGet(const virNVMeDevice *dev,
127 const char **drv,
128 const char **dom)
129 {
130 *drv = dev->drvname;
131 *dom = dev->domname;
132 }
133
134
135 void
virNVMeDeviceUsedBySet(virNVMeDevice * dev,const char * drv,const char * dom)136 virNVMeDeviceUsedBySet(virNVMeDevice *dev,
137 const char *drv,
138 const char *dom)
139 {
140 dev->drvname = g_strdup(drv);
141 dev->domname = g_strdup(dom);
142 }
143
144
145 virNVMeDeviceList *
virNVMeDeviceListNew(void)146 virNVMeDeviceListNew(void)
147 {
148 virNVMeDeviceList *list;
149
150 if (virNVMeInitialize() < 0)
151 return NULL;
152
153 if (!(list = virObjectLockableNew(virNVMeDeviceListClass)))
154 return NULL;
155
156 return list;
157 }
158
159
160 static void
virNVMeDeviceListDispose(void * obj)161 virNVMeDeviceListDispose(void *obj)
162 {
163 virNVMeDeviceList *list = obj;
164 size_t i;
165
166 for (i = 0; i < list->count; i++)
167 virNVMeDeviceFree(list->devs[i]);
168
169 g_free(list->devs);
170 }
171
172
173 size_t
virNVMeDeviceListCount(const virNVMeDeviceList * list)174 virNVMeDeviceListCount(const virNVMeDeviceList *list)
175 {
176 return list->count;
177 }
178
179
180 int
virNVMeDeviceListAdd(virNVMeDeviceList * list,const virNVMeDevice * dev)181 virNVMeDeviceListAdd(virNVMeDeviceList *list,
182 const virNVMeDevice *dev)
183 {
184 virNVMeDevice *tmp;
185
186 if ((tmp = virNVMeDeviceListLookup(list, dev))) {
187 g_autofree char *addrStr = virPCIDeviceAddressAsString(&tmp->address);
188 virReportError(VIR_ERR_INTERNAL_ERROR,
189 _("NVMe device %s namespace %u is already on the list"),
190 NULLSTR(addrStr), tmp->namespace);
191 return -1;
192 }
193
194 if (!(tmp = virNVMeDeviceCopy(dev)))
195 return -1;
196
197 VIR_APPEND_ELEMENT(list->devs, list->count, tmp);
198
199 return 0;
200 }
201
202
203 int
virNVMeDeviceListDel(virNVMeDeviceList * list,const virNVMeDevice * dev)204 virNVMeDeviceListDel(virNVMeDeviceList *list,
205 const virNVMeDevice *dev)
206 {
207 ssize_t idx;
208 virNVMeDevice *tmp = NULL;
209
210 if ((idx = virNVMeDeviceListLookupIndex(list, dev)) < 0) {
211 g_autofree char *addrStr = virPCIDeviceAddressAsString(&dev->address);
212 virReportError(VIR_ERR_INTERNAL_ERROR,
213 _("NVMe device %s namespace %u not found"),
214 NULLSTR(addrStr), dev->namespace);
215 return -1;
216 }
217
218 tmp = list->devs[idx];
219 VIR_DELETE_ELEMENT(list->devs, idx, list->count);
220 virNVMeDeviceFree(tmp);
221 return 0;
222 }
223
224
225 virNVMeDevice *
virNVMeDeviceListGet(virNVMeDeviceList * list,size_t i)226 virNVMeDeviceListGet(virNVMeDeviceList *list,
227 size_t i)
228 {
229 return i < list->count ? list->devs[i] : NULL;
230 }
231
232
233 virNVMeDevice *
virNVMeDeviceListLookup(virNVMeDeviceList * list,const virNVMeDevice * dev)234 virNVMeDeviceListLookup(virNVMeDeviceList *list,
235 const virNVMeDevice *dev)
236 {
237 ssize_t idx;
238
239 if ((idx = virNVMeDeviceListLookupIndex(list, dev)) < 0)
240 return NULL;
241
242 return list->devs[idx];
243 }
244
245
246 ssize_t
virNVMeDeviceListLookupIndex(virNVMeDeviceList * list,const virNVMeDevice * dev)247 virNVMeDeviceListLookupIndex(virNVMeDeviceList *list,
248 const virNVMeDevice *dev)
249 {
250 size_t i;
251
252 if (!list)
253 return -1;
254
255 for (i = 0; i < list->count; i++) {
256 virNVMeDevice *other = list->devs[i];
257
258 if (virPCIDeviceAddressEqual(&dev->address, &other->address) &&
259 dev->namespace == other->namespace)
260 return i;
261 }
262
263 return -1;
264 }
265
266
267 static virNVMeDevice *
virNVMeDeviceListLookupByPCIAddress(virNVMeDeviceList * list,const virPCIDeviceAddress * address)268 virNVMeDeviceListLookupByPCIAddress(virNVMeDeviceList *list,
269 const virPCIDeviceAddress *address)
270 {
271 size_t i;
272
273 if (!list)
274 return NULL;
275
276 for (i = 0; i < list->count; i++) {
277 virNVMeDevice *other = list->devs[i];
278
279 if (virPCIDeviceAddressEqual(address, &other->address))
280 return other;
281 }
282
283 return NULL;
284 }
285
286
287 static virPCIDevice *
virNVMeDeviceCreatePCIDevice(const virNVMeDevice * nvme)288 virNVMeDeviceCreatePCIDevice(const virNVMeDevice *nvme)
289 {
290 g_autoptr(virPCIDevice) pci = NULL;
291
292 if (!(pci = virPCIDeviceNew(&nvme->address)))
293 return NULL;
294
295 /* NVMe devices must be bound to vfio */
296 virPCIDeviceSetStubDriver(pci, VIR_PCI_STUB_DRIVER_VFIO);
297 virPCIDeviceSetManaged(pci, nvme->managed);
298
299 return g_steal_pointer(&pci);
300 }
301
302
303 /**
304 * virNVMeDeviceListCreateDetachList:
305 * @activeList: list of active NVMe devices
306 * @toDetachList: list of NVMe devices to detach from the host
307 *
308 * This function creates a list of PCI devices which can then be
309 * reused by PCI device detach functions (e.g.
310 * virHostdevPreparePCIDevicesImpl()) as each PCI device from the
311 * returned list is initialized properly for detach.
312 *
313 * Basically, this just blindly collects unique PCI addresses
314 * from @toDetachList that don't appear on @activeList.
315 *
316 * Returns: a list on success,
317 * NULL otherwise.
318 */
319 virPCIDeviceList *
virNVMeDeviceListCreateDetachList(virNVMeDeviceList * activeList,virNVMeDeviceList * toDetachList)320 virNVMeDeviceListCreateDetachList(virNVMeDeviceList *activeList,
321 virNVMeDeviceList *toDetachList)
322 {
323 g_autoptr(virPCIDeviceList) pciDevices = NULL;
324 size_t i;
325
326 if (!(pciDevices = virPCIDeviceListNew()))
327 return NULL;
328
329 for (i = 0; i < toDetachList->count; i++) {
330 const virNVMeDevice *d = toDetachList->devs[i];
331 g_autoptr(virPCIDevice) pci = NULL;
332
333 /* If there is a NVMe device with the same PCI address on
334 * the activeList, the device is already detached. */
335 if (virNVMeDeviceListLookupByPCIAddress(activeList, &d->address))
336 continue;
337
338 /* It may happen that we want to detach two namespaces
339 * from the same NVMe device. This will be represented as
340 * two different instances of virNVMeDevice, but
341 * obviously we want to put the PCI device on the detach
342 * list only once. */
343 if (virPCIDeviceListFindByIDs(pciDevices,
344 d->address.domain,
345 d->address.bus,
346 d->address.slot,
347 d->address.function))
348 continue;
349
350 if (!(pci = virNVMeDeviceCreatePCIDevice(d)))
351 return NULL;
352
353 if (virPCIDeviceListAdd(pciDevices, pci) < 0)
354 return NULL;
355
356 /* avoid freeing the device */
357 pci = NULL;
358 }
359
360 return g_steal_pointer(&pciDevices);
361 }
362
363
364 /**
365 * virNVMeDeviceListCreateReAttachList:
366 * @activeList: list of active NVMe devices
367 * @toReAttachList: list of devices to reattach to the host
368 *
369 * This is a counterpart to virNVMeDeviceListCreateDetachList.
370 *
371 * This function creates a list of PCI devices which can then be
372 * reused by PCI device reattach functions (e.g.
373 * virHostdevReAttachPCIDevicesImpl()) as each PCI device from
374 * the returned list is initialized properly for reattach.
375 *
376 * Basically, this just collects unique PCI addresses
377 * of devices that appear on @toReAttachList and are used
378 * exactly once (i.e. no other namespaces are used from the same
379 * NVMe device). For that purpose, this function needs to know
380 * list of active NVMe devices (@activeList).
381 *
382 * Returns: a list on success,
383 * NULL otherwise.
384 */
385 virPCIDeviceList *
virNVMeDeviceListCreateReAttachList(virNVMeDeviceList * activeList,virNVMeDeviceList * toReAttachList)386 virNVMeDeviceListCreateReAttachList(virNVMeDeviceList *activeList,
387 virNVMeDeviceList *toReAttachList)
388 {
389 g_autoptr(virPCIDeviceList) pciDevices = NULL;
390 size_t i;
391
392 if (!(pciDevices = virPCIDeviceListNew()))
393 return NULL;
394
395 for (i = 0; i < toReAttachList->count; i++) {
396 const virNVMeDevice *d = toReAttachList->devs[i];
397 g_autoptr(virPCIDevice) pci = NULL;
398 size_t nused = 0;
399 size_t j;
400
401 /* Check if there is any other NVMe device with the same PCI address as
402 * @d. To simplify this, let's just count how many NVMe devices with
403 * the same PCI address there are on the @activeList. */
404 for (j = 0; j < activeList->count; j++) {
405 virNVMeDevice *other = activeList->devs[j];
406
407 if (!virPCIDeviceAddressEqual(&d->address, &other->address))
408 continue;
409
410 nused++;
411 }
412
413 /* Now, the following cases can happen:
414 * nused > 1 -> there are other NVMe device active, do NOT detach it
415 * nused == 1 -> we've found only @d on the @activeList, detach it
416 * nused == 0 -> huh, wait, what? @d is NOT on the @active list, how can
417 * we reattach it?
418 */
419
420 if (nused == 0) {
421 /* Shouldn't happen (TM) */
422 g_autofree char *addrStr = virPCIDeviceAddressAsString(&d->address);
423 virReportError(VIR_ERR_INTERNAL_ERROR,
424 _("NVMe device %s namespace %u not found"),
425 NULLSTR(addrStr), d->namespace);
426 return NULL;
427 } else if (nused > 1) {
428 /* NVMe device is still in use */
429 continue;
430 }
431
432 /* nused == 1 -> detach the device */
433 if (!(pci = virNVMeDeviceCreatePCIDevice(d)))
434 return NULL;
435
436 if (virPCIDeviceListAdd(pciDevices, pci) < 0)
437 return NULL;
438
439 /* avoid freeing the device */
440 pci = NULL;
441 }
442
443 return g_steal_pointer(&pciDevices);
444 }
445