From 9691440ecbc7d9383a1410f1067a4f9221f2de2c Mon Sep 17 00:00:00 2001 From: Jonathon Jongsma Date: Thu, 18 Jun 2020 16:05:59 -0500 Subject: [PATCH] nodedev: add mdev support to virNodeDeviceCreateXML() With recent additions to the node device xml schema, an xml schema can now describe a mdev device sufficiently for libvirt to create and start the device using the mdevctl utility. Note that some of the the configuration for a mediated device must be passed to mdevctl as a JSON-formatted file. In order to avoid creating and cleaning up temporary files, the JSON is instead fed to stdin and we pass the filename /dev/stdin to mdevctl. While this may not be portable, neither are mediated devices, so I don't believe it should cause any problems. Signed-off-by: Jonathon Jongsma Reviewed-by: Erik Skultety Reviewed-by: Michal Privoznik --- libvirt.spec.in | 2 + m4/virt-external-programs.m4 | 3 + src/conf/virnodedeviceobj.c | 34 +++++ src/conf/virnodedeviceobj.h | 3 + src/libvirt_private.syms | 1 + src/node_device/node_device_driver.c | 197 +++++++++++++++++++++++++++ src/node_device/node_device_driver.h | 5 + 7 files changed, 245 insertions(+) diff --git a/libvirt.spec.in b/libvirt.spec.in index 450c97b46d..cd7c33ab1a 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -522,6 +522,8 @@ Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-libs = %{version}-%{release} # needed for device enumeration Requires: systemd >= 185 +# For managing persistent mediated devices +Requires: mdevctl %description daemon-driver-nodedev The nodedev driver plugin for the libvirtd daemon, providing diff --git a/m4/virt-external-programs.m4 b/m4/virt-external-programs.m4 index 9046e3bf07..bd3cb1f757 100644 --- a/m4/virt-external-programs.m4 +++ b/m4/virt-external-programs.m4 @@ -65,6 +65,7 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ AC_PATH_PROG([OVSVSCTL], [ovs-vsctl], [ovs-vsctl], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([SCRUB], [scrub], [scrub], [$LIBVIRT_SBIN_PATH]) AC_PATH_PROG([ADDR2LINE], [addr2line], [addr2line], [$LIBVIRT_SBIN_PATH]) + AC_PATH_PROG([MDEVCTL], [mdevctl], [mdevctl], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([DMIDECODE], ["$DMIDECODE"], [Location or name of the dmidecode program]) @@ -88,6 +89,8 @@ AC_DEFUN([LIBVIRT_CHECK_EXTERNAL_PROGRAMS], [ [Location or name of the scrub program (for wiping algorithms)]) AC_DEFINE_UNQUOTED([ADDR2LINE], ["$ADDR2LINE"], [Location of addr2line program]) + AC_DEFINE_UNQUOTED([MDEVCTL], ["$MDEVCTL"], + [Location or name of the mdevctl program]) AC_PATH_PROG([IP_PATH], [ip], [/sbin/ip], [$LIBVIRT_SBIN_PATH]) AC_DEFINE_UNQUOTED([IP_PATH], ["$IP_PATH"], [path to ip binary]) diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 3a34a324ca..bfd524121c 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -399,6 +399,40 @@ virNodeDeviceObjListFindSCSIHostByWWNs(virNodeDeviceObjListPtr devs, &data); } +static int +virNodeDeviceObjListFindMediatedDeviceByUUIDCallback(const void *payload, + const void *name G_GNUC_UNUSED, + const void *opaque) +{ + virNodeDeviceObjPtr obj = (virNodeDeviceObjPtr) payload; + const char *uuid = (const char *) opaque; + virNodeDevCapsDefPtr cap; + int want = 0; + + virObjectLock(obj); + + for (cap = obj->def->caps; cap != NULL; cap = cap->next) { + if (cap->data.type == VIR_NODE_DEV_CAP_MDEV) { + if (STREQ(cap->data.mdev.uuid, uuid)) { + want = 1; + break; + } + } + } + + virObjectUnlock(obj); + return want; +} + + +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid) +{ + return virNodeDeviceObjListSearch(devs, + virNodeDeviceObjListFindMediatedDeviceByUUIDCallback, + uuid); +} static void virNodeDeviceObjListDispose(void *obj) diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h index c9df8dedab..6efdb23d36 100644 --- a/src/conf/virnodedeviceobj.h +++ b/src/conf/virnodedeviceobj.h @@ -118,3 +118,6 @@ virNodeDeviceObjListExport(virConnectPtr conn, void virNodeDeviceObjSetSkipUpdateCaps(virNodeDeviceObjPtr obj, bool skipUpdateCaps); +virNodeDeviceObjPtr +virNodeDeviceObjListFindMediatedDeviceByUUID(virNodeDeviceObjListPtr devs, + const char *uuid); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 22bd8b9c17..42f8d7c222 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1179,6 +1179,7 @@ virNodeDeviceObjListAssignDef; virNodeDeviceObjListExport; virNodeDeviceObjListFindByName; virNodeDeviceObjListFindBySysfsPath; +virNodeDeviceObjListFindMediatedDeviceByUUID; virNodeDeviceObjListFindSCSIHostByWWNs; virNodeDeviceObjListFree; virNodeDeviceObjListGetNames; diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c index d6255a43c8..35016782d2 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -30,6 +30,7 @@ #include "datatypes.h" #include "viralloc.h" #include "virfile.h" +#include "virjson.h" #include "virstring.h" #include "node_device_conf.h" #include "node_device_event.h" @@ -40,6 +41,7 @@ #include "viraccessapicheck.h" #include "virnetdev.h" #include "virutil.h" +#include "vircommand.h" #define VIR_FROM_THIS VIR_FROM_NODEDEV @@ -304,6 +306,30 @@ nodeDeviceLookupSCSIHostByWWN(virConnectPtr conn, return device; } +static virNodeDevicePtr +nodeDeviceLookupMediatedDeviceByUUID(virConnectPtr conn, + const char *uuid, + unsigned int flags) +{ + virNodeDeviceObjPtr obj = NULL; + virNodeDeviceDefPtr def; + virNodeDevicePtr device = NULL; + + virCheckFlags(0, NULL); + + if (!(obj = virNodeDeviceObjListFindMediatedDeviceByUUID(driver->devs, + uuid))) + return NULL; + + def = virNodeDeviceObjGetDef(obj); + + if ((device = virGetNodeDevice(conn, def->name))) + device->parentName = g_strdup(def->parent); + + virNodeDeviceObjEndAPI(&obj); + return device; +} + char * nodeDeviceGetXMLDesc(virNodeDevicePtr device, @@ -492,6 +518,26 @@ nodeDeviceFindNewDevice(virConnectPtr conn, } +static virNodeDevicePtr +nodeDeviceFindNewMediatedDeviceFunc(virConnectPtr conn, + const void *opaque) +{ + const char *uuid = opaque; + + return nodeDeviceLookupMediatedDeviceByUUID(conn, uuid, 0); +} + + +static virNodeDevicePtr +nodeDeviceFindNewMediatedDevice(virConnectPtr conn, + const char *mdev_uuid) +{ + return nodeDeviceFindNewDevice(conn, + nodeDeviceFindNewMediatedDeviceFunc, + mdev_uuid); +} + + typedef struct _NewSCSIHostFuncData NewSCSIHostFuncData; struct _NewSCSIHostFuncData { @@ -536,6 +582,155 @@ nodeDeviceHasCapability(virNodeDeviceDefPtr def, virNodeDevCapType type) } +/* format a json string that provides configuration information about this mdev + * to the mdevctl utility */ +static int +nodeDeviceDefToMdevctlConfig(virNodeDeviceDefPtr def, char **buf) +{ + size_t i; + virNodeDevCapMdevPtr mdev = &def->caps->data.mdev; + g_autoptr(virJSONValue) json = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(json, "mdev_type", mdev->type) < 0) + return -1; + + if (virJSONValueObjectAppendString(json, "start", "manual") < 0) + return -1; + + if (mdev->attributes) { + g_autoptr(virJSONValue) attributes = virJSONValueNewArray(); + + for (i = 0; i < mdev->nattributes; i++) { + virMediatedDeviceAttrPtr attr = mdev->attributes[i]; + g_autoptr(virJSONValue) jsonattr = virJSONValueNewObject(); + + if (virJSONValueObjectAppendString(jsonattr, attr->name, attr->value) < 0) + return -1; + + if (virJSONValueArrayAppend(attributes, g_steal_pointer(&jsonattr)) < 0) + return -1; + } + + if (virJSONValueObjectAppend(json, "attrs", g_steal_pointer(&attributes)) < 0) + return -1; + } + + *buf = virJSONValueToString(json, false); + if (!*buf) + return -1; + + return 0; +} + + +static char * +nodeDeviceFindAddressByName(const char *name) +{ + virNodeDeviceDefPtr def = NULL; + virNodeDevCapsDefPtr caps = NULL; + char *pci_addr = NULL; + virNodeDeviceObjPtr dev = virNodeDeviceObjListFindByName(driver->devs, name); + + if (!dev) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("could not find device '%s'"), name); + return NULL; + } + + def = virNodeDeviceObjGetDef(dev); + for (caps = def->caps; caps != NULL; caps = caps->next) { + if (caps->data.type == VIR_NODE_DEV_CAP_PCI_DEV) { + virPCIDeviceAddress addr = { + .domain = caps->data.pci_dev.domain, + .bus = caps->data.pci_dev.bus, + .slot = caps->data.pci_dev.slot, + .function = caps->data.pci_dev.function + }; + + pci_addr = virPCIDeviceAddressAsString(&addr); + break; + } + } + + virNodeDeviceObjEndAPI(&dev); + + return pci_addr; +} + + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + char **uuid_out) +{ + virCommandPtr cmd; + g_autofree char *json = NULL; + g_autofree char *parent_pci = nodeDeviceFindAddressByName(def->parent); + + if (!parent_pci) { + virReportError(VIR_ERR_NO_NODE_DEVICE, + _("unable to find PCI address for parent device '%s'"), def->parent); + return NULL; + } + + if (nodeDeviceDefToMdevctlConfig(def, &json) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("couldn't convert node device def to mdevctl JSON")); + return NULL; + } + + cmd = virCommandNewArgList(MDEVCTL, "start", + "-p", parent_pci, + "--jsonfile", "/dev/stdin", + NULL); + + virCommandSetInputBuffer(cmd, json); + virCommandSetOutputBuffer(cmd, uuid_out); + + return cmd; +} + +static int +virMdevctlStart(virNodeDeviceDefPtr def, char **uuid) +{ + int status; + g_autoptr(virCommand) cmd = nodeDeviceGetMdevctlStartCommand(def, uuid); + if (!cmd) + return -1; + + /* an auto-generated uuid is returned via stdout if no uuid is specified in + * the mdevctl args */ + if (virCommandRun(cmd, &status) < 0 || status != 0) + return -1; + + /* remove newline */ + *uuid = g_strstrip(*uuid); + + return 0; +} + + +static virNodeDevicePtr +nodeDeviceCreateXMLMdev(virConnectPtr conn, + virNodeDeviceDefPtr def) +{ + g_autofree char *uuid = NULL; + + if (!def->parent) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cannot create a mediated device without a parent")); + return NULL; + } + + if (virMdevctlStart(def, &uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to start mediated device")); + return NULL; + } + + return nodeDeviceFindNewMediatedDevice(conn, uuid); +} + + virNodeDevicePtr nodeDeviceCreateXML(virConnectPtr conn, const char *xmlDesc, @@ -580,6 +775,8 @@ nodeDeviceCreateXML(virConnectPtr conn, _("no node device for '%s' with matching " "wwnn '%s' and wwpn '%s'"), def->name, wwnn, wwpn); + } else if (nodeDeviceHasCapability(def, VIR_NODE_DEV_CAP_MDEV)) { + device = nodeDeviceCreateXMLMdev(conn, def); } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Unsupported device type")); diff --git a/src/node_device/node_device_driver.h b/src/node_device/node_device_driver.h index eae5e2cb17..e42c14f6c7 100644 --- a/src/node_device/node_device_driver.h +++ b/src/node_device/node_device_driver.h @@ -24,6 +24,7 @@ #include "internal.h" #include "driver.h" #include "virnodedeviceobj.h" +#include "vircommand.h" #define LINUX_NEW_DEVICE_WAIT_TIME 60 @@ -116,3 +117,7 @@ nodeConnectNodeDeviceEventRegisterAny(virConnectPtr conn, int nodeConnectNodeDeviceEventDeregisterAny(virConnectPtr conn, int callbackID); + +virCommandPtr +nodeDeviceGetMdevctlStartCommand(virNodeDeviceDefPtr def, + char **uuid_out);