/* * Copyright (C) 2009 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Mark McLoughlin */ #include #include "pci.h" #include #include #include #include #include #include #include #include #include #include "logging.h" #include "memory.h" #include "util.h" #include "virterror_internal.h" /* avoid compilation breakage on some systems */ #ifndef MODPROBE #define MODPROBE "modprobe" #endif #define PCI_SYSFS "/sys/bus/pci/" #define PCI_ID_LEN 10 /* "XXXX XXXX" */ #define PCI_ADDR_LEN 13 /* "XXXX:XX:XX.X" */ struct _pciDevice { unsigned domain; unsigned bus; unsigned slot; unsigned function; char name[PCI_ADDR_LEN]; /* domain:bus:slot.function */ char id[PCI_ID_LEN]; /* product vendor */ char path[PATH_MAX]; int fd; unsigned initted; unsigned pcie_cap_pos; unsigned pci_pm_cap_pos; unsigned has_flr : 1; unsigned has_pm_reset : 1; }; /* For virReportOOMError() and virReportSystemError() */ #define VIR_FROM_THIS VIR_FROM_NONE #define pciReportError(conn, code, fmt...) \ virReportErrorHelper(conn, VIR_FROM_NONE, code, __FILE__, \ __FUNCTION__, __LINE__, fmt) /* Specifications referenced in comments: * PCI30 - PCI Local Bus Specification 3.0 * PCIe20 - PCI Express Base Specification 2.0 * BR12 - PCI-to-PCI Bridge Architecture Specification 1.2 * PM12 - PCI Bus Power Management Interface Specification 1.2 * ECN_AF - Advanced Capabilities for Conventional PCI ECN */ /* Type 0 config space header length; PCI30 Section 6.1 Configuration Space Organization */ #define PCI_CONF_LEN 0x100 #define PCI_CONF_HEADER_LEN 0x40 /* PCI30 6.2.1 */ #define PCI_HEADER_TYPE 0x0e /* Header type */ #define PCI_HEADER_TYPE_BRIDGE 0x1 #define PCI_HEADER_TYPE_MASK 0x7f #define PCI_HEADER_TYPE_MULTI 0x80 /* PCI30 6.2.1 Device Identification */ #define PCI_CLASS_DEVICE 0x0a /* Device class */ /* Class Code for bridge; PCI30 D.7 Base Class 06h */ #define PCI_CLASS_BRIDGE_PCI 0x0604 /* PCI30 6.2.3 Device Status */ #define PCI_STATUS 0x06 /* 16 bits */ #define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List */ /* PCI30 6.7 Capabilities List */ #define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */ /* PM12 3.2.1 Capability Identifier */ #define PCI_CAP_ID_PM 0x01 /* Power Management */ /* PCI30 H Capability IDs */ #define PCI_CAP_ID_EXP 0x10 /* PCI Express */ /* ECN_AF 6.x.1.1 Capability ID for AF */ #define PCI_CAP_ID_AF 0x13 /* Advanced Features */ /* PCIe20 7.8.3 Device Capabilities Register (Offset 04h) */ #define PCI_EXP_DEVCAP 0x4 /* Device capabilities */ #define PCI_EXP_DEVCAP_FLR (1<<28) /* Function Level Reset */ /* Header type 1 BR12 3.2 PCI-to-PCI Bridge Configuration Space Header Format */ #define PCI_PRIMARY_BUS 0x18 /* BR12 3.2.5.2 Primary bus number */ #define PCI_SECONDARY_BUS 0x19 /* BR12 3.2.5.3 Secondary bus number */ #define PCI_SUBORDINATE_BUS 0x1a /* BR12 3.2.5.4 Highest bus number behind the bridge */ #define PCI_BRIDGE_CONTROL 0x3e /* BR12 3.2.5.18 Bridge Control Register */ #define PCI_BRIDGE_CTL_RESET 0x40 /* Secondary bus reset */ /* PM12 3.2.4 Power Management Control/Status (Offset = 4) */ #define PCI_PM_CTRL 4 /* PM control and status register */ #define PCI_PM_CTRL_STATE_MASK 0x3 /* Current power state (D0 to D3) */ #define PCI_PM_CTRL_STATE_D0 0x0 /* D0 state */ #define PCI_PM_CTRL_STATE_D3hot 0x3 /* D3 state */ #define PCI_PM_CTRL_NO_SOFT_RESET 0x8 /* No reset for D3hot->D0 */ /* ECN_AF 6.x.1 Advanced Features Capability Structure */ #define PCI_AF_CAP 0x3 /* Advanced features capabilities */ #define PCI_AF_CAP_FLR 0x2 /* Function Level Reset */ static int pciOpenConfig(pciDevice *dev) { int fd; if (dev->fd > 0) return 0; fd = open(dev->path, O_RDWR); if (fd < 0) { char ebuf[1024]; VIR_WARN(_("Failed to open config space file '%s': %s"), dev->path, virStrerror(errno, ebuf, sizeof(ebuf))); return -1; } VIR_DEBUG("%s %s: opened %s", dev->id, dev->name, dev->path); dev->fd = fd; return 0; } static int pciRead(pciDevice *dev, unsigned pos, uint8_t *buf, unsigned buflen) { memset(buf, 0, buflen); if (pciOpenConfig(dev) < 0) return -1; if (lseek(dev->fd, pos, SEEK_SET) != pos || saferead(dev->fd, buf, buflen) != buflen) { char ebuf[1024]; VIR_WARN(_("Failed to read from '%s' : %s"), dev->path, virStrerror(errno, ebuf, sizeof(ebuf))); return -1; } return 0; } static uint8_t pciRead8(pciDevice *dev, unsigned pos) { uint8_t buf; pciRead(dev, pos, &buf, sizeof(buf)); return buf; } static uint16_t pciRead16(pciDevice *dev, unsigned pos) { uint8_t buf[2]; pciRead(dev, pos, &buf[0], sizeof(buf)); return (buf[0] << 0) | (buf[1] << 8); } static uint32_t pciRead32(pciDevice *dev, unsigned pos) { uint8_t buf[4]; pciRead(dev, pos, &buf[0], sizeof(buf)); return (buf[0] << 0) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); } static int pciWrite(pciDevice *dev, unsigned pos, uint8_t *buf, unsigned buflen) { if (pciOpenConfig(dev) < 0) return -1; if (lseek(dev->fd, pos, SEEK_SET) != pos || safewrite(dev->fd, buf, buflen) != buflen) { char ebuf[1024]; VIR_WARN(_("Failed to write to '%s' : %s"), dev->path, virStrerror(errno, ebuf, sizeof(ebuf))); return -1; } return 0; } static void pciWrite16(pciDevice *dev, unsigned pos, uint16_t val) { uint8_t buf[2] = { (val >> 0), (val >> 8) }; pciWrite(dev, pos, &buf[0], sizeof(buf)); } static void pciWrite32(pciDevice *dev, unsigned pos, uint32_t val) { uint8_t buf[4] = { (val >> 0), (val >> 8), (val >> 16), (val >> 14) }; pciWrite(dev, pos, &buf[0], sizeof(buf)); } typedef int (*pciIterPredicate)(pciDevice *, pciDevice *); /* Iterate over available PCI devices calling @predicate * to compare each one to @dev. * Return -1 on error since we don't want to assume it is * safe to reset if there is an error. */ static int pciIterDevices(virConnectPtr conn, pciIterPredicate predicate, pciDevice *dev, pciDevice **matched) { DIR *dir; struct dirent *entry; int ret = 0; *matched = NULL; VIR_DEBUG("%s %s: iterating over " PCI_SYSFS "devices", dev->id, dev->name); dir = opendir(PCI_SYSFS "devices"); if (!dir) { VIR_WARN0("Failed to open " PCI_SYSFS "devices"); return -1; } while ((entry = readdir(dir))) { unsigned domain, bus, slot, function; pciDevice *try; /* Ignore '.' and '..' */ if (entry->d_name[0] == '.') continue; if (sscanf(entry->d_name, "%x:%x:%x.%x", &domain, &bus, &slot, &function) < 4) { VIR_WARN("Unusual entry in " PCI_SYSFS "devices: %s", entry->d_name); continue; } try = pciGetDevice(conn, domain, bus, slot, function); if (!try) { ret = -1; break; } if (predicate(try, dev)) { VIR_DEBUG("%s %s: iter matched on %s", dev->id, dev->name, try->name); *matched = try; break; } pciFreeDevice(conn, try); } closedir(dir); return ret; } static uint8_t pciFindCapabilityOffset(pciDevice *dev, unsigned capability) { uint16_t status; uint8_t pos; status = pciRead16(dev, PCI_STATUS); if (!(status & PCI_STATUS_CAP_LIST)) return 0; pos = pciRead8(dev, PCI_CAPABILITY_LIST); /* Zero indicates last capability, capabilities can't * be in the config space header and 0xff is returned * by the kernel if we don't have access to this region * * Note: we're not handling loops or extended * capabilities here. */ while (pos >= PCI_CONF_HEADER_LEN && pos != 0xff) { uint8_t capid = pciRead8(dev, pos); if (capid == capability) { VIR_DEBUG("%s %s: found cap 0x%.2x at 0x%.2x", dev->id, dev->name, capability, pos); return pos; } pos = pciRead8(dev, pos + 1); } VIR_DEBUG("%s %s: failed to find cap 0x%.2x", dev->id, dev->name, capability); return 0; } static unsigned pciDetectFunctionLevelReset(pciDevice *dev) { uint32_t caps; uint8_t pos; /* The PCIe Function Level Reset capability allows * individual device functions to be reset without * affecting any other functions on the device or * any other devices on the bus. This is only common * on SR-IOV NICs at the moment. */ if (dev->pcie_cap_pos) { caps = pciRead32(dev, dev->pcie_cap_pos + PCI_EXP_DEVCAP); if (caps & PCI_EXP_DEVCAP_FLR) { VIR_DEBUG("%s %s: detected PCIe FLR capability", dev->id, dev->name); return 1; } } /* The PCI AF Function Level Reset capability is * the same thing, except for conventional PCI * devices. This is not common yet. */ pos = pciFindCapabilityOffset(dev, PCI_CAP_ID_AF); if (pos) { caps = pciRead16(dev, pos + PCI_AF_CAP); if (caps & PCI_AF_CAP_FLR) { VIR_DEBUG("%s %s: detected PCI FLR capability", dev->id, dev->name); return 1; } } VIR_DEBUG("%s %s: no FLR capability found", dev->id, dev->name); return 0; } /* Require the device has the PCI Power Management capability * and that a D3hot->D0 transition will results in a full * internal reset, not just a soft reset. */ static unsigned pciDetectPowerManagementReset(pciDevice *dev) { if (dev->pci_pm_cap_pos) { uint32_t ctl; /* require the NO_SOFT_RESET bit is clear */ ctl = pciRead32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL); if (!(ctl & PCI_PM_CTRL_NO_SOFT_RESET)) { VIR_DEBUG("%s %s: detected PM reset capability", dev->id, dev->name); return 1; } } VIR_DEBUG("%s %s: no PM reset capability found", dev->id, dev->name); return 0; } /* Any devices other than the one supplied on the same domain/bus ? */ static int pciSharesBus(pciDevice *a, pciDevice *b) { return a->domain == b->domain && a->bus == b->bus && (a->slot != b->slot || a->function != b->function); } static int pciBusContainsOtherDevices(virConnectPtr conn, pciDevice *dev) { pciDevice *matched = NULL; if (pciIterDevices(conn, pciSharesBus, dev, &matched) < 0) return 1; if (!matched) return 0; pciFreeDevice(conn, matched); return 1; } /* Any other functions on this device ? */ static int pciSharesDevice(pciDevice *a, pciDevice *b) { return a->domain == b->domain && a->bus == b->bus && a->slot == b->slot && a->function != b->function; } static int pciDeviceContainsOtherFunctions(virConnectPtr conn, pciDevice *dev) { pciDevice *matched = NULL; if (pciIterDevices(conn, pciSharesDevice, dev, &matched) < 0) return 1; if (!matched) return 0; pciFreeDevice(conn, matched); return 1; } /* Is @a the parent of @b ? */ static int pciIsParent(pciDevice *a, pciDevice *b) { uint16_t device_class; uint8_t header_type, secondary, subordinate; if (a->domain != b->domain) return 0; /* Is it a bridge? */ device_class = pciRead16(a, PCI_CLASS_DEVICE); if (device_class != PCI_CLASS_BRIDGE_PCI) return 0; /* Is it a plane? */ header_type = pciRead8(a, PCI_HEADER_TYPE); if ((header_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) return 0; secondary = pciRead8(a, PCI_SECONDARY_BUS); subordinate = pciRead8(a, PCI_SUBORDINATE_BUS); VIR_DEBUG("%s %s: found parent device %s\n", b->id, b->name, a->name); /* No, it's superman! */ return (b->bus >= secondary && b->bus <= subordinate); } static pciDevice * pciGetParentDevice(virConnectPtr conn, pciDevice *dev) { pciDevice *parent = NULL; pciIterDevices(conn, pciIsParent, dev, &parent); return parent; } /* Secondary Bus Reset is our sledgehammer - it resets all * devices behind a bus. */ static int pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) { pciDevice *parent; uint8_t config_space[PCI_CONF_LEN]; uint16_t ctl; int ret = -1; /* For now, we just refuse to do a secondary bus reset * if there are other devices/functions behind the bus. * In future, we could allow it so long as those devices * are not in use by the host or other guests. */ if (pciBusContainsOtherDevices(conn, dev)) { VIR_WARN("Other devices on bus with %s, not doing bus reset", dev->name); return -1; } /* Find the parent bus */ parent = pciGetParentDevice(conn, dev); if (!parent) { VIR_WARN("Failed to find parent device for %s", dev->name); return -1; } VIR_DEBUG("%s %s: doing a secondary bus reset", dev->id, dev->name); /* Save and restore the device's config space; we only do this * for the supplied device since we refuse to do a reset if there * are multiple devices/functions */ if (pciRead(dev, 0, config_space, PCI_CONF_LEN) < 0) { VIR_WARN("Failed to save PCI config space for %s", dev->name); goto out; } /* Read the control register, set the reset flag, wait 200ms, * unset the reset flag and wait 200ms. */ ctl = pciRead16(dev, PCI_BRIDGE_CONTROL); pciWrite16(parent, PCI_BRIDGE_CONTROL, ctl | PCI_BRIDGE_CTL_RESET); usleep(200 * 1000); /* sleep 200ms */ pciWrite16(parent, PCI_BRIDGE_CONTROL, ctl); usleep(200 * 1000); /* sleep 200ms */ if (pciWrite(dev, 0, config_space, PCI_CONF_LEN) < 0) VIR_WARN("Failed to restore PCI config space for %s", dev->name); ret = 0; out: pciFreeDevice(conn, parent); return ret; } /* Power management reset attempts to reset a device using a * D-state transition from D3hot to D0. Note, in detect_pm_reset() * above we require the device supports a full internal reset. */ static int pciTryPowerManagementReset(virConnectPtr conn, pciDevice *dev) { uint8_t config_space[PCI_CONF_LEN]; uint32_t ctl; if (!dev->pci_pm_cap_pos) return -1; /* For now, we just refuse to do a power management reset * if there are other functions on this device. * In future, we could allow it so long as those functions * are not in use by the host or other guests. */ if (pciDeviceContainsOtherFunctions(conn, dev)) { VIR_WARN("%s contains other functions, not resetting", dev->name); return -1; } /* Save and restore the device's config space. */ if (pciRead(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) { VIR_WARN("Failed to save PCI config space for %s", dev->name); return -1; } VIR_DEBUG("%s %s: doing a power management reset", dev->id, dev->name); ctl = pciRead32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL); ctl &= ~PCI_PM_CTRL_STATE_MASK; pciWrite32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL, ctl|PCI_PM_CTRL_STATE_D3hot); usleep(10 * 1000); /* sleep 10ms */ pciWrite32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL, ctl|PCI_PM_CTRL_STATE_D0); usleep(10 * 1000); /* sleep 10ms */ if (pciWrite(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) VIR_WARN("Failed to restore PCI config space for %s", dev->name); return 0; } static int pciInitDevice(virConnectPtr conn, pciDevice *dev) { if (pciOpenConfig(dev) < 0) { virReportSystemError(conn, errno, _("Failed to open config space file '%s'"), dev->path); return -1; } dev->pcie_cap_pos = pciFindCapabilityOffset(dev, PCI_CAP_ID_EXP); dev->pci_pm_cap_pos = pciFindCapabilityOffset(dev, PCI_CAP_ID_PM); dev->has_flr = pciDetectFunctionLevelReset(dev); dev->has_pm_reset = pciDetectPowerManagementReset(dev); dev->initted = 1; return 0; } int pciResetDevice(virConnectPtr conn, pciDevice *dev) { int ret = -1; if (!dev->initted && pciInitDevice(conn, dev) < 0) return -1; /* KVM will perform FLR when starting and stopping * a guest, so there is no need for us to do it here. */ if (dev->has_flr) return 0; /* Bus reset is not an option with the root bus */ if (dev->bus != 0) ret = pciTrySecondaryBusReset(conn, dev); /* Next best option is a PCI power management reset */ if (ret < 0 && dev->has_pm_reset) ret = pciTryPowerManagementReset(conn, dev); if (ret < 0) pciReportError(conn, VIR_ERR_NO_SUPPORT, _("No PCI reset capability available for %s"), dev->name); return ret; } static void pciDriverDir(char *buf, size_t buflen, const char *driver) { snprintf(buf, buflen, PCI_SYSFS "drivers/%s", driver); } static void pciDriverFile(char *buf, size_t buflen, const char *driver, const char *file) { snprintf(buf, buflen, PCI_SYSFS "drivers/%s/%s", driver, file); } static void pciDeviceFile(char *buf, size_t buflen, const char *device, const char *file) { snprintf(buf, buflen, PCI_SYSFS "devices/%s/%s", device, file); } static const char * pciFindStubDriver(virConnectPtr conn) { char drvpath[PATH_MAX]; int probed = 0; recheck: pciDriverDir(drvpath, sizeof(drvpath), "pci-stub"); if (virFileExists(drvpath)) return "pci-stub"; pciDriverDir(drvpath, sizeof(drvpath), "pciback"); if (virFileExists(drvpath)) return "pciback"; if (!probed) { const char *const stubprobe[] = { MODPROBE, "pci-stub", NULL }; const char *const backprobe[] = { MODPROBE, "pciback", NULL }; probed = 1; /* * Probing for pci-stub will succeed regardless of whether * on native or Xen kernels. * On Xen though, we want to prefer pciback, so probe * for that first, because that will only work on Xen */ if (virRun(conn, backprobe, NULL) < 0 && virRun(conn, stubprobe, NULL) < 0) { char ebuf[1024]; VIR_WARN(_("failed to load pci-stub or pciback drivers: %s"), virStrerror(errno, ebuf, sizeof ebuf)); return 0; } goto recheck; } return NULL; } static int pciBindDeviceToStub(virConnectPtr conn, pciDevice *dev, const char *driver) { char drvdir[PATH_MAX]; char path[PATH_MAX]; /* Add the PCI device ID to the stub's dynamic ID table; * this is needed to allow us to bind the device to the stub. * Note: if the device is not currently bound to any driver, * stub will immediately be bound to the device. Also, note * that if a new device with this ID is hotplugged, or if a probe * is triggered for such a device, it will also be immediately * bound by the stub. */ pciDriverFile(path, sizeof(path), driver, "new_id"); if (virFileWriteStr(path, dev->id) < 0) { virReportSystemError(conn, errno, _("Failed to add PCI device ID '%s' to %s"), dev->id, driver); return -1; } /* If the device is already bound to a driver, unbind it. * Note, this will have rather unpleasant side effects if this * PCI device happens to be IDE controller for the disk hosting * your root filesystem. */ pciDeviceFile(path, sizeof(path), dev->name, "driver/unbind"); if (virFileExists(path) && virFileWriteStr(path, dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to unbind PCI device '%s'"), dev->name); return -1; } /* If the device isn't already bound to pci-stub, try binding it now. */ pciDriverDir(drvdir, sizeof(drvdir), driver); pciDeviceFile(path, sizeof(path), dev->name, "driver"); if (!virFileLinkPointsTo(path, drvdir)) { /* Xen's pciback.ko wants you to use new_slot first */ pciDriverFile(path, sizeof(path), driver, "new_slot"); if (virFileExists(path) && virFileWriteStr(path, dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to add slot for PCI device '%s' to %s"), dev->name, driver); return -1; } pciDriverFile(path, sizeof(path), driver, "bind"); if (virFileWriteStr(path, dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to bind PCI device '%s' to %s"), dev->name, driver); return -1; } } /* If 'remove_id' exists, remove the device id from pci-stub's dynamic * ID table so that 'drivers_probe' works below. */ pciDriverFile(path, sizeof(path), driver, "remove_id"); if (virFileExists(path) && virFileWriteStr(path, dev->id) < 0) { virReportSystemError(conn, errno, _("Failed to remove PCI ID '%s' from %s"), dev->id, driver); return -1; } return 0; } int pciDettachDevice(virConnectPtr conn, pciDevice *dev) { const char *driver = pciFindStubDriver(conn); if (!driver) { pciReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("cannot find any PCI stub module")); return -1; } return pciBindDeviceToStub(conn, dev, driver); } static int pciUnBindDeviceFromStub(virConnectPtr conn, pciDevice *dev, const char *driver) { char drvdir[PATH_MAX]; char path[PATH_MAX]; /* If the device is bound to stub, unbind it. */ pciDriverDir(drvdir, sizeof(drvdir), driver); pciDeviceFile(path, sizeof(path), dev->name, "driver"); if (virFileExists(drvdir) && virFileLinkPointsTo(path, drvdir)) { pciDriverFile(path, sizeof(path), driver, "unbind"); if (virFileWriteStr(path, dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to bind PCI device '%s' to %s"), dev->name, driver); return -1; } } /* Xen's pciback.ko wants you to use remove_slot on the specific device */ pciDriverFile(path, sizeof(path), driver, "remove_slot"); if (virFileExists(path) && virFileWriteStr(path, dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to remove slot for PCI device '%s' to %s"), dev->name, driver); return -1; } /* Trigger a re-probe of the device is not in the stub's dynamic * ID table. If the stub is available, but 'remove_id' isn't * available, then re-probing would just cause the device to be * re-bound to the stub. */ pciDriverFile(path, sizeof(path), driver, "remove_id"); if (!virFileExists(drvdir) || virFileExists(path)) { if (virFileWriteStr(PCI_SYSFS "drivers_probe", dev->name) < 0) { virReportSystemError(conn, errno, _("Failed to trigger a re-probe for PCI device '%s'"), dev->name); return -1; } } return 0; } int pciReAttachDevice(virConnectPtr conn, pciDevice *dev) { const char *driver = pciFindStubDriver(conn); if (!driver) { pciReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("cannot find any PCI stub module")); return -1; } return pciUnBindDeviceFromStub(conn, dev, driver); } static char * pciReadDeviceID(pciDevice *dev, const char *id_name) { char path[PATH_MAX]; char *id_str; snprintf(path, sizeof(path), PCI_SYSFS "devices/%s/%s", dev->name, id_name); /* ID string is '0xNNNN\n' ... i.e. 7 bytes */ if (virFileReadAll(path, 7, &id_str) < 0) return NULL; /* Check for 0x suffix */ if (id_str[0] != '0' || id_str[1] != 'x') { VIR_FREE(id_str); return NULL; } /* Chop off the newline; we know the string is 7 bytes */ id_str[6] = '\0'; return id_str; } pciDevice * pciGetDevice(virConnectPtr conn, unsigned domain, unsigned bus, unsigned slot, unsigned function) { pciDevice *dev; char *vendor, *product; if (VIR_ALLOC(dev) < 0) { virReportOOMError(conn); return NULL; } dev->fd = -1; dev->domain = domain; dev->bus = bus; dev->slot = slot; dev->function = function; snprintf(dev->name, sizeof(dev->name), "%.4x:%.2x:%.2x.%.1x", dev->domain, dev->bus, dev->slot, dev->function); snprintf(dev->path, sizeof(dev->path), PCI_SYSFS "devices/%s/config", dev->name); vendor = pciReadDeviceID(dev, "vendor"); product = pciReadDeviceID(dev, "device"); if (!vendor || !product) { pciReportError(conn, VIR_ERR_NO_SUPPORT, _("Failed to read product/vendor ID for %s"), dev->name); VIR_FREE(product); VIR_FREE(vendor); pciFreeDevice(conn, dev); return NULL; } /* strings contain '0x' prefix */ snprintf(dev->id, sizeof(dev->id), "%s %s", &vendor[2], &product[2]); VIR_FREE(product); VIR_FREE(vendor); VIR_DEBUG("%s %s: initialized", dev->id, dev->name); return dev; } void pciFreeDevice(virConnectPtr conn ATTRIBUTE_UNUSED, pciDevice *dev) { VIR_DEBUG("%s %s: freeing", dev->id, dev->name); if (dev->fd >= 0) close(dev->fd); VIR_FREE(dev); }