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(&copy->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