From 9bd9a05b8b64489598ca4e0d241247cb99406d20 Mon Sep 17 00:00:00 2001 From: Jim Fehlig Date: Mon, 1 Aug 2016 21:16:43 -0600 Subject: [PATCH] virpci: support driver_override sysfs interface libvirt uses the new_id PCI sysfs interface to bind a PCI stub driver to a PCI device. The new_id interface is known to be buggy and racey, hence a more deterministic interface was introduced in the 3.12 kernel: driver_override. For more details see https://www.redhat.com/archives/libvir-list/2016-June/msg02124.html This patch adds support for the driver_override interface by - adding new virPCIDevice{BindTo,UnbindFrom}StubWithOverride functions that use the driver_override interface - renames the existing virPCIDevice{BindTo,UnbindFrom}Stub functions to virPCIDevice{BindTo,UnbindFrom}StubWithNewid to perserve existing behavior on new_id interface - changes virPCIDevice{BindTo,UnbindFrom}Stub function to call one of the above depending on availability of driver_override The patch includes a bit of duplicate code, but allows for easily dropping the new_id code once support for older kernels is no longer desired. Signed-off-by: Jim Fehlig --- src/util/virpci.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) Index: libvirt-2.2.0/src/util/virpci.c =================================================================== --- libvirt-2.2.0.orig/src/util/virpci.c +++ libvirt-2.2.0/src/util/virpci.c @@ -1089,8 +1089,54 @@ virPCIDeviceUnbind(virPCIDevicePtr dev) return ret; } +/* + * Bind a PCI device to a driver using driver_override sysfs interface. + * E.g. + * + * echo driver-name > /sys/bus/pci/devices/0000:03:00.0/driver_override + * echo 0000:03:00.0 > /sys/bus/pci/devices/0000:03:00.0/driver/unbind + * echo 0000:03:00.0 > /sys/bus/pci/drivers_probe + * + * An empty driverName will cause the device to be bound to its + * preferred driver. + */ static int -virPCIDeviceUnbindFromStub(virPCIDevicePtr dev) +virPCIDeviceBindWithDriverOverride(virPCIDevicePtr dev, + const char *driverName) +{ + int ret = -1; + char *path; + + if (!(path = virPCIFile(dev->name, "driver_override"))) + return -1; + + if (virFileWriteStr(path, driverName, 0) < 0) { + virReportSystemError(errno, + _("Failed to add driver '%s' to driver_override " + " interface of PCI device '%s'"), + driverName, dev->name); + goto cleanup; + } + + if (virPCIDeviceUnbind(dev) < 0) + goto cleanup; + + if (virFileWriteStr(PCI_SYSFS "drivers_probe", dev->name, 0) < 0) { + virReportSystemError(errno, + _("Failed to trigger a probe for PCI device '%s'"), + dev->name); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FREE(path); + return ret; +} + +static int +virPCIDeviceUnbindFromStubWithNewid(virPCIDevicePtr dev) { int result = -1; char *drvdir = NULL; @@ -1191,9 +1237,41 @@ virPCIDeviceUnbindFromStub(virPCIDeviceP return result; } +static int +virPCIDeviceUnbindFromStubWithOverride(virPCIDevicePtr dev) +{ + if (!dev->unbind_from_stub) { + VIR_DEBUG("Unbind from stub skipped for PCI device %s", dev->name); + return 0; + } + + return virPCIDeviceBindWithDriverOverride(dev, "\n"); +} + +static int +virPCIDeviceUnbindFromStub(virPCIDevicePtr dev) +{ + int ret; + char *path; + + /* + * Prefer using the device's driver_override interface, falling back + * to the unpleasant new_id interface. + */ + if (!(path = virPCIFile(dev->name, "driver_override"))) + return -1; + + if (virFileExists(path)) + ret = virPCIDeviceUnbindFromStubWithOverride(dev); + else + ret = virPCIDeviceUnbindFromStubWithNewid(dev); + + VIR_FREE(path); + return ret; +} static int -virPCIDeviceBindToStub(virPCIDevicePtr dev) +virPCIDeviceBindToStubWithNewid(virPCIDevicePtr dev) { int result = -1; bool reprobe = false; @@ -1345,6 +1423,75 @@ virPCIDeviceBindToStub(virPCIDevicePtr d return result; } +static int +virPCIDeviceBindToStubWithOverride(virPCIDevicePtr dev) +{ + int ret = -1; + const char *stubDriverName; + char *stubDriverPath = NULL; + char *driverLink = NULL; + + /* Check the device is configured to use one of the known stub drivers */ + if (dev->stubDriver == VIR_PCI_STUB_DRIVER_NONE) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("No stub driver configured for PCI device %s"), + dev->name); + return -1; + } else if (!(stubDriverName = virPCIStubDriverTypeToString(dev->stubDriver))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown stub driver configured for PCI device %s"), + dev->name); + return -1; + } + + if (!(stubDriverPath = virPCIDriverDir(stubDriverName)) || + !(driverLink = virPCIFile(dev->name, "driver"))) + goto cleanup; + + if (virFileExists(driverLink)) { + if (virFileLinkPointsTo(driverLink, stubDriverPath)) { + /* The device is already bound to the correct driver */ + VIR_DEBUG("Device %s is already bound to %s", + dev->name, stubDriverName); + ret = 0; + goto cleanup; + } + } + + if (virPCIDeviceBindWithDriverOverride(dev, stubDriverName) < 0) + goto cleanup; + + dev->unbind_from_stub = true; + ret = 0; + + cleanup: + VIR_FREE(stubDriverPath); + VIR_FREE(driverLink); + return ret; +} + +static int +virPCIDeviceBindToStub(virPCIDevicePtr dev) +{ + int ret; + char *path; + + /* + * Prefer using the device's driver_override interface, falling back + * to the unpleasant new_id interface. + */ + if (!(path = virPCIFile(dev->name, "driver_override"))) + return -1; + + if (virFileExists(path)) + ret = virPCIDeviceBindToStubWithOverride(dev); + else + ret = virPCIDeviceBindToStubWithNewid(dev); + + VIR_FREE(path); + return ret; +} + /* virPCIDeviceDetach: * * Detach this device from the host driver, attach it to the stub