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

For more details about the driver_override interface and examples of
its usage, see

https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/drivers/pci/pci-driver.c?h=v3.12&id=782a985d7af26db39e86070d28f987cad21313c0

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 <jfehlig@suse.com>
This commit is contained in:
Jim Fehlig 2016-08-01 21:36:45 -06:00
parent e01d300bb1
commit 70f83f9d52

View File

@ -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(virPCIDevicePtr dev)
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
virPCIDeviceBindToStub(virPCIDevicePtr dev)
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
virPCIDeviceBindToStubWithNewid(virPCIDevicePtr dev)
{
int result = -1;
bool reprobe = false;
@ -1345,6 +1423,75 @@ virPCIDeviceBindToStub(virPCIDevicePtr dev)
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