[libvirt] [PATCH 0/2] Support network stats for hostdev(SR-IOV) in Switchdev mode

With availability of switchdev model in linux, it is possible to capture stats for hostdev SR-IOV VFs using its VF representor interface name on host for nics supporting switchdev model. These stats are supported by adding helper APIs for getting VF Representor name based on BDF info in 'hostdev' and querying required net sysfs entries on host. These helper APIs are then used in qemu_driver to get the hostdev interface stats for pci SR-IOV device. [1] https://www.kernel.org/doc/Documentation/networking/switchdev.txt Jai Singh Rana (2): util: Add helper APIs to get/verify VF Representor name qemu: conf: Network stats support for hostdev VF Representor po/POTFILES.in | 1 + src/Makefile.am | 1 + src/conf/domain_conf.c | 7 ++ src/libvirt_private.syms | 5 + src/qemu/qemu_driver.c | 34 +++++- src/util/virhostdev.c | 10 ++ src/util/virhostdev.h | 6 + src/util/virnetdevhostdev.c | 284 ++++++++++++++++++++++++++++++++++++++++++++ src/util/virnetdevhostdev.h | 33 +++++ 9 files changed, 377 insertions(+), 4 deletions(-) create mode 100644 src/util/virnetdevhostdev.c create mode 100644 src/util/virnetdevhostdev.h -- 2.13.6

Switchdev VF Representor interface name on host is derived based on BDF of pci SR-IOV device in 'hostdev' and querying required net sysfs entries on host. --- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 5 + src/util/virhostdev.c | 10 ++ src/util/virhostdev.h | 6 + src/util/virnetdevhostdev.c | 284 ++++++++++++++++++++++++++++++++++++++++++++ src/util/virnetdevhostdev.h | 33 +++++ 7 files changed, 340 insertions(+) create mode 100644 src/util/virnetdevhostdev.c create mode 100644 src/util/virnetdevhostdev.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 285955469..73ce73397 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -237,6 +237,7 @@ src/util/virnetdevmacvlan.c src/util/virnetdevmidonet.c src/util/virnetdevopenvswitch.c src/util/virnetdevtap.c +src/util/virnetdevhostdev.c src/util/virnetdevveth.c src/util/virnetdevvportprofile.c src/util/virnetlink.c diff --git a/src/Makefile.am b/src/Makefile.am index db68e01db..0f3c3f1bc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -148,6 +148,7 @@ UTIL_SOURCES = \ util/virnetdev.h util/virnetdev.c \ util/virnetdevbandwidth.h util/virnetdevbandwidth.c \ util/virnetdevbridge.h util/virnetdevbridge.c \ + util/virnetdevhostdev.h util/virnetdevhostdev.c \ util/virnetdevip.h util/virnetdevip.c \ util/virnetdevmacvlan.c util/virnetdevmacvlan.h \ util/virnetdevmidonet.h util/virnetdevmidonet.c \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 3b14d7d15..d9bc8ad72 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2288,6 +2288,11 @@ virNetDevBridgeSetSTPDelay; virNetDevBridgeSetVlanFiltering; +# util/virnetdevhostdev.h +virNetdevHostdevCheckVFRepIFName; +virNetdevHostdevGetVFRepIFName; + + # util/virnetdevip.h virNetDevIPAddrAdd; virNetDevIPAddrDel; diff --git a/src/util/virhostdev.c b/src/util/virhostdev.c index a12224c58..7a90779f0 100644 --- a/src/util/virhostdev.c +++ b/src/util/virhostdev.c @@ -350,6 +350,16 @@ virHostdevNetDevice(virDomainHostdevDefPtr hostdev, return ret; } +/* Non static wrapper for virHostdevNetDevice to use outside filescope */ +int +virHostdevNetDeviceWrapper(virDomainHostdevDefPtr hostdev, + int pfNetDevIdx, + char **linkdev, + int *vf) +{ + return virHostdevNetDevice(hostdev, pfNetDevIdx, linkdev, vf); +} + static bool virHostdevIsPCINetDevice(virDomainHostdevDefPtr hostdev) diff --git a/src/util/virhostdev.h b/src/util/virhostdev.h index 54e1c66be..7dc860a85 100644 --- a/src/util/virhostdev.h +++ b/src/util/virhostdev.h @@ -202,5 +202,11 @@ int virHostdevPCINodeDeviceReAttach(virHostdevManagerPtr mgr, int virHostdevPCINodeDeviceReset(virHostdevManagerPtr mgr, virPCIDevicePtr pci) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int +virHostdevNetDeviceWrapper(virDomainHostdevDefPtr hostdev, + int pfNetDevIdx, + char **linkdev, + int *vf) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(4); #endif /* __VIR_HOSTDEV_H__ */ diff --git a/src/util/virnetdevhostdev.c b/src/util/virnetdevhostdev.c new file mode 100644 index 000000000..ecf4b7d8f --- /dev/null +++ b/src/util/virnetdevhostdev.c @@ -0,0 +1,284 @@ +/* + * virnetdevhostdev.c: utilities to get/verify Switchdev VF Representor + * + * 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 + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <dirent.h> + +#include "virhostdev.h" +#include "virnetdevhostdev.h" +#include "viralloc.h" +#include "virstring.h" +#include "virfile.h" +#include "virerror.h" +#include "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("util.netdevhostdev"); + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +#define IFSWITCHIDSIZ 20 + +#ifndef SYSFS_NET_DIR +#define SYSFS_NET_DIR "/sys/class/net/" +#endif + +#if 0 +static int +virNetdevHostdevPCISysfsPath(virDomainHostdevDefPtr hostdev, + char **sysfs_path) +{ + virPCIDeviceAddress config_address; + + config_address.domain = hostdev->source.subsys.u.pci.addr.domain; + config_address.bus = hostdev->source.subsys.u.pci.addr.bus; + config_address.slot = hostdev->source.subsys.u.pci.addr.slot; + config_address.function = hostdev->source.subsys.u.pci.addr.function; + + return virPCIDeviceAddressGetSysfsFile(&config_address, sysfs_path); +} + + +static int +virNetdevHostdevNetDevice(virDomainHostdevDefPtr hostdev, + int pfNetDevIdx, + char **linkdev, + int *vf) +{ + int ret = -1; + char *sysfs_path = NULL; + + if (virNetdevHostdevPCISysfsPath(hostdev, &sysfs_path) < 0) + return ret; + + if (virPCIIsVirtualFunction(sysfs_path) == 1) { + if (virPCIGetVirtualFunctionInfo(sysfs_path, pfNetDevIdx, + linkdev, vf) < 0) + goto cleanup; + } else { + /* In practice this should never happen, since we currently + * only support assigning SRIOV VFs via <interface + * type='hostdev'>, and it is only those devices that should + * end up calling this function. + */ + if (virPCIGetNetName(sysfs_path, 0, NULL, linkdev) < 0) + goto cleanup; + + if (!linkdev) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("The device at %s has no network device name"), + sysfs_path); + goto cleanup; + } + + *vf = -1; + } + + ret = 0; + + cleanup: + VIR_FREE(sysfs_path); + + return ret; +} +#endif + + +/** + * virNetdevHostdevNetSysfsPath + * + * @pf_name: netdev name of the physical function (PF) + * @vf: virtual function (VF) number for the device of interest + * @vf_representor: name of the VF representor interface + * + * Finds the VF representor name of VF# @vf of SRIOV PF @pfname, and + * puts it in @vf_representor. The caller must free @vf_representor + * when it's finished with it + * + * Returns 0 on success, -1 on failure + */ +static int +virNetdevHostdevNetSysfsPath(char *pf_name, int vf, char **vf_representor) +{ + char *pf_switch_id = NULL; + char *pf_switch_id_file = NULL; + char *pf_subsystem_device_file = NULL; + char *pf_subsystem_device_switch_id = NULL; + char *pf_subsystem_device_port_name_file = NULL; + char *pf_subsystem_dir = NULL; + char *vf_representor_name = NULL; + char *vf_num_str = NULL; + char *vf_suffix = NULL; + DIR *dirp = NULL; + struct dirent *dp; + int ret = -1; + + + if (virAsprintf(&pf_switch_id_file, SYSFS_NET_DIR "%s/phys_switch_id", + pf_name) < 0) + goto cleanup; + + if (virAsprintf(&pf_subsystem_dir, SYSFS_NET_DIR "%s/subsystem", + pf_name) < 0) + goto cleanup; + + if (virFileReadAllQuiet(pf_switch_id_file, IFSWITCHIDSIZ, + &pf_switch_id) <= 0) { + goto cleanup; + } + + if (virDirOpen(&dirp, pf_subsystem_dir) < 0) + goto cleanup; + + /* Iterate over the PFs subsystem devices to find entry with matching + * switch_id with that of PF. + */ + while (virDirRead(dirp, &dp, pf_subsystem_dir) > 0) { + if (STREQ(dp->d_name, pf_name)) + continue; + + if (virAsprintf(&pf_subsystem_device_file, "%s/%s/phys_switch_id", + pf_subsystem_dir, dp->d_name) < 0) + goto cleanup; + + if (virFileReadAllQuiet(pf_subsystem_device_file, IFSWITCHIDSIZ, + &pf_subsystem_device_switch_id) > 0) { + if (!STREQ(pf_switch_id, pf_subsystem_device_switch_id)) { + VIR_FREE(pf_subsystem_device_file); + VIR_FREE(pf_subsystem_device_switch_id); + continue; + } + } + + if (virAsprintf(&pf_subsystem_device_port_name_file, + "%s/%s/phys_port_name", pf_subsystem_dir, + dp->d_name) < 0) + goto cleanup; + + if (virFileReadAllQuiet + (pf_subsystem_device_port_name_file, IFNAMSIZ, + &vf_representor_name) <= 0) { + VIR_FREE(pf_subsystem_device_file); + VIR_FREE(pf_subsystem_device_switch_id); + VIR_FREE(pf_subsystem_device_port_name_file); + continue; + } + + if (virAsprintf(&vf_num_str, "%d", vf) < 0) + goto cleanup; + + /* phys_port_name may contain just VF number or string with + * 'vf' or 'VF' followed by VF number at the end. + */ + if (!(vf_suffix = strcasestr(vf_representor_name, "vf"))) + vf_suffix = vf_representor_name; + + if (strstr(vf_suffix, vf_num_str)) { + if (virAsprintf(vf_representor, "%s", dp->d_name) < 0) + goto cleanup; + + ret = 0; + break; + } + } + + cleanup: + VIR_DIR_CLOSE(dirp); + VIR_FREE(pf_switch_id); + VIR_FREE(pf_switch_id_file); + VIR_FREE(pf_subsystem_dir); + VIR_FREE(pf_subsystem_device_file); + VIR_FREE(pf_subsystem_device_switch_id); + VIR_FREE(pf_subsystem_device_port_name_file); + VIR_FREE(vf_num_str); + VIR_FREE(vf_representor_name); + return ret; +} + + +/** + * virNetdevHostdevGetVFRepIFName + * + * @hostdev: host device to check + * @ifname : Contains VF representor name upon successful return. + * + * Returns 0 on success, -1 on failure + */ +int +virNetdevHostdevGetVFRepIFName(virDomainHostdevDefPtr hostdev, + char **ifname) +{ + char *linkdev = NULL; + char *vf_representor = NULL; + int vf = -1; + int ret = -1; + + if (virHostdevNetDeviceWrapper(hostdev, -1, &linkdev, &vf) < 0) + goto cleanup; + + if (virNetdevHostdevNetSysfsPath(linkdev, vf, &vf_representor)) + goto cleanup; + + if (VIR_STRDUP(*ifname, vf_representor) > 0) + ret = 0; + + cleanup: + VIR_FREE(linkdev); + VIR_FREE(vf_representor); + return ret; +} + + +/** + * virNetdevHostdevCheckVFRepIFName + * + * @hostdev: host device to check + * @ifname : VF representor name to verify + * + * Returns 0 on success, -1 on failure + */ +int +virNetdevHostdevCheckVFRepIFName(virDomainHostdevDefPtr hostdev, + const char *ifname) +{ + char *linkdev = NULL; + char *vf_representor = NULL; + int vf = -1; + int ret = -1; + + if (virHostdevNetDeviceWrapper(hostdev, -1, &linkdev, &vf) < 0) + goto cleanup; + + if (virNetdevHostdevNetSysfsPath(linkdev, vf, &vf_representor)) + goto cleanup; + + if (STREQ(ifname, vf_representor)) + ret = 0; + + cleanup: + VIR_FREE(linkdev); + VIR_FREE(vf_representor); + return ret; +} diff --git a/src/util/virnetdevhostdev.h b/src/util/virnetdevhostdev.h new file mode 100644 index 000000000..9fb5c5069 --- /dev/null +++ b/src/util/virnetdevhostdev.h @@ -0,0 +1,33 @@ +/* + * virnetdevhostdev.h: utilities to get/verify Switchdev VF Representor + * + * + * 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 + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_NETDEV_HOSTDEV_H__ +#define __VIR_NETDEV_HOSTDEV_H__ +#include "virnetdevtap.h" + +int +virNetdevHostdevGetVFRepIFName(virDomainHostdevDefPtr hostdev, + char **ifname) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; +int +virNetdevHostdevCheckVFRepIFName(virDomainHostdevDefPtr hostdev, + const char *ifname) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +#define virNetdevHostdevVFRepInterfaceStats virNetDevTapInterfaceStats +#endif /* __VIR_NETDEV_HOSTDEV_H__ */ -- 2.13.6

In case of <interface type='hostdev'>, return stats if its a Switchdev VF Representor interface of pci SR-IOV device. --- src/conf/domain_conf.c | 7 +++++++ src/qemu/qemu_driver.c | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index fb732a0c2..649fc2eb8 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -58,6 +58,7 @@ #include "virnetdev.h" #include "virnetdevmacvlan.h" #include "virhostdev.h" +#include "virnetdevhostdev.h" #include "virmdev.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN @@ -28112,6 +28113,12 @@ virDomainNetFind(virDomainDefPtr def, const char *device) return net; } + /* Give a try to hostdev */ + for (i = 0; i < def->nnets; i++) { + if(!virNetdevHostdevCheckVFRepIFName(def->hostdevs[i], device)) + return def->nets[i]; + } + virReportError(VIR_ERR_INVALID_ARG, _("'%s' is not a known interface"), device); return NULL; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 978ecd4e0..3a3a9c986 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -67,6 +67,7 @@ #include "virhostcpu.h" #include "virhostmem.h" #include "virnetdevtap.h" +#include "virnetdevhostdev.h" #include "virnetdevopenvswitch.h" #include "capabilities.h" #include "viralloc.h" @@ -11153,6 +11154,11 @@ qemuDomainInterfaceStats(virDomainPtr dom, if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { if (virNetDevOpenvswitchInterfaceStats(net->ifname, stats) < 0) goto cleanup; + } else if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_HOSTDEV) { + if (virNetdevHostdevVFRepInterfaceStats(device, stats, + !virDomainNetTypeSharesHostView + (net)) < 0) + goto cleanup; } else { if (virNetDevTapInterfaceStats(net->ifname, stats, !virDomainNetTypeSharesHostView(net)) < 0) @@ -19794,6 +19800,7 @@ qemuDomainGetStatsInterface(virQEMUDriverPtr driver ATTRIBUTE_UNUSED, { size_t i; struct _virDomainInterfaceStats tmp; + char *vf_representor_ifname = NULL; int ret = -1; if (!virDomainObjIsActive(dom)) @@ -19806,21 +19813,40 @@ qemuDomainGetStatsInterface(virQEMUDriverPtr driver ATTRIBUTE_UNUSED, virDomainNetDefPtr net = dom->def->nets[i]; virDomainNetType actualType; - if (!net->ifname) + actualType = virDomainNetGetActualType(net); + + if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV) { + if (virNetdevHostdevGetVFRepIFName(dom->def->hostdevs[i], + &vf_representor_ifname)) + continue; + } + else if (!net->ifname) continue; memset(&tmp, 0, sizeof(tmp)); - actualType = virDomainNetGetActualType(net); - QEMU_ADD_NAME_PARAM(record, maxparams, - "net", "name", i, net->ifname); + if (actualType != VIR_DOMAIN_NET_TYPE_HOSTDEV) + QEMU_ADD_NAME_PARAM(record, maxparams, + "net", "name", i, net->ifname); + else + QEMU_ADD_NAME_PARAM(record, maxparams, + "net", "name", i, vf_representor_ifname); if (actualType == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { if (virNetDevOpenvswitchInterfaceStats(net->ifname, &tmp) < 0) { virResetLastError(); continue; } + } else if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV) { + if (virNetdevHostdevVFRepInterfaceStats + (vf_representor_ifname, &tmp, + !virDomainNetTypeSharesHostView(net)) < 0) { + VIR_FREE(vf_representor_ifname); + virResetLastError(); + continue; + } + VIR_FREE(vf_representor_ifname); } else { if (virNetDevTapInterfaceStats(net->ifname, &tmp, !virDomainNetTypeSharesHostView(net)) < 0) { -- 2.13.6
participants (1)
-
Jai Singh Rana