/* * virnvme.c: helper APIs for managing NVMe devices * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "virnvme.h" #include "virobject.h" #include "virpci.h" #include "viralloc.h" #include "virlog.h" #include "virstring.h" VIR_LOG_INIT("util.nvme"); #define VIR_FROM_THIS VIR_FROM_NONE struct _virNVMeDevice { virPCIDeviceAddress address; /* PCI address of controller */ unsigned int namespace; /* Namespace ID */ bool managed; char *drvname; char *domname; }; struct _virNVMeDeviceList { virObjectLockable parent; size_t count; virNVMeDevice **devs; }; static virClass *virNVMeDeviceListClass; static void virNVMeDeviceListDispose(void *obj); static int virNVMeOnceInit(void) { if (!VIR_CLASS_NEW(virNVMeDeviceList, virClassForObjectLockable())) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virNVMe); virNVMeDevice * virNVMeDeviceNew(const virPCIDeviceAddress *address, unsigned long namespace, bool managed) { virNVMeDevice *dev = NULL; dev = g_new0(virNVMeDevice, 1); virPCIDeviceAddressCopy(&dev->address, address); dev->namespace = namespace; dev->managed = managed; return dev; } void virNVMeDeviceFree(virNVMeDevice *dev) { if (!dev) return; virNVMeDeviceUsedByClear(dev); g_free(dev); } virNVMeDevice * virNVMeDeviceCopy(const virNVMeDevice *dev) { virNVMeDevice *copy = NULL; copy = g_new0(virNVMeDevice, 1); copy->drvname = g_strdup(dev->drvname); copy->domname = g_strdup(dev->domname); virPCIDeviceAddressCopy(©->address, &dev->address); copy->namespace = dev->namespace; copy->managed = dev->managed; return copy; } const virPCIDeviceAddress * virNVMeDeviceAddressGet(const virNVMeDevice *dev) { return &dev->address; } void virNVMeDeviceUsedByClear(virNVMeDevice *dev) { VIR_FREE(dev->drvname); VIR_FREE(dev->domname); } void virNVMeDeviceUsedByGet(const virNVMeDevice *dev, const char **drv, const char **dom) { *drv = dev->drvname; *dom = dev->domname; } void virNVMeDeviceUsedBySet(virNVMeDevice *dev, const char *drv, const char *dom) { dev->drvname = g_strdup(drv); dev->domname = g_strdup(dom); } virNVMeDeviceList * virNVMeDeviceListNew(void) { virNVMeDeviceList *list; if (virNVMeInitialize() < 0) return NULL; if (!(list = virObjectLockableNew(virNVMeDeviceListClass))) return NULL; return list; } static void virNVMeDeviceListDispose(void *obj) { virNVMeDeviceList *list = obj; size_t i; for (i = 0; i < list->count; i++) virNVMeDeviceFree(list->devs[i]); g_free(list->devs); } size_t virNVMeDeviceListCount(const virNVMeDeviceList *list) { return list->count; } int virNVMeDeviceListAdd(virNVMeDeviceList *list, const virNVMeDevice *dev) { virNVMeDevice *tmp; if ((tmp = virNVMeDeviceListLookup(list, dev))) { g_autofree char *addrStr = virPCIDeviceAddressAsString(&tmp->address); virReportError(VIR_ERR_INTERNAL_ERROR, _("NVMe device %s namespace %u is already on the list"), NULLSTR(addrStr), tmp->namespace); return -1; } if (!(tmp = virNVMeDeviceCopy(dev)) || VIR_APPEND_ELEMENT(list->devs, list->count, tmp) < 0) { virNVMeDeviceFree(tmp); return -1; } return 0; } int virNVMeDeviceListDel(virNVMeDeviceList *list, const virNVMeDevice *dev) { ssize_t idx; virNVMeDevice *tmp = NULL; if ((idx = virNVMeDeviceListLookupIndex(list, dev)) < 0) { g_autofree char *addrStr = virPCIDeviceAddressAsString(&dev->address); virReportError(VIR_ERR_INTERNAL_ERROR, _("NVMe device %s namespace %u not found"), NULLSTR(addrStr), dev->namespace); return -1; } tmp = list->devs[idx]; VIR_DELETE_ELEMENT(list->devs, idx, list->count); virNVMeDeviceFree(tmp); return 0; } virNVMeDevice * virNVMeDeviceListGet(virNVMeDeviceList *list, size_t i) { return i < list->count ? list->devs[i] : NULL; } virNVMeDevice * virNVMeDeviceListLookup(virNVMeDeviceList *list, const virNVMeDevice *dev) { ssize_t idx; if ((idx = virNVMeDeviceListLookupIndex(list, dev)) < 0) return NULL; return list->devs[idx]; } ssize_t virNVMeDeviceListLookupIndex(virNVMeDeviceList *list, const virNVMeDevice *dev) { size_t i; if (!list) return -1; for (i = 0; i < list->count; i++) { virNVMeDevice *other = list->devs[i]; if (virPCIDeviceAddressEqual(&dev->address, &other->address) && dev->namespace == other->namespace) return i; } return -1; } static virNVMeDevice * virNVMeDeviceListLookupByPCIAddress(virNVMeDeviceList *list, const virPCIDeviceAddress *address) { size_t i; if (!list) return NULL; for (i = 0; i < list->count; i++) { virNVMeDevice *other = list->devs[i]; if (virPCIDeviceAddressEqual(address, &other->address)) return other; } return NULL; } static virPCIDevice * virNVMeDeviceCreatePCIDevice(const virNVMeDevice *nvme) { g_autoptr(virPCIDevice) pci = NULL; if (!(pci = virPCIDeviceNew(&nvme->address))) return NULL; /* NVMe devices must be bound to vfio */ virPCIDeviceSetStubDriver(pci, VIR_PCI_STUB_DRIVER_VFIO); virPCIDeviceSetManaged(pci, nvme->managed); return g_steal_pointer(&pci); } /** * virNVMeDeviceListCreateDetachList: * @activeList: list of active NVMe devices * @toDetachList: list of NVMe devices to detach from the host * * This function creates a list of PCI devices which can then be * reused by PCI device detach functions (e.g. * virHostdevPreparePCIDevicesImpl()) as each PCI device from the * returned list is initialized properly for detach. * * Basically, this just blindly collects unique PCI addresses * from @toDetachList that don't appear on @activeList. * * Returns: a list on success, * NULL otherwise. */ virPCIDeviceList * virNVMeDeviceListCreateDetachList(virNVMeDeviceList *activeList, virNVMeDeviceList *toDetachList) { g_autoptr(virPCIDeviceList) pciDevices = NULL; size_t i; if (!(pciDevices = virPCIDeviceListNew())) return NULL; for (i = 0; i < toDetachList->count; i++) { const virNVMeDevice *d = toDetachList->devs[i]; g_autoptr(virPCIDevice) pci = NULL; /* If there is a NVMe device with the same PCI address on * the activeList, the device is already detached. */ if (virNVMeDeviceListLookupByPCIAddress(activeList, &d->address)) continue; /* It may happen that we want to detach two namespaces * from the same NVMe device. This will be represented as * two different instances of virNVMeDevice, but * obviously we want to put the PCI device on the detach * list only once. */ if (virPCIDeviceListFindByIDs(pciDevices, d->address.domain, d->address.bus, d->address.slot, d->address.function)) continue; if (!(pci = virNVMeDeviceCreatePCIDevice(d))) return NULL; if (virPCIDeviceListAdd(pciDevices, pci) < 0) return NULL; /* avoid freeing the device */ pci = NULL; } return g_steal_pointer(&pciDevices); } /** * virNVMeDeviceListCreateReAttachList: * @activeList: list of active NVMe devices * @toReAttachList: list of devices to reattach to the host * * This is a counterpart to virNVMeDeviceListCreateDetachList. * * This function creates a list of PCI devices which can then be * reused by PCI device reattach functions (e.g. * virHostdevReAttachPCIDevicesImpl()) as each PCI device from * the returned list is initialized properly for reattach. * * Basically, this just collects unique PCI addresses * of devices that appear on @toReAttachList and are used * exactly once (i.e. no other namespaces are used from the same * NVMe device). For that purpose, this function needs to know * list of active NVMe devices (@activeList). * * Returns: a list on success, * NULL otherwise. */ virPCIDeviceList * virNVMeDeviceListCreateReAttachList(virNVMeDeviceList *activeList, virNVMeDeviceList *toReAttachList) { g_autoptr(virPCIDeviceList) pciDevices = NULL; size_t i; if (!(pciDevices = virPCIDeviceListNew())) return NULL; for (i = 0; i < toReAttachList->count; i++) { const virNVMeDevice *d = toReAttachList->devs[i]; g_autoptr(virPCIDevice) pci = NULL; size_t nused = 0; size_t j; /* Check if there is any other NVMe device with the same PCI address as * @d. To simplify this, let's just count how many NVMe devices with * the same PCI address there are on the @activeList. */ for (j = 0; j < activeList->count; j++) { virNVMeDevice *other = activeList->devs[j]; if (!virPCIDeviceAddressEqual(&d->address, &other->address)) continue; nused++; } /* Now, the following cases can happen: * nused > 1 -> there are other NVMe device active, do NOT detach it * nused == 1 -> we've found only @d on the @activeList, detach it * nused == 0 -> huh, wait, what? @d is NOT on the @active list, how can * we reattach it? */ if (nused == 0) { /* Shouldn't happen (TM) */ g_autofree char *addrStr = virPCIDeviceAddressAsString(&d->address); virReportError(VIR_ERR_INTERNAL_ERROR, _("NVMe device %s namespace %u not found"), NULLSTR(addrStr), d->namespace); return NULL; } else if (nused > 1) { /* NVMe device is still in use */ continue; } /* nused == 1 -> detach the device */ if (!(pci = virNVMeDeviceCreatePCIDevice(d))) return NULL; if (virPCIDeviceListAdd(pciDevices, pci) < 0) return NULL; /* avoid freeing the device */ pci = NULL; } return g_steal_pointer(&pciDevices); }