mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-05 04:41:20 +00:00
3e7db8d3e8
Right-aligning backslashes when defining macros or using complex commands in Makefiles looks cute, but as soon as any changes is required to the code you end up with either distractingly broken alignment or unnecessarily big diffs where most of the changes are just pushing all backslashes a few characters to one side. Generated using $ git grep -El '[[:blank:]][[:blank:]]\\$' | \ grep -E '*\.([chx]|am|mk)$$' | \ while read f; do \ sed -Ei 's/[[:blank:]]*[[:blank:]]\\$/ \\/g' "$f"; \ done Signed-off-by: Andrea Bolognani <abologna@redhat.com>
1052 lines
26 KiB
C
1052 lines
26 KiB
C
/*
|
|
* Copyright (C) 2013 Red Hat, Inc.
|
|
*
|
|
* 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/>.
|
|
*
|
|
* Author: Michal Privoznik <mprivozn@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef __linux__
|
|
# include "virmock.h"
|
|
# include <stdio.h>
|
|
# include <stdlib.h>
|
|
# include <unistd.h>
|
|
# include <fcntl.h>
|
|
# include <sys/stat.h>
|
|
# include <stdarg.h>
|
|
# include <dirent.h>
|
|
# include "viralloc.h"
|
|
# include "virstring.h"
|
|
# include "virfile.h"
|
|
# include "dirname.h"
|
|
|
|
static int (*real_access)(const char *path, int mode);
|
|
static int (*real_lstat)(const char *path, struct stat *sb);
|
|
static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
|
|
static int (*real_stat)(const char *path, struct stat *sb);
|
|
static int (*real___xstat)(int ver, const char *path, struct stat *sb);
|
|
static char *(*real_canonicalize_file_name)(const char *path);
|
|
static int (*real_open)(const char *path, int flags, ...);
|
|
static int (*real_close)(int fd);
|
|
static DIR * (*real_opendir)(const char *name);
|
|
|
|
/* Don't make static, since it causes problems with clang
|
|
* when passed as an arg to virAsprintf()
|
|
* vircgroupmock.c:462:22: error: static variable 'fakesysfsdir' is used in an inline function with external linkage [-Werror,-Wstatic-in-inline]
|
|
*/
|
|
char *fakerootdir;
|
|
char *fakesysfspcidir;
|
|
|
|
# define SYSFS_PCI_PREFIX "/sys/bus/pci/"
|
|
|
|
# define STDERR(...) \
|
|
fprintf(stderr, "%s %zu: ", __FUNCTION__, (size_t) __LINE__); \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
fprintf(stderr, "\n"); \
|
|
|
|
# define ABORT(...) \
|
|
do { \
|
|
STDERR(__VA_ARGS__); \
|
|
abort(); \
|
|
} while (0)
|
|
|
|
# define ABORT_OOM() \
|
|
ABORT("Out of memory")
|
|
/*
|
|
* The plan:
|
|
*
|
|
* Mock some file handling functions. Redirect them into a stub tree passed via
|
|
* LIBVIRT_FAKE_ROOT_DIR env variable. All files and links within stub tree is
|
|
* created by us. There are some actions that we must take if some special
|
|
* files are written to. Here's the list of files we watch:
|
|
*
|
|
* /sys/bus/pci/drivers/<driver>/new_id:
|
|
* Learn the driver new vendor and device combination.
|
|
* Data in format "VVVV DDDD".
|
|
*
|
|
* /sys/bus/pci/drivers/<driver>/remove_id
|
|
* Make the driver forget about vendor and device.
|
|
* Data in format "VVVV DDDD".
|
|
*
|
|
* /sys/bus/pci/drivers/<driver>/bind
|
|
* Check if driver supports the device and bind driver to it (create symlink
|
|
* called 'driver' pointing to the /sys/but/pci/drivers/<driver>).
|
|
* Data in format "DDDD:BB:DD.F" (Domain:Bus:Device.Function).
|
|
*
|
|
* /sys/bus/pci/drivers/<driver>/unbind
|
|
* Unbind driver from the device.
|
|
* Data in format "DDDD:BB:DD.F" (Domain:Bus:Device.Function).
|
|
*
|
|
* /sys/bus/pci/drivers_probe
|
|
* Probe for a driver that handles the specified device.
|
|
* Data in format "DDDD:BB:DD.F" (Domain:Bus:Device.Function).
|
|
*
|
|
* As a little hack, we are not mocking write to these files, but close()
|
|
* instead. The advantage is we don't need any self growing array to hold the
|
|
* partial writes and construct them back. We can let all the writes finish,
|
|
* and then just read the file content back.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* Functions to model kernel behavior
|
|
*
|
|
*/
|
|
|
|
enum driverActions {
|
|
PCI_ACTION_BIND = 1 << 0,
|
|
PCI_ACTION_UNBIND = 1 << 1,
|
|
PCI_ACTION_NEW_ID = 1 << 2,
|
|
PCI_ACTION_REMOVE_ID = 1 << 3,
|
|
};
|
|
|
|
struct pciDriver {
|
|
char *name;
|
|
int *vendor; /* List of vendor:device IDs the driver can handle */
|
|
int *device;
|
|
size_t len; /* @len is used for both @vendor and @device */
|
|
unsigned int fail; /* Bitwise-OR of driverActions that should fail */
|
|
};
|
|
|
|
struct pciDevice {
|
|
char *id;
|
|
int vendor;
|
|
int device;
|
|
int class;
|
|
int iommuGroup;
|
|
struct pciDriver *driver; /* Driver attached. NULL if attached to no driver */
|
|
};
|
|
|
|
struct fdCallback {
|
|
int fd;
|
|
char *path;
|
|
};
|
|
|
|
struct pciDevice **pciDevices = NULL;
|
|
size_t nPCIDevices = 0;
|
|
|
|
struct pciDriver **pciDrivers = NULL;
|
|
size_t nPCIDrivers = 0;
|
|
|
|
struct fdCallback *callbacks = NULL;
|
|
size_t nCallbacks = 0;
|
|
|
|
static void init_env(void);
|
|
|
|
static int pci_device_autobind(struct pciDevice *dev);
|
|
static void pci_device_new_from_stub(const struct pciDevice *data);
|
|
static struct pciDevice *pci_device_find_by_id(const char *id);
|
|
static struct pciDevice *pci_device_find_by_content(const char *path);
|
|
|
|
static void pci_driver_new(const char *name, int fail, ...);
|
|
static struct pciDriver *pci_driver_find_by_dev(struct pciDevice *dev);
|
|
static struct pciDriver *pci_driver_find_by_path(const char *path);
|
|
static int pci_driver_bind(struct pciDriver *driver, struct pciDevice *dev);
|
|
static int pci_driver_unbind(struct pciDriver *driver, struct pciDevice *dev);
|
|
static int pci_driver_handle_change(int fd, const char *path);
|
|
static int pci_driver_handle_bind(const char *path);
|
|
static int pci_driver_handle_unbind(const char *path);
|
|
static int pci_driver_handle_new_id(const char *path);
|
|
static int pci_driver_handle_remove_id(const char *path);
|
|
|
|
|
|
/*
|
|
* Helper functions
|
|
*/
|
|
static void
|
|
make_file(const char *path,
|
|
const char *name,
|
|
const char *value,
|
|
ssize_t len)
|
|
{
|
|
int fd = -1;
|
|
char *filepath = NULL;
|
|
if (value && len == -1)
|
|
len = strlen(value);
|
|
|
|
if (virAsprintfQuiet(&filepath, "%s/%s", path, name) < 0)
|
|
ABORT_OOM();
|
|
|
|
if ((fd = real_open(filepath, O_CREAT|O_WRONLY, 0666)) < 0)
|
|
ABORT("Unable to open: %s", filepath);
|
|
|
|
if (value && safewrite(fd, value, len) != len)
|
|
ABORT("Unable to write: %s", filepath);
|
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
VIR_FREE(filepath);
|
|
}
|
|
|
|
static void
|
|
make_symlink(const char *path,
|
|
const char *name,
|
|
const char *target)
|
|
{
|
|
char *filepath = NULL;
|
|
|
|
if (virAsprintfQuiet(&filepath, "%s/%s", path, name) < 0)
|
|
ABORT_OOM();
|
|
|
|
if (symlink(target, filepath) < 0)
|
|
ABORT("Unable to create symlink filepath -> target");
|
|
|
|
VIR_FREE(filepath);
|
|
}
|
|
|
|
static int
|
|
pci_read_file(const char *path,
|
|
char *buf,
|
|
size_t buf_size)
|
|
{
|
|
int ret = -1;
|
|
int fd = -1;
|
|
char *newpath;
|
|
|
|
if (virAsprintfQuiet(&newpath, "%s/%s",
|
|
fakesysfspcidir,
|
|
path + strlen(SYSFS_PCI_PREFIX)) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((fd = real_open(newpath, O_RDWR)) < 0)
|
|
goto cleanup;
|
|
|
|
bzero(buf, buf_size);
|
|
if (saferead(fd, buf, buf_size - 1) < 0) {
|
|
STDERR("Unable to read from %s", newpath);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ftruncate(fd, 0) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(newpath);
|
|
real_close(fd);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
getrealpath(char **newpath,
|
|
const char *path)
|
|
{
|
|
init_env();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
if (virAsprintfQuiet(newpath, "%s/%s",
|
|
fakesysfspcidir,
|
|
path + strlen(SYSFS_PCI_PREFIX)) < 0) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (VIR_STRDUP_QUIET(*newpath, path) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
find_fd(int fd, size_t *indx)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nCallbacks; i++) {
|
|
if (callbacks[i].fd == fd) {
|
|
if (indx)
|
|
*indx = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
add_fd(int fd, const char *path)
|
|
{
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (find_fd(fd, &i)) {
|
|
struct fdCallback cb = callbacks[i];
|
|
ABORT("FD %d %s already present in the array as %d %s",
|
|
fd, path, cb.fd, cb.path);
|
|
}
|
|
|
|
if (VIR_REALLOC_N_QUIET(callbacks, nCallbacks + 1) < 0 ||
|
|
VIR_STRDUP_QUIET(callbacks[nCallbacks].path, path) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
callbacks[nCallbacks++].fd = fd;
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
remove_fd(int fd)
|
|
{
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (find_fd(fd, &i)) {
|
|
struct fdCallback cb = callbacks[i];
|
|
|
|
if (pci_driver_handle_change(cb.fd, cb.path) < 0)
|
|
goto cleanup;
|
|
|
|
VIR_FREE(cb.path);
|
|
if (VIR_DELETE_ELEMENT(callbacks, i, nCallbacks) < 0) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* PCI Device functions
|
|
*/
|
|
static void
|
|
pci_device_new_from_stub(const struct pciDevice *data)
|
|
{
|
|
struct pciDevice *dev;
|
|
char *devpath;
|
|
char *id;
|
|
char *c;
|
|
char *configSrc;
|
|
char tmp[256];
|
|
struct stat sb;
|
|
|
|
if (VIR_STRDUP_QUIET(id, data->id) < 0)
|
|
ABORT_OOM();
|
|
|
|
/* Replace ':' with '-' to create the config filename from the
|
|
* device ID. The device ID cannot be used directly as filename
|
|
* because it contains ':' and Windows does not allow ':' in
|
|
* filenames. */
|
|
c = strchr(id, ':');
|
|
|
|
while (c) {
|
|
*c = '-';
|
|
c = strchr(c, ':');
|
|
}
|
|
|
|
if (VIR_ALLOC_QUIET(dev) < 0 ||
|
|
virAsprintfQuiet(&configSrc, "%s/virpcitestdata/%s.config",
|
|
abs_srcdir, id) < 0 ||
|
|
virAsprintfQuiet(&devpath, "%s/devices/%s", fakesysfspcidir, data->id) < 0)
|
|
ABORT_OOM();
|
|
|
|
VIR_FREE(id);
|
|
memcpy(dev, data, sizeof(*dev));
|
|
|
|
if (virFileMakePath(devpath) < 0)
|
|
ABORT("Unable to create: %s", devpath);
|
|
|
|
/* If there is a config file for the device within virpcitestdata dir,
|
|
* symlink it. Otherwise create a dummy config file. */
|
|
if ((real_stat && real_stat(configSrc, &sb) == 0) ||
|
|
(real___xstat && real___xstat(_STAT_VER, configSrc, &sb) == 0)) {
|
|
/* On success, copy @configSrc into the destination (a copy,
|
|
* rather than a symlink, is required since we write into the
|
|
* file, and parallel VPATH builds must not stomp on the
|
|
* original; besides, 'make distcheck' requires the original
|
|
* to be read-only */
|
|
char *buf;
|
|
ssize_t len;
|
|
|
|
if ((len = virFileReadAll(configSrc, 4096, &buf)) < 0)
|
|
ABORT("Unable to read config file '%s'", configSrc);
|
|
|
|
make_file(devpath, "config", buf, len);
|
|
VIR_FREE(buf);
|
|
} else {
|
|
/* If there's no config data in the virpcitestdata dir, create a dummy
|
|
* config file */
|
|
make_file(devpath, "config", "some dummy config", -1);
|
|
}
|
|
|
|
if (snprintf(tmp, sizeof(tmp), "0x%.4x", dev->vendor) < 0)
|
|
ABORT("@tmp overflow");
|
|
make_file(devpath, "vendor", tmp, -1);
|
|
|
|
if (snprintf(tmp, sizeof(tmp), "0x%.4x", dev->device) < 0)
|
|
ABORT("@tmp overflow");
|
|
make_file(devpath, "device", tmp, -1);
|
|
|
|
if (snprintf(tmp, sizeof(tmp), "0x%.4x", dev->class) < 0)
|
|
ABORT("@tmp overflow");
|
|
make_file(devpath, "class", tmp, -1);
|
|
|
|
if (snprintf(tmp, sizeof(tmp),
|
|
"%s/../../../kernel/iommu_groups/%d",
|
|
devpath, dev->iommuGroup) < 0) {
|
|
ABORT("@tmp overflow");
|
|
}
|
|
if (virFileMakePath(tmp) < 0)
|
|
ABORT("Unable to create %s", tmp);
|
|
|
|
if (snprintf(tmp, sizeof(tmp),
|
|
"../../../kernel/iommu_groups/%d", dev->iommuGroup) < 0) {
|
|
ABORT("@tmp overflow");
|
|
}
|
|
make_symlink(devpath, "iommu_group", tmp);
|
|
|
|
if (pci_device_autobind(dev) < 0)
|
|
ABORT("Unable to bind: %s", data->id);
|
|
|
|
if (VIR_APPEND_ELEMENT_QUIET(pciDevices, nPCIDevices, dev) < 0)
|
|
ABORT_OOM();
|
|
|
|
VIR_FREE(devpath);
|
|
VIR_FREE(configSrc);
|
|
}
|
|
|
|
static struct pciDevice *
|
|
pci_device_find_by_id(const char *id)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < nPCIDevices; i++) {
|
|
struct pciDevice *dev = pciDevices[i];
|
|
|
|
if (STREQ(dev->id, id))
|
|
return dev;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct pciDevice *
|
|
pci_device_find_by_content(const char *path)
|
|
{
|
|
char tmp[32];
|
|
|
|
if (pci_read_file(path, tmp, sizeof(tmp)) < 0)
|
|
return NULL;
|
|
|
|
return pci_device_find_by_id(tmp);
|
|
}
|
|
|
|
static int
|
|
pci_device_autobind(struct pciDevice *dev)
|
|
{
|
|
struct pciDriver *driver = pci_driver_find_by_dev(dev);
|
|
|
|
if (!driver) {
|
|
/* No driver found. Nothing to do */
|
|
return 0;
|
|
}
|
|
|
|
return pci_driver_bind(driver, dev);
|
|
}
|
|
|
|
|
|
/*
|
|
* PCI Driver functions
|
|
*/
|
|
static void
|
|
pci_driver_new(const char *name, int fail, ...)
|
|
{
|
|
struct pciDriver *driver;
|
|
va_list args;
|
|
int vendor, device;
|
|
char *driverpath;
|
|
|
|
if (VIR_ALLOC_QUIET(driver) < 0 ||
|
|
VIR_STRDUP_QUIET(driver->name, name) < 0 ||
|
|
virAsprintfQuiet(&driverpath, "%s/drivers/%s", fakesysfspcidir, name) < 0)
|
|
ABORT_OOM();
|
|
|
|
driver->fail = fail;
|
|
|
|
if (virFileMakePath(driverpath) < 0)
|
|
ABORT("Unable to create: %s", driverpath);
|
|
|
|
va_start(args, fail);
|
|
|
|
while ((vendor = va_arg(args, int)) != -1) {
|
|
if ((device = va_arg(args, int)) == -1)
|
|
ABORT("Invalid vendor device pair for driver %s", name);
|
|
|
|
if (VIR_REALLOC_N_QUIET(driver->vendor, driver->len + 1) < 0 ||
|
|
VIR_REALLOC_N_QUIET(driver->device, driver->len + 1) < 0)
|
|
ABORT_OOM();
|
|
|
|
driver->vendor[driver->len] = vendor;
|
|
driver->device[driver->len] = device;
|
|
driver->len++;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
make_file(driverpath, "bind", NULL, -1);
|
|
make_file(driverpath, "unbind", NULL, -1);
|
|
make_file(driverpath, "new_id", NULL, -1);
|
|
make_file(driverpath, "remove_id", NULL, -1);
|
|
|
|
if (VIR_APPEND_ELEMENT_QUIET(pciDrivers, nPCIDrivers, driver) < 0)
|
|
ABORT_OOM();
|
|
|
|
VIR_FREE(driverpath);
|
|
}
|
|
|
|
static struct pciDriver *
|
|
pci_driver_find_by_dev(struct pciDevice *dev)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nPCIDrivers; i++) {
|
|
struct pciDriver *driver = pciDrivers[i];
|
|
size_t j;
|
|
|
|
for (j = 0; j < driver->len; j++) {
|
|
if (driver->vendor[j] == dev->vendor &&
|
|
driver->device[j] == dev->device)
|
|
return driver;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct pciDriver *
|
|
pci_driver_find_by_path(const char *path)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nPCIDrivers; i++) {
|
|
struct pciDriver *driver = pciDrivers[i];
|
|
|
|
if (strstr(path, driver->name))
|
|
return driver;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
pci_driver_bind(struct pciDriver *driver,
|
|
struct pciDevice *dev)
|
|
{
|
|
int ret = -1;
|
|
char *devpath = NULL, *driverpath = NULL;
|
|
|
|
if (dev->driver || PCI_ACTION_BIND & driver->fail) {
|
|
/* Device already bound or failing driver requested */
|
|
errno = ENODEV;
|
|
return ret;
|
|
}
|
|
|
|
/* Make symlink under device tree */
|
|
if (virAsprintfQuiet(&devpath, "%s/devices/%s/driver",
|
|
fakesysfspcidir, dev->id) < 0 ||
|
|
virAsprintfQuiet(&driverpath, "%s/drivers/%s",
|
|
fakesysfspcidir, driver->name) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (symlink(driverpath, devpath) < 0)
|
|
goto cleanup;
|
|
|
|
/* Make symlink under driver tree */
|
|
VIR_FREE(devpath);
|
|
VIR_FREE(driverpath);
|
|
if (virAsprintfQuiet(&devpath, "%s/devices/%s",
|
|
fakesysfspcidir, dev->id) < 0 ||
|
|
virAsprintfQuiet(&driverpath, "%s/drivers/%s/%s",
|
|
fakesysfspcidir, driver->name, dev->id) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (symlink(devpath, driverpath) < 0)
|
|
goto cleanup;
|
|
|
|
dev->driver = driver;
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(devpath);
|
|
VIR_FREE(driverpath);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
pci_driver_unbind(struct pciDriver *driver,
|
|
struct pciDevice *dev)
|
|
{
|
|
int ret = -1;
|
|
char *devpath = NULL, *driverpath = NULL;
|
|
|
|
if (dev->driver != driver || PCI_ACTION_UNBIND & driver->fail) {
|
|
/* Device not bound to the @driver or failing driver used */
|
|
errno = ENODEV;
|
|
return ret;
|
|
}
|
|
|
|
/* Make symlink under device tree */
|
|
if (virAsprintfQuiet(&devpath, "%s/devices/%s/driver",
|
|
fakesysfspcidir, dev->id) < 0 ||
|
|
virAsprintfQuiet(&driverpath, "%s/drivers/%s/%s",
|
|
fakesysfspcidir, driver->name, dev->id) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (unlink(devpath) < 0 ||
|
|
unlink(driverpath) < 0)
|
|
goto cleanup;
|
|
|
|
dev->driver = NULL;
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(devpath);
|
|
VIR_FREE(driverpath);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
pci_driver_handle_drivers_probe(const char *path)
|
|
{
|
|
struct pciDevice *dev;
|
|
|
|
if (!(dev = pci_device_find_by_content(path))) {
|
|
errno = ENODEV;
|
|
return -1;
|
|
}
|
|
|
|
if (dev->driver)
|
|
return 0;
|
|
|
|
return pci_device_autobind(dev);
|
|
}
|
|
|
|
static int
|
|
pci_driver_handle_change(int fd ATTRIBUTE_UNUSED, const char *path)
|
|
{
|
|
int ret;
|
|
const char *file = last_component(path);
|
|
|
|
if (STREQ(file, "bind"))
|
|
ret = pci_driver_handle_bind(path);
|
|
else if (STREQ(file, "unbind"))
|
|
ret = pci_driver_handle_unbind(path);
|
|
else if (STREQ(file, "new_id"))
|
|
ret = pci_driver_handle_new_id(path);
|
|
else if (STREQ(file, "remove_id"))
|
|
ret = pci_driver_handle_remove_id(path);
|
|
else if (STREQ(file, "drivers_probe"))
|
|
ret = pci_driver_handle_drivers_probe(path);
|
|
else
|
|
ABORT("Not handled write to: %s", path);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
pci_driver_handle_bind(const char *path)
|
|
{
|
|
int ret = -1;
|
|
struct pciDevice *dev = pci_device_find_by_content(path);
|
|
struct pciDriver *driver = pci_driver_find_by_path(path);
|
|
|
|
if (!driver || !dev) {
|
|
/* This should never happen (TM) */
|
|
errno = ENODEV;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = pci_driver_bind(driver, dev);
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
pci_driver_handle_unbind(const char *path)
|
|
{
|
|
int ret = -1;
|
|
struct pciDevice *dev = pci_device_find_by_content(path);
|
|
|
|
if (!dev || !dev->driver) {
|
|
/* This should never happen (TM) */
|
|
errno = ENODEV;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = pci_driver_unbind(dev->driver, dev);
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
static int
|
|
pci_driver_handle_new_id(const char *path)
|
|
{
|
|
int ret = -1;
|
|
struct pciDriver *driver = pci_driver_find_by_path(path);
|
|
size_t i;
|
|
int vendor, device;
|
|
char buf[32];
|
|
|
|
if (!driver || PCI_ACTION_NEW_ID & driver->fail) {
|
|
errno = ENODEV;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (pci_read_file(path, buf, sizeof(buf)) < 0)
|
|
goto cleanup;
|
|
|
|
if (sscanf(buf, "%x %x", &vendor, &device) < 2) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < driver->len; i++) {
|
|
if (driver->vendor[i] == vendor &&
|
|
driver->device[i] == device)
|
|
break;
|
|
}
|
|
|
|
if (i == driver->len) {
|
|
if (VIR_REALLOC_N_QUIET(driver->vendor, driver->len + 1) < 0 ||
|
|
VIR_REALLOC_N_QUIET(driver->device, driver->len + 1) < 0) {
|
|
errno = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
driver->vendor[driver->len] = vendor;
|
|
driver->device[driver->len] = device;
|
|
driver->len++;
|
|
}
|
|
|
|
for (i = 0; i < nPCIDevices; i++) {
|
|
struct pciDevice *dev = pciDevices[i];
|
|
|
|
if (!dev->driver &&
|
|
dev->vendor == vendor &&
|
|
dev->device == device &&
|
|
pci_driver_bind(driver, dev) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
pci_driver_handle_remove_id(const char *path)
|
|
{
|
|
int ret = -1;
|
|
struct pciDriver *driver = pci_driver_find_by_path(path);
|
|
size_t i;
|
|
int vendor, device;
|
|
char buf[32];
|
|
|
|
if (!driver || PCI_ACTION_REMOVE_ID & driver->fail) {
|
|
errno = ENODEV;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (pci_read_file(path, buf, sizeof(buf)) < 0)
|
|
goto cleanup;
|
|
|
|
if (sscanf(buf, "%x %x", &vendor, &device) < 2) {
|
|
errno = EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < driver->len; i++) {
|
|
if (driver->vendor[i] == vendor &&
|
|
driver->device[i] == device)
|
|
continue;
|
|
}
|
|
|
|
if (i != driver->len) {
|
|
if (VIR_DELETE_ELEMENT(driver->vendor, i, driver->len) < 0)
|
|
goto cleanup;
|
|
driver->len++;
|
|
if (VIR_DELETE_ELEMENT(driver->device, i, driver->len) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Functions to load the symbols and init the environment
|
|
*/
|
|
static void
|
|
init_syms(void)
|
|
{
|
|
if (real_access)
|
|
return;
|
|
|
|
VIR_MOCK_REAL_INIT(access);
|
|
VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
|
|
VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
|
|
VIR_MOCK_REAL_INIT(canonicalize_file_name);
|
|
VIR_MOCK_REAL_INIT(open);
|
|
VIR_MOCK_REAL_INIT(close);
|
|
VIR_MOCK_REAL_INIT(opendir);
|
|
}
|
|
|
|
static void
|
|
init_env(void)
|
|
{
|
|
if (fakerootdir && fakesysfspcidir)
|
|
return;
|
|
|
|
if (!(fakerootdir = getenv("LIBVIRT_FAKE_ROOT_DIR")))
|
|
ABORT("Missing LIBVIRT_FAKE_ROOT_DIR env variable\n");
|
|
|
|
if (virAsprintfQuiet(&fakesysfspcidir, "%s%s",
|
|
fakerootdir, SYSFS_PCI_PREFIX) < 0)
|
|
ABORT_OOM();
|
|
|
|
if (virFileMakePath(fakesysfspcidir) < 0)
|
|
ABORT("Unable to create: %s", fakesysfspcidir);
|
|
|
|
make_file(fakesysfspcidir, "drivers_probe", NULL, -1);
|
|
|
|
# define MAKE_PCI_DRIVER(name, ...) \
|
|
pci_driver_new(name, 0, __VA_ARGS__, -1, -1)
|
|
|
|
MAKE_PCI_DRIVER("iwlwifi", 0x8086, 0x0044);
|
|
MAKE_PCI_DRIVER("i915", 0x8086, 0x0046, 0x8086, 0x0047);
|
|
MAKE_PCI_DRIVER("pci-stub", -1, -1);
|
|
pci_driver_new("vfio-pci", PCI_ACTION_BIND, -1, -1);
|
|
|
|
# define MAKE_PCI_DEVICE(Id, Vendor, Device, ...) \
|
|
do { \
|
|
struct pciDevice dev = {.id = (char *)Id, .vendor = Vendor, \
|
|
.device = Device, __VA_ARGS__}; \
|
|
pci_device_new_from_stub(&dev); \
|
|
} while (0)
|
|
|
|
MAKE_PCI_DEVICE("0000:00:00.0", 0x8086, 0x0044);
|
|
MAKE_PCI_DEVICE("0000:00:01.0", 0x8086, 0x0044);
|
|
MAKE_PCI_DEVICE("0000:00:02.0", 0x8086, 0x0046);
|
|
MAKE_PCI_DEVICE("0000:00:03.0", 0x8086, 0x0048);
|
|
MAKE_PCI_DEVICE("0001:00:00.0", 0x1014, 0x03b9, .class = 0x060400);
|
|
MAKE_PCI_DEVICE("0001:01:00.0", 0x8086, 0x105e, .iommuGroup = 0);
|
|
MAKE_PCI_DEVICE("0001:01:00.1", 0x8086, 0x105e, .iommuGroup = 0);
|
|
MAKE_PCI_DEVICE("0005:80:00.0", 0x10b5, 0x8112, .class = 0x060400);
|
|
MAKE_PCI_DEVICE("0005:90:01.0", 0x1033, 0x0035, .iommuGroup = 1);
|
|
MAKE_PCI_DEVICE("0005:90:01.1", 0x1033, 0x0035, .iommuGroup = 1);
|
|
MAKE_PCI_DEVICE("0005:90:01.2", 0x1033, 0x00e0, .iommuGroup = 1);
|
|
MAKE_PCI_DEVICE("0000:0a:01.0", 0x8086, 0x0047);
|
|
MAKE_PCI_DEVICE("0000:0a:02.0", 0x8286, 0x0048);
|
|
MAKE_PCI_DEVICE("0000:0a:03.0", 0x8386, 0x0048);
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* Mocked functions
|
|
*
|
|
*/
|
|
|
|
int
|
|
access(const char *path, int mode)
|
|
{
|
|
int ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
ret = real_access(newpath, mode);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real_access(path, mode);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
__lxstat(int ver, const char *path, struct stat *sb)
|
|
{
|
|
int ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
ret = real___lxstat(ver, newpath, sb);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real___lxstat(ver, path, sb);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
lstat(const char *path, struct stat *sb)
|
|
{
|
|
int ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
ret = real_lstat(newpath, sb);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real_lstat(path, sb);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
__xstat(int ver, const char *path, struct stat *sb)
|
|
{
|
|
int ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
ret = real___xstat(ver, newpath, sb);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real___xstat(ver, path, sb);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
stat(const char *path, struct stat *sb)
|
|
{
|
|
int ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
ret = real_stat(newpath, sb);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real_stat(path, sb);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
canonicalize_file_name(const char *path)
|
|
{
|
|
char *ret;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX)) {
|
|
char *newpath;
|
|
if (getrealpath(&newpath, path) < 0)
|
|
return NULL;
|
|
ret = real_canonicalize_file_name(newpath);
|
|
VIR_FREE(newpath);
|
|
} else {
|
|
ret = real_canonicalize_file_name(path);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
open(const char *path, int flags, ...)
|
|
{
|
|
int ret;
|
|
char *newpath = NULL;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX) &&
|
|
getrealpath(&newpath, path) < 0)
|
|
return -1;
|
|
|
|
if (flags & O_CREAT) {
|
|
va_list ap;
|
|
mode_t mode;
|
|
va_start(ap, flags);
|
|
mode = va_arg(ap, mode_t);
|
|
va_end(ap);
|
|
ret = real_open(newpath ? newpath : path, flags, mode);
|
|
} else {
|
|
ret = real_open(newpath ? newpath : path, flags);
|
|
}
|
|
|
|
/* Catch both: /sys/bus/pci/drivers/... and
|
|
* /sys/bus/pci/device/.../driver/... */
|
|
if (ret >= 0 && STRPREFIX(path, SYSFS_PCI_PREFIX) &&
|
|
strstr(path, "driver") && add_fd(ret, path) < 0) {
|
|
real_close(ret);
|
|
ret = -1;
|
|
}
|
|
|
|
VIR_FREE(newpath);
|
|
return ret;
|
|
}
|
|
|
|
DIR *
|
|
opendir(const char *path)
|
|
{
|
|
DIR *ret;
|
|
char *newpath = NULL;
|
|
|
|
init_syms();
|
|
|
|
if (STRPREFIX(path, SYSFS_PCI_PREFIX) &&
|
|
getrealpath(&newpath, path) < 0)
|
|
return NULL;
|
|
|
|
ret = real_opendir(newpath ? newpath : path);
|
|
|
|
VIR_FREE(newpath);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
close(int fd)
|
|
{
|
|
if (remove_fd(fd) < 0)
|
|
return -1;
|
|
return real_close(fd);
|
|
}
|
|
#else
|
|
/* Nothing to override on non-__linux__ platforms */
|
|
#endif
|