feb9a2ab4b
When a guest enables MSIX on a device we evaluate the MSIX vector table, typically find no unmasked vectors and don't switch the device to MSIX mode. This generally works fine and the device will be switched once the guest enables and therefore unmasks a vector. Unfortunately some drivers enable MSIX, then use interfaces to send commands between VF & PF or PF & firmware that act based on the host state of the device. These therefore may break when MSIX is managed lazily. This change re-enables the previous test used to enable MSIX (see qemu-kvm a6b402c9), which basically guesses whether a vector will be used based on the data field of the vector table. Cc: qemu-stable@nongnu.org Signed-off-by: Alex Williamson <alex.williamson@redhat.com> Acked-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
1925 lines
63 KiB
C
1925 lines
63 KiB
C
/*
|
|
* Copyright (c) 2007, Neocleus Corporation.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
* the COPYING file in the top-level directory.
|
|
*
|
|
*
|
|
* Assign a PCI device from the host to a guest VM.
|
|
*
|
|
* This implementation uses the classic device assignment interface of KVM
|
|
* and is only available on x86 hosts. It is expected to be obsoleted by VFIO
|
|
* based device assignment.
|
|
*
|
|
* Adapted for KVM (qemu-kvm) by Qumranet. QEMU version was based on qemu-kvm
|
|
* revision 4144fe9d48. See its repository for the history.
|
|
*
|
|
* Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com)
|
|
* Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com)
|
|
* Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com)
|
|
* Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com)
|
|
* Copyright (C) 2008, IBM, Muli Ben-Yehuda (muli@il.ibm.com)
|
|
*/
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <sys/io.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include "hw/hw.h"
|
|
#include "hw/pc.h"
|
|
#include "qemu/error-report.h"
|
|
#include "ui/console.h"
|
|
#include "hw/loader.h"
|
|
#include "monitor/monitor.h"
|
|
#include "qemu/range.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/pci/msi.h"
|
|
#include "kvm_i386.h"
|
|
|
|
#define MSIX_PAGE_SIZE 0x1000
|
|
|
|
/* From linux/ioport.h */
|
|
#define IORESOURCE_IO 0x00000100 /* Resource type */
|
|
#define IORESOURCE_MEM 0x00000200
|
|
#define IORESOURCE_IRQ 0x00000400
|
|
#define IORESOURCE_DMA 0x00000800
|
|
#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
|
|
#define IORESOURCE_MEM_64 0x00100000
|
|
|
|
//#define DEVICE_ASSIGNMENT_DEBUG
|
|
|
|
#ifdef DEVICE_ASSIGNMENT_DEBUG
|
|
#define DEBUG(fmt, ...) \
|
|
do { \
|
|
fprintf(stderr, "%s: " fmt, __func__ , __VA_ARGS__); \
|
|
} while (0)
|
|
#else
|
|
#define DEBUG(fmt, ...)
|
|
#endif
|
|
|
|
typedef struct PCIRegion {
|
|
int type; /* Memory or port I/O */
|
|
int valid;
|
|
uint64_t base_addr;
|
|
uint64_t size; /* size of the region */
|
|
int resource_fd;
|
|
} PCIRegion;
|
|
|
|
typedef struct PCIDevRegions {
|
|
uint8_t bus, dev, func; /* Bus inside domain, device and function */
|
|
int irq; /* IRQ number */
|
|
uint16_t region_number; /* number of active regions */
|
|
|
|
/* Port I/O or MMIO Regions */
|
|
PCIRegion regions[PCI_NUM_REGIONS - 1];
|
|
int config_fd;
|
|
} PCIDevRegions;
|
|
|
|
typedef struct AssignedDevRegion {
|
|
MemoryRegion container;
|
|
MemoryRegion real_iomem;
|
|
union {
|
|
uint8_t *r_virtbase; /* mmapped access address for memory regions */
|
|
uint32_t r_baseport; /* the base guest port for I/O regions */
|
|
} u;
|
|
pcibus_t e_size; /* emulated size of region in bytes */
|
|
pcibus_t r_size; /* real size of region in bytes */
|
|
PCIRegion *region;
|
|
} AssignedDevRegion;
|
|
|
|
#define ASSIGNED_DEVICE_PREFER_MSI_BIT 0
|
|
#define ASSIGNED_DEVICE_SHARE_INTX_BIT 1
|
|
|
|
#define ASSIGNED_DEVICE_PREFER_MSI_MASK (1 << ASSIGNED_DEVICE_PREFER_MSI_BIT)
|
|
#define ASSIGNED_DEVICE_SHARE_INTX_MASK (1 << ASSIGNED_DEVICE_SHARE_INTX_BIT)
|
|
|
|
typedef struct MSIXTableEntry {
|
|
uint32_t addr_lo;
|
|
uint32_t addr_hi;
|
|
uint32_t data;
|
|
uint32_t ctrl;
|
|
} MSIXTableEntry;
|
|
|
|
typedef enum AssignedIRQType {
|
|
ASSIGNED_IRQ_NONE = 0,
|
|
ASSIGNED_IRQ_INTX_HOST_INTX,
|
|
ASSIGNED_IRQ_INTX_HOST_MSI,
|
|
ASSIGNED_IRQ_MSI,
|
|
ASSIGNED_IRQ_MSIX
|
|
} AssignedIRQType;
|
|
|
|
typedef struct AssignedDevice {
|
|
PCIDevice dev;
|
|
PCIHostDeviceAddress host;
|
|
uint32_t dev_id;
|
|
uint32_t features;
|
|
int intpin;
|
|
AssignedDevRegion v_addrs[PCI_NUM_REGIONS - 1];
|
|
PCIDevRegions real_device;
|
|
PCIINTxRoute intx_route;
|
|
AssignedIRQType assigned_irq_type;
|
|
struct {
|
|
#define ASSIGNED_DEVICE_CAP_MSI (1 << 0)
|
|
#define ASSIGNED_DEVICE_CAP_MSIX (1 << 1)
|
|
uint32_t available;
|
|
#define ASSIGNED_DEVICE_MSI_ENABLED (1 << 0)
|
|
#define ASSIGNED_DEVICE_MSIX_ENABLED (1 << 1)
|
|
#define ASSIGNED_DEVICE_MSIX_MASKED (1 << 2)
|
|
uint32_t state;
|
|
} cap;
|
|
uint8_t emulate_config_read[PCI_CONFIG_SPACE_SIZE];
|
|
uint8_t emulate_config_write[PCI_CONFIG_SPACE_SIZE];
|
|
int msi_virq_nr;
|
|
int *msi_virq;
|
|
MSIXTableEntry *msix_table;
|
|
hwaddr msix_table_addr;
|
|
uint16_t msix_max;
|
|
MemoryRegion mmio;
|
|
char *configfd_name;
|
|
int32_t bootindex;
|
|
} AssignedDevice;
|
|
|
|
static void assigned_dev_update_irq_routing(PCIDevice *dev);
|
|
|
|
static void assigned_dev_load_option_rom(AssignedDevice *dev);
|
|
|
|
static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev);
|
|
|
|
static uint64_t assigned_dev_ioport_rw(AssignedDevRegion *dev_region,
|
|
hwaddr addr, int size,
|
|
uint64_t *data)
|
|
{
|
|
uint64_t val = 0;
|
|
int fd = dev_region->region->resource_fd;
|
|
|
|
if (fd >= 0) {
|
|
if (data) {
|
|
DEBUG("pwrite data=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
|
|
", addr="TARGET_FMT_plx"\n", *data, size, addr, addr);
|
|
if (pwrite(fd, data, size, addr) != size) {
|
|
error_report("%s - pwrite failed %s",
|
|
__func__, strerror(errno));
|
|
}
|
|
} else {
|
|
if (pread(fd, &val, size, addr) != size) {
|
|
error_report("%s - pread failed %s",
|
|
__func__, strerror(errno));
|
|
val = (1UL << (size * 8)) - 1;
|
|
}
|
|
DEBUG("pread val=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
|
|
", addr=" TARGET_FMT_plx "\n", val, size, addr, addr);
|
|
}
|
|
} else {
|
|
uint32_t port = addr + dev_region->u.r_baseport;
|
|
|
|
if (data) {
|
|
DEBUG("out data=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
|
|
", host=%x\n", *data, size, addr, port);
|
|
switch (size) {
|
|
case 1:
|
|
outb(*data, port);
|
|
break;
|
|
case 2:
|
|
outw(*data, port);
|
|
break;
|
|
case 4:
|
|
outl(*data, port);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (size) {
|
|
case 1:
|
|
val = inb(port);
|
|
break;
|
|
case 2:
|
|
val = inw(port);
|
|
break;
|
|
case 4:
|
|
val = inl(port);
|
|
break;
|
|
}
|
|
DEBUG("in data=%" PRIx64 ", size=%d, e_phys=" TARGET_FMT_plx
|
|
", host=%x\n", val, size, addr, port);
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void assigned_dev_ioport_write(void *opaque, hwaddr addr,
|
|
uint64_t data, unsigned size)
|
|
{
|
|
assigned_dev_ioport_rw(opaque, addr, size, &data);
|
|
}
|
|
|
|
static uint64_t assigned_dev_ioport_read(void *opaque,
|
|
hwaddr addr, unsigned size)
|
|
{
|
|
return assigned_dev_ioport_rw(opaque, addr, size, NULL);
|
|
}
|
|
|
|
static uint32_t slow_bar_readb(void *opaque, hwaddr addr)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint8_t *in = d->u.r_virtbase + addr;
|
|
uint32_t r;
|
|
|
|
r = *in;
|
|
DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static uint32_t slow_bar_readw(void *opaque, hwaddr addr)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint16_t *in = (uint16_t *)(d->u.r_virtbase + addr);
|
|
uint32_t r;
|
|
|
|
r = *in;
|
|
DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static uint32_t slow_bar_readl(void *opaque, hwaddr addr)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint32_t *in = (uint32_t *)(d->u.r_virtbase + addr);
|
|
uint32_t r;
|
|
|
|
r = *in;
|
|
DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void slow_bar_writeb(void *opaque, hwaddr addr, uint32_t val)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint8_t *out = d->u.r_virtbase + addr;
|
|
|
|
DEBUG("slow_bar_writeb addr=0x" TARGET_FMT_plx " val=0x%02x\n", addr, val);
|
|
*out = val;
|
|
}
|
|
|
|
static void slow_bar_writew(void *opaque, hwaddr addr, uint32_t val)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint16_t *out = (uint16_t *)(d->u.r_virtbase + addr);
|
|
|
|
DEBUG("slow_bar_writew addr=0x" TARGET_FMT_plx " val=0x%04x\n", addr, val);
|
|
*out = val;
|
|
}
|
|
|
|
static void slow_bar_writel(void *opaque, hwaddr addr, uint32_t val)
|
|
{
|
|
AssignedDevRegion *d = opaque;
|
|
uint32_t *out = (uint32_t *)(d->u.r_virtbase + addr);
|
|
|
|
DEBUG("slow_bar_writel addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, val);
|
|
*out = val;
|
|
}
|
|
|
|
static const MemoryRegionOps slow_bar_ops = {
|
|
.old_mmio = {
|
|
.read = { slow_bar_readb, slow_bar_readw, slow_bar_readl, },
|
|
.write = { slow_bar_writeb, slow_bar_writew, slow_bar_writel, },
|
|
},
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
static void assigned_dev_iomem_setup(PCIDevice *pci_dev, int region_num,
|
|
pcibus_t e_size)
|
|
{
|
|
AssignedDevice *r_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
AssignedDevRegion *region = &r_dev->v_addrs[region_num];
|
|
PCIRegion *real_region = &r_dev->real_device.regions[region_num];
|
|
|
|
if (e_size > 0) {
|
|
memory_region_init(®ion->container, "assigned-dev-container",
|
|
e_size);
|
|
memory_region_add_subregion(®ion->container, 0, ®ion->real_iomem);
|
|
|
|
/* deal with MSI-X MMIO page */
|
|
if (real_region->base_addr <= r_dev->msix_table_addr &&
|
|
real_region->base_addr + real_region->size >
|
|
r_dev->msix_table_addr) {
|
|
uint64_t offset = r_dev->msix_table_addr - real_region->base_addr;
|
|
|
|
memory_region_add_subregion_overlap(®ion->container,
|
|
offset,
|
|
&r_dev->mmio,
|
|
1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps assigned_dev_ioport_ops = {
|
|
.read = assigned_dev_ioport_read,
|
|
.write = assigned_dev_ioport_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
static void assigned_dev_ioport_setup(PCIDevice *pci_dev, int region_num,
|
|
pcibus_t size)
|
|
{
|
|
AssignedDevice *r_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
AssignedDevRegion *region = &r_dev->v_addrs[region_num];
|
|
|
|
region->e_size = size;
|
|
memory_region_init(®ion->container, "assigned-dev-container", size);
|
|
memory_region_init_io(®ion->real_iomem, &assigned_dev_ioport_ops,
|
|
r_dev->v_addrs + region_num,
|
|
"assigned-dev-iomem", size);
|
|
memory_region_add_subregion(®ion->container, 0, ®ion->real_iomem);
|
|
}
|
|
|
|
static uint32_t assigned_dev_pci_read(PCIDevice *d, int pos, int len)
|
|
{
|
|
AssignedDevice *pci_dev = DO_UPCAST(AssignedDevice, dev, d);
|
|
uint32_t val;
|
|
ssize_t ret;
|
|
int fd = pci_dev->real_device.config_fd;
|
|
|
|
again:
|
|
ret = pread(fd, &val, len, pos);
|
|
if (ret != len) {
|
|
if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) {
|
|
goto again;
|
|
}
|
|
|
|
hw_error("pci read failed, ret = %zd errno = %d\n", ret, errno);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static uint8_t assigned_dev_pci_read_byte(PCIDevice *d, int pos)
|
|
{
|
|
return (uint8_t)assigned_dev_pci_read(d, pos, 1);
|
|
}
|
|
|
|
static void assigned_dev_pci_write(PCIDevice *d, int pos, uint32_t val, int len)
|
|
{
|
|
AssignedDevice *pci_dev = DO_UPCAST(AssignedDevice, dev, d);
|
|
ssize_t ret;
|
|
int fd = pci_dev->real_device.config_fd;
|
|
|
|
again:
|
|
ret = pwrite(fd, &val, len, pos);
|
|
if (ret != len) {
|
|
if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) {
|
|
goto again;
|
|
}
|
|
|
|
hw_error("pci write failed, ret = %zd errno = %d\n", ret, errno);
|
|
}
|
|
}
|
|
|
|
static void assigned_dev_emulate_config_read(AssignedDevice *dev,
|
|
uint32_t offset, uint32_t len)
|
|
{
|
|
memset(dev->emulate_config_read + offset, 0xff, len);
|
|
}
|
|
|
|
static void assigned_dev_direct_config_read(AssignedDevice *dev,
|
|
uint32_t offset, uint32_t len)
|
|
{
|
|
memset(dev->emulate_config_read + offset, 0, len);
|
|
}
|
|
|
|
static void assigned_dev_direct_config_write(AssignedDevice *dev,
|
|
uint32_t offset, uint32_t len)
|
|
{
|
|
memset(dev->emulate_config_write + offset, 0, len);
|
|
}
|
|
|
|
static uint8_t pci_find_cap_offset(PCIDevice *d, uint8_t cap, uint8_t start)
|
|
{
|
|
int id;
|
|
int max_cap = 48;
|
|
int pos = start ? start : PCI_CAPABILITY_LIST;
|
|
int status;
|
|
|
|
status = assigned_dev_pci_read_byte(d, PCI_STATUS);
|
|
if ((status & PCI_STATUS_CAP_LIST) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
while (max_cap--) {
|
|
pos = assigned_dev_pci_read_byte(d, pos);
|
|
if (pos < 0x40) {
|
|
break;
|
|
}
|
|
|
|
pos &= ~3;
|
|
id = assigned_dev_pci_read_byte(d, pos + PCI_CAP_LIST_ID);
|
|
|
|
if (id == 0xff) {
|
|
break;
|
|
}
|
|
if (id == cap) {
|
|
return pos;
|
|
}
|
|
|
|
pos += PCI_CAP_LIST_NEXT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int assigned_dev_register_regions(PCIRegion *io_regions,
|
|
unsigned long regions_num,
|
|
AssignedDevice *pci_dev)
|
|
{
|
|
uint32_t i;
|
|
PCIRegion *cur_region = io_regions;
|
|
|
|
for (i = 0; i < regions_num; i++, cur_region++) {
|
|
if (!cur_region->valid) {
|
|
continue;
|
|
}
|
|
|
|
/* handle memory io regions */
|
|
if (cur_region->type & IORESOURCE_MEM) {
|
|
int t = PCI_BASE_ADDRESS_SPACE_MEMORY;
|
|
if (cur_region->type & IORESOURCE_PREFETCH) {
|
|
t |= PCI_BASE_ADDRESS_MEM_PREFETCH;
|
|
}
|
|
if (cur_region->type & IORESOURCE_MEM_64) {
|
|
t |= PCI_BASE_ADDRESS_MEM_TYPE_64;
|
|
}
|
|
|
|
/* map physical memory */
|
|
pci_dev->v_addrs[i].u.r_virtbase = mmap(NULL, cur_region->size,
|
|
PROT_WRITE | PROT_READ,
|
|
MAP_SHARED,
|
|
cur_region->resource_fd,
|
|
(off_t)0);
|
|
|
|
if (pci_dev->v_addrs[i].u.r_virtbase == MAP_FAILED) {
|
|
pci_dev->v_addrs[i].u.r_virtbase = NULL;
|
|
error_report("%s: Error: Couldn't mmap 0x%" PRIx64 "!",
|
|
__func__, cur_region->base_addr);
|
|
return -1;
|
|
}
|
|
|
|
pci_dev->v_addrs[i].r_size = cur_region->size;
|
|
pci_dev->v_addrs[i].e_size = 0;
|
|
|
|
/* add offset */
|
|
pci_dev->v_addrs[i].u.r_virtbase +=
|
|
(cur_region->base_addr & 0xFFF);
|
|
|
|
if (cur_region->size & 0xFFF) {
|
|
error_report("PCI region %d at address 0x%" PRIx64 " has "
|
|
"size 0x%" PRIx64 ", which is not a multiple of "
|
|
"4K. You might experience some performance hit "
|
|
"due to that.",
|
|
i, cur_region->base_addr, cur_region->size);
|
|
memory_region_init_io(&pci_dev->v_addrs[i].real_iomem,
|
|
&slow_bar_ops, &pci_dev->v_addrs[i],
|
|
"assigned-dev-slow-bar",
|
|
cur_region->size);
|
|
} else {
|
|
void *virtbase = pci_dev->v_addrs[i].u.r_virtbase;
|
|
char name[32];
|
|
snprintf(name, sizeof(name), "%s.bar%d",
|
|
object_get_typename(OBJECT(pci_dev)), i);
|
|
memory_region_init_ram_ptr(&pci_dev->v_addrs[i].real_iomem,
|
|
name, cur_region->size,
|
|
virtbase);
|
|
vmstate_register_ram(&pci_dev->v_addrs[i].real_iomem,
|
|
&pci_dev->dev.qdev);
|
|
}
|
|
|
|
assigned_dev_iomem_setup(&pci_dev->dev, i, cur_region->size);
|
|
pci_register_bar((PCIDevice *) pci_dev, i, t,
|
|
&pci_dev->v_addrs[i].container);
|
|
continue;
|
|
} else {
|
|
/* handle port io regions */
|
|
uint32_t val;
|
|
int ret;
|
|
|
|
/* Test kernel support for ioport resource read/write. Old
|
|
* kernels return EIO. New kernels only allow 1/2/4 byte reads
|
|
* so should return EINVAL for a 3 byte read */
|
|
ret = pread(pci_dev->v_addrs[i].region->resource_fd, &val, 3, 0);
|
|
if (ret >= 0) {
|
|
error_report("Unexpected return from I/O port read: %d", ret);
|
|
abort();
|
|
} else if (errno != EINVAL) {
|
|
error_report("Kernel doesn't support ioport resource "
|
|
"access, hiding this region.");
|
|
close(pci_dev->v_addrs[i].region->resource_fd);
|
|
cur_region->valid = 0;
|
|
continue;
|
|
}
|
|
|
|
pci_dev->v_addrs[i].u.r_baseport = cur_region->base_addr;
|
|
pci_dev->v_addrs[i].r_size = cur_region->size;
|
|
pci_dev->v_addrs[i].e_size = 0;
|
|
|
|
assigned_dev_ioport_setup(&pci_dev->dev, i, cur_region->size);
|
|
pci_register_bar((PCIDevice *) pci_dev, i,
|
|
PCI_BASE_ADDRESS_SPACE_IO,
|
|
&pci_dev->v_addrs[i].container);
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return 0;
|
|
}
|
|
|
|
static int get_real_id(const char *devpath, const char *idname, uint16_t *val)
|
|
{
|
|
FILE *f;
|
|
char name[128];
|
|
long id;
|
|
|
|
snprintf(name, sizeof(name), "%s%s", devpath, idname);
|
|
f = fopen(name, "r");
|
|
if (f == NULL) {
|
|
error_report("%s: %s: %m", __func__, name);
|
|
return -1;
|
|
}
|
|
if (fscanf(f, "%li\n", &id) == 1) {
|
|
*val = id;
|
|
} else {
|
|
return -1;
|
|
}
|
|
fclose(f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_real_vendor_id(const char *devpath, uint16_t *val)
|
|
{
|
|
return get_real_id(devpath, "vendor", val);
|
|
}
|
|
|
|
static int get_real_device_id(const char *devpath, uint16_t *val)
|
|
{
|
|
return get_real_id(devpath, "device", val);
|
|
}
|
|
|
|
static int get_real_device(AssignedDevice *pci_dev, uint16_t r_seg,
|
|
uint8_t r_bus, uint8_t r_dev, uint8_t r_func)
|
|
{
|
|
char dir[128], name[128];
|
|
int fd, r = 0, v;
|
|
FILE *f;
|
|
uint64_t start, end, size, flags;
|
|
uint16_t id;
|
|
PCIRegion *rp;
|
|
PCIDevRegions *dev = &pci_dev->real_device;
|
|
|
|
dev->region_number = 0;
|
|
|
|
snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%x/",
|
|
r_seg, r_bus, r_dev, r_func);
|
|
|
|
snprintf(name, sizeof(name), "%sconfig", dir);
|
|
|
|
if (pci_dev->configfd_name && *pci_dev->configfd_name) {
|
|
dev->config_fd = monitor_handle_fd_param(cur_mon, pci_dev->configfd_name);
|
|
if (dev->config_fd < 0) {
|
|
return 1;
|
|
}
|
|
} else {
|
|
dev->config_fd = open(name, O_RDWR);
|
|
|
|
if (dev->config_fd == -1) {
|
|
error_report("%s: %s: %m", __func__, name);
|
|
return 1;
|
|
}
|
|
}
|
|
again:
|
|
r = read(dev->config_fd, pci_dev->dev.config,
|
|
pci_config_size(&pci_dev->dev));
|
|
if (r < 0) {
|
|
if (errno == EINTR || errno == EAGAIN) {
|
|
goto again;
|
|
}
|
|
error_report("%s: read failed, errno = %d", __func__, errno);
|
|
}
|
|
|
|
/* Restore or clear multifunction, this is always controlled by qemu */
|
|
if (pci_dev->dev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
|
|
pci_dev->dev.config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
|
|
} else {
|
|
pci_dev->dev.config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION;
|
|
}
|
|
|
|
/* Clear host resource mapping info. If we choose not to register a
|
|
* BAR, such as might be the case with the option ROM, we can get
|
|
* confusing, unwritable, residual addresses from the host here. */
|
|
memset(&pci_dev->dev.config[PCI_BASE_ADDRESS_0], 0, 24);
|
|
memset(&pci_dev->dev.config[PCI_ROM_ADDRESS], 0, 4);
|
|
|
|
snprintf(name, sizeof(name), "%sresource", dir);
|
|
|
|
f = fopen(name, "r");
|
|
if (f == NULL) {
|
|
error_report("%s: %s: %m", __func__, name);
|
|
return 1;
|
|
}
|
|
|
|
for (r = 0; r < PCI_ROM_SLOT; r++) {
|
|
if (fscanf(f, "%" SCNi64 " %" SCNi64 " %" SCNi64 "\n",
|
|
&start, &end, &flags) != 3) {
|
|
break;
|
|
}
|
|
|
|
rp = dev->regions + r;
|
|
rp->valid = 0;
|
|
rp->resource_fd = -1;
|
|
size = end - start + 1;
|
|
flags &= IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH
|
|
| IORESOURCE_MEM_64;
|
|
if (size == 0 || (flags & ~IORESOURCE_PREFETCH) == 0) {
|
|
continue;
|
|
}
|
|
if (flags & IORESOURCE_MEM) {
|
|
flags &= ~IORESOURCE_IO;
|
|
} else {
|
|
flags &= ~IORESOURCE_PREFETCH;
|
|
}
|
|
snprintf(name, sizeof(name), "%sresource%d", dir, r);
|
|
fd = open(name, O_RDWR);
|
|
if (fd == -1) {
|
|
continue;
|
|
}
|
|
rp->resource_fd = fd;
|
|
|
|
rp->type = flags;
|
|
rp->valid = 1;
|
|
rp->base_addr = start;
|
|
rp->size = size;
|
|
pci_dev->v_addrs[r].region = rp;
|
|
DEBUG("region %d size %" PRIu64 " start 0x%" PRIx64
|
|
" type %d resource_fd %d\n",
|
|
r, rp->size, start, rp->type, rp->resource_fd);
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
/* read and fill vendor ID */
|
|
v = get_real_vendor_id(dir, &id);
|
|
if (v) {
|
|
return 1;
|
|
}
|
|
pci_dev->dev.config[0] = id & 0xff;
|
|
pci_dev->dev.config[1] = (id & 0xff00) >> 8;
|
|
|
|
/* read and fill device ID */
|
|
v = get_real_device_id(dir, &id);
|
|
if (v) {
|
|
return 1;
|
|
}
|
|
pci_dev->dev.config[2] = id & 0xff;
|
|
pci_dev->dev.config[3] = (id & 0xff00) >> 8;
|
|
|
|
pci_word_test_and_clear_mask(pci_dev->emulate_config_write + PCI_COMMAND,
|
|
PCI_COMMAND_MASTER | PCI_COMMAND_INTX_DISABLE);
|
|
|
|
dev->region_number = r;
|
|
return 0;
|
|
}
|
|
|
|
static void free_msi_virqs(AssignedDevice *dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < dev->msi_virq_nr; i++) {
|
|
if (dev->msi_virq[i] >= 0) {
|
|
kvm_irqchip_release_virq(kvm_state, dev->msi_virq[i]);
|
|
dev->msi_virq[i] = -1;
|
|
}
|
|
}
|
|
g_free(dev->msi_virq);
|
|
dev->msi_virq = NULL;
|
|
dev->msi_virq_nr = 0;
|
|
}
|
|
|
|
static void free_assigned_device(AssignedDevice *dev)
|
|
{
|
|
int i;
|
|
|
|
if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
|
|
assigned_dev_unregister_msix_mmio(dev);
|
|
}
|
|
for (i = 0; i < dev->real_device.region_number; i++) {
|
|
PCIRegion *pci_region = &dev->real_device.regions[i];
|
|
AssignedDevRegion *region = &dev->v_addrs[i];
|
|
|
|
if (!pci_region->valid) {
|
|
continue;
|
|
}
|
|
if (pci_region->type & IORESOURCE_IO) {
|
|
if (region->u.r_baseport) {
|
|
memory_region_del_subregion(®ion->container,
|
|
®ion->real_iomem);
|
|
memory_region_destroy(®ion->real_iomem);
|
|
memory_region_destroy(®ion->container);
|
|
}
|
|
} else if (pci_region->type & IORESOURCE_MEM) {
|
|
if (region->u.r_virtbase) {
|
|
memory_region_del_subregion(®ion->container,
|
|
®ion->real_iomem);
|
|
|
|
/* Remove MSI-X table subregion */
|
|
if (pci_region->base_addr <= dev->msix_table_addr &&
|
|
pci_region->base_addr + pci_region->size >
|
|
dev->msix_table_addr) {
|
|
memory_region_del_subregion(®ion->container,
|
|
&dev->mmio);
|
|
}
|
|
|
|
memory_region_destroy(®ion->real_iomem);
|
|
memory_region_destroy(®ion->container);
|
|
if (munmap(region->u.r_virtbase,
|
|
(pci_region->size + 0xFFF) & 0xFFFFF000)) {
|
|
error_report("Failed to unmap assigned device region: %s",
|
|
strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
if (pci_region->resource_fd >= 0) {
|
|
close(pci_region->resource_fd);
|
|
}
|
|
}
|
|
|
|
if (dev->real_device.config_fd >= 0) {
|
|
close(dev->real_device.config_fd);
|
|
}
|
|
|
|
free_msi_virqs(dev);
|
|
}
|
|
|
|
static void assign_failed_examine(AssignedDevice *dev)
|
|
{
|
|
char name[PATH_MAX], dir[PATH_MAX], driver[PATH_MAX] = {}, *ns;
|
|
uint16_t vendor_id, device_id;
|
|
int r;
|
|
|
|
snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/",
|
|
dev->host.domain, dev->host.bus, dev->host.slot,
|
|
dev->host.function);
|
|
|
|
snprintf(name, sizeof(name), "%sdriver", dir);
|
|
|
|
r = readlink(name, driver, sizeof(driver));
|
|
if ((r <= 0) || r >= sizeof(driver)) {
|
|
goto fail;
|
|
}
|
|
|
|
ns = strrchr(driver, '/');
|
|
if (!ns) {
|
|
goto fail;
|
|
}
|
|
|
|
ns++;
|
|
|
|
if (get_real_vendor_id(dir, &vendor_id) ||
|
|
get_real_device_id(dir, &device_id)) {
|
|
goto fail;
|
|
}
|
|
|
|
error_report("*** The driver '%s' is occupying your device "
|
|
"%04x:%02x:%02x.%x.",
|
|
ns, dev->host.domain, dev->host.bus, dev->host.slot,
|
|
dev->host.function);
|
|
error_report("***");
|
|
error_report("*** You can try the following commands to free it:");
|
|
error_report("***");
|
|
error_report("*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub/"
|
|
"new_id", vendor_id, device_id);
|
|
error_report("*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/"
|
|
"%s/unbind",
|
|
dev->host.domain, dev->host.bus, dev->host.slot,
|
|
dev->host.function, ns);
|
|
error_report("*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/"
|
|
"pci-stub/bind",
|
|
dev->host.domain, dev->host.bus, dev->host.slot,
|
|
dev->host.function);
|
|
error_report("*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub"
|
|
"/remove_id", vendor_id, device_id);
|
|
error_report("***");
|
|
|
|
return;
|
|
|
|
fail:
|
|
error_report("Couldn't find out why.");
|
|
}
|
|
|
|
static int assign_device(AssignedDevice *dev)
|
|
{
|
|
uint32_t flags = KVM_DEV_ASSIGN_ENABLE_IOMMU;
|
|
int r;
|
|
|
|
/* Only pass non-zero PCI segment to capable module */
|
|
if (!kvm_check_extension(kvm_state, KVM_CAP_PCI_SEGMENT) &&
|
|
dev->host.domain) {
|
|
error_report("Can't assign device inside non-zero PCI segment "
|
|
"as this KVM module doesn't support it.");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!kvm_check_extension(kvm_state, KVM_CAP_IOMMU)) {
|
|
error_report("No IOMMU found. Unable to assign device \"%s\"",
|
|
dev->dev.qdev.id);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (dev->features & ASSIGNED_DEVICE_SHARE_INTX_MASK &&
|
|
kvm_has_intx_set_mask()) {
|
|
flags |= KVM_DEV_ASSIGN_PCI_2_3;
|
|
}
|
|
|
|
r = kvm_device_pci_assign(kvm_state, &dev->host, flags, &dev->dev_id);
|
|
if (r < 0) {
|
|
error_report("Failed to assign device \"%s\" : %s",
|
|
dev->dev.qdev.id, strerror(-r));
|
|
|
|
switch (r) {
|
|
case -EBUSY:
|
|
assign_failed_examine(dev);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static bool check_irqchip_in_kernel(void)
|
|
{
|
|
if (kvm_irqchip_in_kernel()) {
|
|
return true;
|
|
}
|
|
error_report("pci-assign: error: requires KVM with in-kernel irqchip "
|
|
"enabled");
|
|
return false;
|
|
}
|
|
|
|
static int assign_intx(AssignedDevice *dev)
|
|
{
|
|
AssignedIRQType new_type;
|
|
PCIINTxRoute intx_route;
|
|
bool intx_host_msi;
|
|
int r;
|
|
|
|
/* Interrupt PIN 0 means don't use INTx */
|
|
if (assigned_dev_pci_read_byte(&dev->dev, PCI_INTERRUPT_PIN) == 0) {
|
|
pci_device_set_intx_routing_notifier(&dev->dev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
if (!check_irqchip_in_kernel()) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
pci_device_set_intx_routing_notifier(&dev->dev,
|
|
assigned_dev_update_irq_routing);
|
|
|
|
intx_route = pci_device_route_intx_to_irq(&dev->dev, dev->intpin);
|
|
assert(intx_route.mode != PCI_INTX_INVERTED);
|
|
|
|
if (!pci_intx_route_changed(&dev->intx_route, &intx_route)) {
|
|
return 0;
|
|
}
|
|
|
|
switch (dev->assigned_irq_type) {
|
|
case ASSIGNED_IRQ_INTX_HOST_INTX:
|
|
case ASSIGNED_IRQ_INTX_HOST_MSI:
|
|
intx_host_msi = dev->assigned_irq_type == ASSIGNED_IRQ_INTX_HOST_MSI;
|
|
r = kvm_device_intx_deassign(kvm_state, dev->dev_id, intx_host_msi);
|
|
break;
|
|
case ASSIGNED_IRQ_MSI:
|
|
r = kvm_device_msi_deassign(kvm_state, dev->dev_id);
|
|
break;
|
|
case ASSIGNED_IRQ_MSIX:
|
|
r = kvm_device_msix_deassign(kvm_state, dev->dev_id);
|
|
break;
|
|
default:
|
|
r = 0;
|
|
break;
|
|
}
|
|
if (r) {
|
|
perror("assign_intx: deassignment of previous interrupt failed");
|
|
}
|
|
dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
|
|
|
|
if (intx_route.mode == PCI_INTX_DISABLED) {
|
|
dev->intx_route = intx_route;
|
|
return 0;
|
|
}
|
|
|
|
retry:
|
|
if (dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK &&
|
|
dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
|
|
intx_host_msi = true;
|
|
new_type = ASSIGNED_IRQ_INTX_HOST_MSI;
|
|
} else {
|
|
intx_host_msi = false;
|
|
new_type = ASSIGNED_IRQ_INTX_HOST_INTX;
|
|
}
|
|
|
|
r = kvm_device_intx_assign(kvm_state, dev->dev_id, intx_host_msi,
|
|
intx_route.irq);
|
|
if (r < 0) {
|
|
if (r == -EIO && !(dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK) &&
|
|
dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
|
|
/* Retry with host-side MSI. There might be an IRQ conflict and
|
|
* either the kernel or the device doesn't support sharing. */
|
|
error_report("Host-side INTx sharing not supported, "
|
|
"using MSI instead.\n"
|
|
"Some devices do not to work properly in this mode.");
|
|
dev->features |= ASSIGNED_DEVICE_PREFER_MSI_MASK;
|
|
goto retry;
|
|
}
|
|
error_report("Failed to assign irq for \"%s\": %s",
|
|
dev->dev.qdev.id, strerror(-r));
|
|
error_report("Perhaps you are assigning a device "
|
|
"that shares an IRQ with another device?");
|
|
return r;
|
|
}
|
|
|
|
dev->intx_route = intx_route;
|
|
dev->assigned_irq_type = new_type;
|
|
return r;
|
|
}
|
|
|
|
static void deassign_device(AssignedDevice *dev)
|
|
{
|
|
int r;
|
|
|
|
r = kvm_device_pci_deassign(kvm_state, dev->dev_id);
|
|
assert(r == 0);
|
|
}
|
|
|
|
/* The pci config space got updated. Check if irq numbers have changed
|
|
* for our devices
|
|
*/
|
|
static void assigned_dev_update_irq_routing(PCIDevice *dev)
|
|
{
|
|
AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, dev);
|
|
Error *err = NULL;
|
|
int r;
|
|
|
|
r = assign_intx(assigned_dev);
|
|
if (r < 0) {
|
|
qdev_unplug(&dev->qdev, &err);
|
|
assert(!err);
|
|
}
|
|
}
|
|
|
|
static void assigned_dev_update_msi(PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint8_t ctrl_byte = pci_get_byte(pci_dev->config + pci_dev->msi_cap +
|
|
PCI_MSI_FLAGS);
|
|
int r;
|
|
|
|
/* Some guests gratuitously disable MSI even if they're not using it,
|
|
* try to catch this by only deassigning irqs if the guest is using
|
|
* MSI or intends to start. */
|
|
if (assigned_dev->assigned_irq_type == ASSIGNED_IRQ_MSI ||
|
|
(ctrl_byte & PCI_MSI_FLAGS_ENABLE)) {
|
|
r = kvm_device_msi_deassign(kvm_state, assigned_dev->dev_id);
|
|
/* -ENXIO means no assigned irq */
|
|
if (r && r != -ENXIO) {
|
|
perror("assigned_dev_update_msi: deassign irq");
|
|
}
|
|
|
|
free_msi_virqs(assigned_dev);
|
|
|
|
assigned_dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
|
|
pci_device_set_intx_routing_notifier(pci_dev, NULL);
|
|
}
|
|
|
|
if (ctrl_byte & PCI_MSI_FLAGS_ENABLE) {
|
|
MSIMessage msg = msi_get_message(pci_dev, 0);
|
|
int virq;
|
|
|
|
virq = kvm_irqchip_add_msi_route(kvm_state, msg);
|
|
if (virq < 0) {
|
|
perror("assigned_dev_update_msi: kvm_irqchip_add_msi_route");
|
|
return;
|
|
}
|
|
|
|
assigned_dev->msi_virq = g_malloc(sizeof(*assigned_dev->msi_virq));
|
|
assigned_dev->msi_virq_nr = 1;
|
|
assigned_dev->msi_virq[0] = virq;
|
|
if (kvm_device_msi_assign(kvm_state, assigned_dev->dev_id, virq) < 0) {
|
|
perror("assigned_dev_update_msi: kvm_device_msi_assign");
|
|
}
|
|
|
|
assigned_dev->intx_route.mode = PCI_INTX_DISABLED;
|
|
assigned_dev->intx_route.irq = -1;
|
|
assigned_dev->assigned_irq_type = ASSIGNED_IRQ_MSI;
|
|
} else {
|
|
assign_intx(assigned_dev);
|
|
}
|
|
}
|
|
|
|
static bool assigned_dev_msix_masked(MSIXTableEntry *entry)
|
|
{
|
|
return (entry->ctrl & cpu_to_le32(0x1)) != 0;
|
|
}
|
|
|
|
/*
|
|
* When MSI-X is first enabled the vector table typically has all the
|
|
* vectors masked, so we can't use that as the obvious test to figure out
|
|
* how many vectors to initially enable. Instead we look at the data field
|
|
* because this is what worked for pci-assign for a long time. This makes
|
|
* sure the physical MSI-X state tracks the guest's view, which is important
|
|
* for some VF/PF and PF/fw communication channels.
|
|
*/
|
|
static bool assigned_dev_msix_skipped(MSIXTableEntry *entry)
|
|
{
|
|
return !entry->data;
|
|
}
|
|
|
|
static int assigned_dev_update_msix_mmio(PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *adev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint16_t entries_nr = 0;
|
|
int i, r = 0;
|
|
MSIXTableEntry *entry = adev->msix_table;
|
|
MSIMessage msg;
|
|
|
|
/* Get the usable entry number for allocating */
|
|
for (i = 0; i < adev->msix_max; i++, entry++) {
|
|
if (assigned_dev_msix_skipped(entry)) {
|
|
continue;
|
|
}
|
|
entries_nr++;
|
|
}
|
|
|
|
DEBUG("MSI-X entries: %d\n", entries_nr);
|
|
|
|
/* It's valid to enable MSI-X with all entries masked */
|
|
if (!entries_nr) {
|
|
return 0;
|
|
}
|
|
|
|
r = kvm_device_msix_init_vectors(kvm_state, adev->dev_id, entries_nr);
|
|
if (r != 0) {
|
|
error_report("fail to set MSI-X entry number for MSIX! %s",
|
|
strerror(-r));
|
|
return r;
|
|
}
|
|
|
|
free_msi_virqs(adev);
|
|
|
|
adev->msi_virq_nr = adev->msix_max;
|
|
adev->msi_virq = g_malloc(adev->msix_max * sizeof(*adev->msi_virq));
|
|
|
|
entry = adev->msix_table;
|
|
for (i = 0; i < adev->msix_max; i++, entry++) {
|
|
adev->msi_virq[i] = -1;
|
|
|
|
if (assigned_dev_msix_skipped(entry)) {
|
|
continue;
|
|
}
|
|
|
|
msg.address = entry->addr_lo | ((uint64_t)entry->addr_hi << 32);
|
|
msg.data = entry->data;
|
|
r = kvm_irqchip_add_msi_route(kvm_state, msg);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
adev->msi_virq[i] = r;
|
|
|
|
DEBUG("MSI-X vector %d, gsi %d, addr %08x_%08x, data %08x\n", i,
|
|
r, entry->addr_hi, entry->addr_lo, entry->data);
|
|
|
|
r = kvm_device_msix_set_vector(kvm_state, adev->dev_id, i,
|
|
adev->msi_virq[i]);
|
|
if (r) {
|
|
error_report("fail to set MSI-X entry! %s", strerror(-r));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void assigned_dev_update_msix(PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint16_t ctrl_word = pci_get_word(pci_dev->config + pci_dev->msix_cap +
|
|
PCI_MSIX_FLAGS);
|
|
int r;
|
|
|
|
/* Some guests gratuitously disable MSIX even if they're not using it,
|
|
* try to catch this by only deassigning irqs if the guest is using
|
|
* MSIX or intends to start. */
|
|
if ((assigned_dev->assigned_irq_type == ASSIGNED_IRQ_MSIX) ||
|
|
(ctrl_word & PCI_MSIX_FLAGS_ENABLE)) {
|
|
r = kvm_device_msix_deassign(kvm_state, assigned_dev->dev_id);
|
|
/* -ENXIO means no assigned irq */
|
|
if (r && r != -ENXIO) {
|
|
perror("assigned_dev_update_msix: deassign irq");
|
|
}
|
|
|
|
free_msi_virqs(assigned_dev);
|
|
|
|
assigned_dev->assigned_irq_type = ASSIGNED_IRQ_NONE;
|
|
pci_device_set_intx_routing_notifier(pci_dev, NULL);
|
|
}
|
|
|
|
if (ctrl_word & PCI_MSIX_FLAGS_ENABLE) {
|
|
if (assigned_dev_update_msix_mmio(pci_dev) < 0) {
|
|
perror("assigned_dev_update_msix_mmio");
|
|
return;
|
|
}
|
|
|
|
if (assigned_dev->msi_virq_nr > 0) {
|
|
if (kvm_device_msix_assign(kvm_state, assigned_dev->dev_id) < 0) {
|
|
perror("assigned_dev_enable_msix: assign irq");
|
|
return;
|
|
}
|
|
}
|
|
assigned_dev->intx_route.mode = PCI_INTX_DISABLED;
|
|
assigned_dev->intx_route.irq = -1;
|
|
assigned_dev->assigned_irq_type = ASSIGNED_IRQ_MSIX;
|
|
} else {
|
|
assign_intx(assigned_dev);
|
|
}
|
|
}
|
|
|
|
static uint32_t assigned_dev_pci_read_config(PCIDevice *pci_dev,
|
|
uint32_t address, int len)
|
|
{
|
|
AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint32_t virt_val = pci_default_read_config(pci_dev, address, len);
|
|
uint32_t real_val, emulate_mask, full_emulation_mask;
|
|
|
|
emulate_mask = 0;
|
|
memcpy(&emulate_mask, assigned_dev->emulate_config_read + address, len);
|
|
emulate_mask = le32_to_cpu(emulate_mask);
|
|
|
|
full_emulation_mask = 0xffffffff >> (32 - len * 8);
|
|
|
|
if (emulate_mask != full_emulation_mask) {
|
|
real_val = assigned_dev_pci_read(pci_dev, address, len);
|
|
return (virt_val & emulate_mask) | (real_val & ~emulate_mask);
|
|
} else {
|
|
return virt_val;
|
|
}
|
|
}
|
|
|
|
static void assigned_dev_pci_write_config(PCIDevice *pci_dev, uint32_t address,
|
|
uint32_t val, int len)
|
|
{
|
|
AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint16_t old_cmd = pci_get_word(pci_dev->config + PCI_COMMAND);
|
|
uint32_t emulate_mask, full_emulation_mask;
|
|
int ret;
|
|
|
|
pci_default_write_config(pci_dev, address, val, len);
|
|
|
|
if (kvm_has_intx_set_mask() &&
|
|
range_covers_byte(address, len, PCI_COMMAND + 1)) {
|
|
bool intx_masked = (pci_get_word(pci_dev->config + PCI_COMMAND) &
|
|
PCI_COMMAND_INTX_DISABLE);
|
|
|
|
if (intx_masked != !!(old_cmd & PCI_COMMAND_INTX_DISABLE)) {
|
|
ret = kvm_device_intx_set_mask(kvm_state, assigned_dev->dev_id,
|
|
intx_masked);
|
|
if (ret) {
|
|
perror("assigned_dev_pci_write_config: set intx mask");
|
|
}
|
|
}
|
|
}
|
|
if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) {
|
|
if (range_covers_byte(address, len,
|
|
pci_dev->msi_cap + PCI_MSI_FLAGS)) {
|
|
assigned_dev_update_msi(pci_dev);
|
|
}
|
|
}
|
|
if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
|
|
if (range_covers_byte(address, len,
|
|
pci_dev->msix_cap + PCI_MSIX_FLAGS + 1)) {
|
|
assigned_dev_update_msix(pci_dev);
|
|
}
|
|
}
|
|
|
|
emulate_mask = 0;
|
|
memcpy(&emulate_mask, assigned_dev->emulate_config_write + address, len);
|
|
emulate_mask = le32_to_cpu(emulate_mask);
|
|
|
|
full_emulation_mask = 0xffffffff >> (32 - len * 8);
|
|
|
|
if (emulate_mask != full_emulation_mask) {
|
|
if (emulate_mask) {
|
|
val &= ~emulate_mask;
|
|
val |= assigned_dev_pci_read(pci_dev, address, len) & emulate_mask;
|
|
}
|
|
assigned_dev_pci_write(pci_dev, address, val, len);
|
|
}
|
|
}
|
|
|
|
static void assigned_dev_setup_cap_read(AssignedDevice *dev, uint32_t offset,
|
|
uint32_t len)
|
|
{
|
|
assigned_dev_direct_config_read(dev, offset, len);
|
|
assigned_dev_emulate_config_read(dev, offset + PCI_CAP_LIST_NEXT, 1);
|
|
}
|
|
|
|
static int assigned_device_pci_cap_init(PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
PCIRegion *pci_region = dev->real_device.regions;
|
|
int ret, pos;
|
|
|
|
/* Clear initial capabilities pointer and status copied from hw */
|
|
pci_set_byte(pci_dev->config + PCI_CAPABILITY_LIST, 0);
|
|
pci_set_word(pci_dev->config + PCI_STATUS,
|
|
pci_get_word(pci_dev->config + PCI_STATUS) &
|
|
~PCI_STATUS_CAP_LIST);
|
|
|
|
/* Expose MSI capability
|
|
* MSI capability is the 1st capability in capability config */
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSI, 0);
|
|
if (pos != 0 && kvm_check_extension(kvm_state, KVM_CAP_ASSIGN_DEV_IRQ)) {
|
|
if (!check_irqchip_in_kernel()) {
|
|
return -ENOTSUP;
|
|
}
|
|
dev->cap.available |= ASSIGNED_DEVICE_CAP_MSI;
|
|
/* Only 32-bit/no-mask currently supported */
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_MSI, pos, 10);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
pci_dev->msi_cap = pos;
|
|
|
|
pci_set_word(pci_dev->config + pos + PCI_MSI_FLAGS,
|
|
pci_get_word(pci_dev->config + pos + PCI_MSI_FLAGS) &
|
|
PCI_MSI_FLAGS_QMASK);
|
|
pci_set_long(pci_dev->config + pos + PCI_MSI_ADDRESS_LO, 0);
|
|
pci_set_word(pci_dev->config + pos + PCI_MSI_DATA_32, 0);
|
|
|
|
/* Set writable fields */
|
|
pci_set_word(pci_dev->wmask + pos + PCI_MSI_FLAGS,
|
|
PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
|
|
pci_set_long(pci_dev->wmask + pos + PCI_MSI_ADDRESS_LO, 0xfffffffc);
|
|
pci_set_word(pci_dev->wmask + pos + PCI_MSI_DATA_32, 0xffff);
|
|
}
|
|
/* Expose MSI-X capability */
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSIX, 0);
|
|
if (pos != 0 && kvm_device_msix_supported(kvm_state)) {
|
|
int bar_nr;
|
|
uint32_t msix_table_entry;
|
|
|
|
if (!check_irqchip_in_kernel()) {
|
|
return -ENOTSUP;
|
|
}
|
|
dev->cap.available |= ASSIGNED_DEVICE_CAP_MSIX;
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_MSIX, pos, 12);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
pci_dev->msix_cap = pos;
|
|
|
|
pci_set_word(pci_dev->config + pos + PCI_MSIX_FLAGS,
|
|
pci_get_word(pci_dev->config + pos + PCI_MSIX_FLAGS) &
|
|
PCI_MSIX_FLAGS_QSIZE);
|
|
|
|
/* Only enable and function mask bits are writable */
|
|
pci_set_word(pci_dev->wmask + pos + PCI_MSIX_FLAGS,
|
|
PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL);
|
|
|
|
msix_table_entry = pci_get_long(pci_dev->config + pos + PCI_MSIX_TABLE);
|
|
bar_nr = msix_table_entry & PCI_MSIX_FLAGS_BIRMASK;
|
|
msix_table_entry &= ~PCI_MSIX_FLAGS_BIRMASK;
|
|
dev->msix_table_addr = pci_region[bar_nr].base_addr + msix_table_entry;
|
|
dev->msix_max = pci_get_word(pci_dev->config + pos + PCI_MSIX_FLAGS);
|
|
dev->msix_max &= PCI_MSIX_FLAGS_QSIZE;
|
|
dev->msix_max += 1;
|
|
}
|
|
|
|
/* Minimal PM support, nothing writable, device appears to NAK changes */
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PM, 0);
|
|
if (pos) {
|
|
uint16_t pmc;
|
|
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_PM, pos, PCI_PM_SIZEOF);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
assigned_dev_setup_cap_read(dev, pos, PCI_PM_SIZEOF);
|
|
|
|
pmc = pci_get_word(pci_dev->config + pos + PCI_CAP_FLAGS);
|
|
pmc &= (PCI_PM_CAP_VER_MASK | PCI_PM_CAP_DSI);
|
|
pci_set_word(pci_dev->config + pos + PCI_CAP_FLAGS, pmc);
|
|
|
|
/* assign_device will bring the device up to D0, so we don't need
|
|
* to worry about doing that ourselves here. */
|
|
pci_set_word(pci_dev->config + pos + PCI_PM_CTRL,
|
|
PCI_PM_CTRL_NO_SOFT_RESET);
|
|
|
|
pci_set_byte(pci_dev->config + pos + PCI_PM_PPB_EXTENSIONS, 0);
|
|
pci_set_byte(pci_dev->config + pos + PCI_PM_DATA_REGISTER, 0);
|
|
}
|
|
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_EXP, 0);
|
|
if (pos) {
|
|
uint8_t version, size = 0;
|
|
uint16_t type, devctl, lnksta;
|
|
uint32_t devcap, lnkcap;
|
|
|
|
version = pci_get_byte(pci_dev->config + pos + PCI_EXP_FLAGS);
|
|
version &= PCI_EXP_FLAGS_VERS;
|
|
if (version == 1) {
|
|
size = 0x14;
|
|
} else if (version == 2) {
|
|
/*
|
|
* Check for non-std size, accept reduced size to 0x34,
|
|
* which is what bcm5761 implemented, violating the
|
|
* PCIe v3.0 spec that regs should exist and be read as 0,
|
|
* not optionally provided and shorten the struct size.
|
|
*/
|
|
size = MIN(0x3c, PCI_CONFIG_SPACE_SIZE - pos);
|
|
if (size < 0x34) {
|
|
error_report("%s: Invalid size PCIe cap-id 0x%x",
|
|
__func__, PCI_CAP_ID_EXP);
|
|
return -EINVAL;
|
|
} else if (size != 0x3c) {
|
|
error_report("WARNING, %s: PCIe cap-id 0x%x has "
|
|
"non-standard size 0x%x; std size should be 0x3c",
|
|
__func__, PCI_CAP_ID_EXP, size);
|
|
}
|
|
} else if (version == 0) {
|
|
uint16_t vid, did;
|
|
vid = pci_get_word(pci_dev->config + PCI_VENDOR_ID);
|
|
did = pci_get_word(pci_dev->config + PCI_DEVICE_ID);
|
|
if (vid == PCI_VENDOR_ID_INTEL && did == 0x10ed) {
|
|
/*
|
|
* quirk for Intel 82599 VF with invalid PCIe capability
|
|
* version, should really be version 2 (same as PF)
|
|
*/
|
|
size = 0x3c;
|
|
}
|
|
}
|
|
|
|
if (size == 0) {
|
|
error_report("%s: Unsupported PCI express capability version %d",
|
|
__func__, version);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_EXP, pos, size);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
assigned_dev_setup_cap_read(dev, pos, size);
|
|
|
|
type = pci_get_word(pci_dev->config + pos + PCI_EXP_FLAGS);
|
|
type = (type & PCI_EXP_FLAGS_TYPE) >> 4;
|
|
if (type != PCI_EXP_TYPE_ENDPOINT &&
|
|
type != PCI_EXP_TYPE_LEG_END && type != PCI_EXP_TYPE_RC_END) {
|
|
error_report("Device assignment only supports endpoint assignment,"
|
|
" device type %d", type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* capabilities, pass existing read-only copy
|
|
* PCI_EXP_FLAGS_IRQ: updated by hardware, should be direct read */
|
|
|
|
/* device capabilities: hide FLR */
|
|
devcap = pci_get_long(pci_dev->config + pos + PCI_EXP_DEVCAP);
|
|
devcap &= ~PCI_EXP_DEVCAP_FLR;
|
|
pci_set_long(pci_dev->config + pos + PCI_EXP_DEVCAP, devcap);
|
|
|
|
/* device control: clear all error reporting enable bits, leaving
|
|
* only a few host values. Note, these are
|
|
* all writable, but not passed to hw.
|
|
*/
|
|
devctl = pci_get_word(pci_dev->config + pos + PCI_EXP_DEVCTL);
|
|
devctl = (devctl & (PCI_EXP_DEVCTL_READRQ | PCI_EXP_DEVCTL_PAYLOAD)) |
|
|
PCI_EXP_DEVCTL_RELAX_EN | PCI_EXP_DEVCTL_NOSNOOP_EN;
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_DEVCTL, devctl);
|
|
devctl = PCI_EXP_DEVCTL_BCR_FLR | PCI_EXP_DEVCTL_AUX_PME;
|
|
pci_set_word(pci_dev->wmask + pos + PCI_EXP_DEVCTL, ~devctl);
|
|
|
|
/* Clear device status */
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_DEVSTA, 0);
|
|
|
|
/* Link capabilities, expose links and latencues, clear reporting */
|
|
lnkcap = pci_get_long(pci_dev->config + pos + PCI_EXP_LNKCAP);
|
|
lnkcap &= (PCI_EXP_LNKCAP_SLS | PCI_EXP_LNKCAP_MLW |
|
|
PCI_EXP_LNKCAP_ASPMS | PCI_EXP_LNKCAP_L0SEL |
|
|
PCI_EXP_LNKCAP_L1EL);
|
|
pci_set_long(pci_dev->config + pos + PCI_EXP_LNKCAP, lnkcap);
|
|
|
|
/* Link control, pass existing read-only copy. Should be writable? */
|
|
|
|
/* Link status, only expose current speed and width */
|
|
lnksta = pci_get_word(pci_dev->config + pos + PCI_EXP_LNKSTA);
|
|
lnksta &= (PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW);
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_LNKSTA, lnksta);
|
|
|
|
if (version >= 2) {
|
|
/* Slot capabilities, control, status - not needed for endpoints */
|
|
pci_set_long(pci_dev->config + pos + PCI_EXP_SLTCAP, 0);
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_SLTCTL, 0);
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_SLTSTA, 0);
|
|
|
|
/* Root control, capabilities, status - not needed for endpoints */
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_RTCTL, 0);
|
|
pci_set_word(pci_dev->config + pos + PCI_EXP_RTCAP, 0);
|
|
pci_set_long(pci_dev->config + pos + PCI_EXP_RTSTA, 0);
|
|
|
|
/* Device capabilities/control 2, pass existing read-only copy */
|
|
/* Link control 2, pass existing read-only copy */
|
|
}
|
|
}
|
|
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PCIX, 0);
|
|
if (pos) {
|
|
uint16_t cmd;
|
|
uint32_t status;
|
|
|
|
/* Only expose the minimum, 8 byte capability */
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_PCIX, pos, 8);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
assigned_dev_setup_cap_read(dev, pos, 8);
|
|
|
|
/* Command register, clear upper bits, including extended modes */
|
|
cmd = pci_get_word(pci_dev->config + pos + PCI_X_CMD);
|
|
cmd &= (PCI_X_CMD_DPERR_E | PCI_X_CMD_ERO | PCI_X_CMD_MAX_READ |
|
|
PCI_X_CMD_MAX_SPLIT);
|
|
pci_set_word(pci_dev->config + pos + PCI_X_CMD, cmd);
|
|
|
|
/* Status register, update with emulated PCI bus location, clear
|
|
* error bits, leave the rest. */
|
|
status = pci_get_long(pci_dev->config + pos + PCI_X_STATUS);
|
|
status &= ~(PCI_X_STATUS_BUS | PCI_X_STATUS_DEVFN);
|
|
status |= (pci_bus_num(pci_dev->bus) << 8) | pci_dev->devfn;
|
|
status &= ~(PCI_X_STATUS_SPL_DISC | PCI_X_STATUS_UNX_SPL |
|
|
PCI_X_STATUS_SPL_ERR);
|
|
pci_set_long(pci_dev->config + pos + PCI_X_STATUS, status);
|
|
}
|
|
|
|
pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VPD, 0);
|
|
if (pos) {
|
|
/* Direct R/W passthrough */
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_VPD, pos, 8);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
assigned_dev_setup_cap_read(dev, pos, 8);
|
|
|
|
/* direct write for cap content */
|
|
assigned_dev_direct_config_write(dev, pos + 2, 6);
|
|
}
|
|
|
|
/* Devices can have multiple vendor capabilities, get them all */
|
|
for (pos = 0; (pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VNDR, pos));
|
|
pos += PCI_CAP_LIST_NEXT) {
|
|
uint8_t len = pci_get_byte(pci_dev->config + pos + PCI_CAP_FLAGS);
|
|
/* Direct R/W passthrough */
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_VNDR, pos, len);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
assigned_dev_setup_cap_read(dev, pos, len);
|
|
|
|
/* direct write for cap content */
|
|
assigned_dev_direct_config_write(dev, pos + 2, len - 2);
|
|
}
|
|
|
|
/* If real and virtual capability list status bits differ, virtualize the
|
|
* access. */
|
|
if ((pci_get_word(pci_dev->config + PCI_STATUS) & PCI_STATUS_CAP_LIST) !=
|
|
(assigned_dev_pci_read_byte(pci_dev, PCI_STATUS) &
|
|
PCI_STATUS_CAP_LIST)) {
|
|
dev->emulate_config_read[PCI_STATUS] |= PCI_STATUS_CAP_LIST;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t
|
|
assigned_dev_msix_mmio_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
AssignedDevice *adev = opaque;
|
|
uint64_t val;
|
|
|
|
memcpy(&val, (void *)((uint8_t *)adev->msix_table + addr), size);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void assigned_dev_msix_mmio_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
AssignedDevice *adev = opaque;
|
|
PCIDevice *pdev = &adev->dev;
|
|
uint16_t ctrl;
|
|
MSIXTableEntry orig;
|
|
int i = addr >> 4;
|
|
|
|
if (i >= adev->msix_max) {
|
|
return; /* Drop write */
|
|
}
|
|
|
|
ctrl = pci_get_word(pdev->config + pdev->msix_cap + PCI_MSIX_FLAGS);
|
|
|
|
DEBUG("write to MSI-X table offset 0x%lx, val 0x%lx\n", addr, val);
|
|
|
|
if (ctrl & PCI_MSIX_FLAGS_ENABLE) {
|
|
orig = adev->msix_table[i];
|
|
}
|
|
|
|
memcpy((uint8_t *)adev->msix_table + addr, &val, size);
|
|
|
|
if (ctrl & PCI_MSIX_FLAGS_ENABLE) {
|
|
MSIXTableEntry *entry = &adev->msix_table[i];
|
|
|
|
if (!assigned_dev_msix_masked(&orig) &&
|
|
assigned_dev_msix_masked(entry)) {
|
|
/*
|
|
* Vector masked, disable it
|
|
*
|
|
* XXX It's not clear if we can or should actually attempt
|
|
* to mask or disable the interrupt. KVM doesn't have
|
|
* support for pending bits and kvm_assign_set_msix_entry
|
|
* doesn't modify the device hardware mask. Interrupts
|
|
* while masked are simply not injected to the guest, so
|
|
* are lost. Can we get away with always injecting an
|
|
* interrupt on unmask?
|
|
*/
|
|
} else if (assigned_dev_msix_masked(&orig) &&
|
|
!assigned_dev_msix_masked(entry)) {
|
|
/* Vector unmasked */
|
|
if (i >= adev->msi_virq_nr || adev->msi_virq[i] < 0) {
|
|
/* Previously unassigned vector, start from scratch */
|
|
assigned_dev_update_msix(pdev);
|
|
return;
|
|
} else {
|
|
/* Update an existing, previously masked vector */
|
|
MSIMessage msg;
|
|
int ret;
|
|
|
|
msg.address = entry->addr_lo |
|
|
((uint64_t)entry->addr_hi << 32);
|
|
msg.data = entry->data;
|
|
|
|
ret = kvm_irqchip_update_msi_route(kvm_state,
|
|
adev->msi_virq[i], msg);
|
|
if (ret) {
|
|
error_report("Error updating irq routing entry (%d)", ret);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps assigned_dev_msix_mmio_ops = {
|
|
.read = assigned_dev_msix_mmio_read,
|
|
.write = assigned_dev_msix_mmio_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
|
|
static void assigned_dev_msix_reset(AssignedDevice *dev)
|
|
{
|
|
MSIXTableEntry *entry;
|
|
int i;
|
|
|
|
if (!dev->msix_table) {
|
|
return;
|
|
}
|
|
|
|
memset(dev->msix_table, 0, MSIX_PAGE_SIZE);
|
|
|
|
for (i = 0, entry = dev->msix_table; i < dev->msix_max; i++, entry++) {
|
|
entry->ctrl = cpu_to_le32(0x1); /* Masked */
|
|
}
|
|
}
|
|
|
|
static int assigned_dev_register_msix_mmio(AssignedDevice *dev)
|
|
{
|
|
dev->msix_table = mmap(NULL, MSIX_PAGE_SIZE, PROT_READ|PROT_WRITE,
|
|
MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
|
|
if (dev->msix_table == MAP_FAILED) {
|
|
error_report("fail allocate msix_table! %s", strerror(errno));
|
|
return -EFAULT;
|
|
}
|
|
|
|
assigned_dev_msix_reset(dev);
|
|
|
|
memory_region_init_io(&dev->mmio, &assigned_dev_msix_mmio_ops, dev,
|
|
"assigned-dev-msix", MSIX_PAGE_SIZE);
|
|
return 0;
|
|
}
|
|
|
|
static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev)
|
|
{
|
|
if (!dev->msix_table) {
|
|
return;
|
|
}
|
|
|
|
memory_region_destroy(&dev->mmio);
|
|
|
|
if (munmap(dev->msix_table, MSIX_PAGE_SIZE) == -1) {
|
|
error_report("error unmapping msix_table! %s", strerror(errno));
|
|
}
|
|
dev->msix_table = NULL;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_assigned_device = {
|
|
.name = "pci-assign",
|
|
.unmigratable = 1,
|
|
};
|
|
|
|
static void reset_assigned_device(DeviceState *dev)
|
|
{
|
|
PCIDevice *pci_dev = DO_UPCAST(PCIDevice, qdev, dev);
|
|
AssignedDevice *adev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
char reset_file[64];
|
|
const char reset[] = "1";
|
|
int fd, ret;
|
|
|
|
/*
|
|
* If a guest is reset without being shutdown, MSI/MSI-X can still
|
|
* be running. We want to return the device to a known state on
|
|
* reset, so disable those here. We especially do not want MSI-X
|
|
* enabled since it lives in MMIO space, which is about to get
|
|
* disabled.
|
|
*/
|
|
if (adev->assigned_irq_type == ASSIGNED_IRQ_MSIX) {
|
|
uint16_t ctrl = pci_get_word(pci_dev->config +
|
|
pci_dev->msix_cap + PCI_MSIX_FLAGS);
|
|
|
|
pci_set_word(pci_dev->config + pci_dev->msix_cap + PCI_MSIX_FLAGS,
|
|
ctrl & ~PCI_MSIX_FLAGS_ENABLE);
|
|
assigned_dev_update_msix(pci_dev);
|
|
} else if (adev->assigned_irq_type == ASSIGNED_IRQ_MSI) {
|
|
uint8_t ctrl = pci_get_byte(pci_dev->config +
|
|
pci_dev->msi_cap + PCI_MSI_FLAGS);
|
|
|
|
pci_set_byte(pci_dev->config + pci_dev->msi_cap + PCI_MSI_FLAGS,
|
|
ctrl & ~PCI_MSI_FLAGS_ENABLE);
|
|
assigned_dev_update_msi(pci_dev);
|
|
}
|
|
|
|
snprintf(reset_file, sizeof(reset_file),
|
|
"/sys/bus/pci/devices/%04x:%02x:%02x.%01x/reset",
|
|
adev->host.domain, adev->host.bus, adev->host.slot,
|
|
adev->host.function);
|
|
|
|
/*
|
|
* Issue a device reset via pci-sysfs. Note that we use write(2) here
|
|
* and ignore the return value because some kernels have a bug that
|
|
* returns 0 rather than bytes written on success, sending us into an
|
|
* infinite retry loop using other write mechanisms.
|
|
*/
|
|
fd = open(reset_file, O_WRONLY);
|
|
if (fd != -1) {
|
|
ret = write(fd, reset, strlen(reset));
|
|
(void)ret;
|
|
close(fd);
|
|
}
|
|
|
|
/*
|
|
* When a 0 is written to the bus master register, the device is logically
|
|
* disconnected from the PCI bus. This avoids further DMA transfers.
|
|
*/
|
|
assigned_dev_pci_write_config(pci_dev, PCI_COMMAND, 0, 1);
|
|
}
|
|
|
|
static int assigned_initfn(struct PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
uint8_t e_intx;
|
|
int r;
|
|
|
|
if (!kvm_enabled()) {
|
|
error_report("pci-assign: error: requires KVM support");
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->host.domain && !dev->host.bus && !dev->host.slot &&
|
|
!dev->host.function) {
|
|
error_report("pci-assign: error: no host device specified");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Set up basic config space access control. Will be further refined during
|
|
* device initialization.
|
|
*/
|
|
assigned_dev_emulate_config_read(dev, 0, PCI_CONFIG_SPACE_SIZE);
|
|
assigned_dev_direct_config_read(dev, PCI_STATUS, 2);
|
|
assigned_dev_direct_config_read(dev, PCI_REVISION_ID, 1);
|
|
assigned_dev_direct_config_read(dev, PCI_CLASS_PROG, 3);
|
|
assigned_dev_direct_config_read(dev, PCI_CACHE_LINE_SIZE, 1);
|
|
assigned_dev_direct_config_read(dev, PCI_LATENCY_TIMER, 1);
|
|
assigned_dev_direct_config_read(dev, PCI_BIST, 1);
|
|
assigned_dev_direct_config_read(dev, PCI_CARDBUS_CIS, 4);
|
|
assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_VENDOR_ID, 2);
|
|
assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_ID, 2);
|
|
assigned_dev_direct_config_read(dev, PCI_CAPABILITY_LIST + 1, 7);
|
|
assigned_dev_direct_config_read(dev, PCI_MIN_GNT, 1);
|
|
assigned_dev_direct_config_read(dev, PCI_MAX_LAT, 1);
|
|
memcpy(dev->emulate_config_write, dev->emulate_config_read,
|
|
sizeof(dev->emulate_config_read));
|
|
|
|
if (get_real_device(dev, dev->host.domain, dev->host.bus,
|
|
dev->host.slot, dev->host.function)) {
|
|
error_report("pci-assign: Error: Couldn't get real device (%s)!",
|
|
dev->dev.qdev.id);
|
|
goto out;
|
|
}
|
|
|
|
if (assigned_device_pci_cap_init(pci_dev) < 0) {
|
|
goto out;
|
|
}
|
|
|
|
/* intercept MSI-X entry page in the MMIO */
|
|
if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) {
|
|
if (assigned_dev_register_msix_mmio(dev)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* handle real device's MMIO/PIO BARs */
|
|
if (assigned_dev_register_regions(dev->real_device.regions,
|
|
dev->real_device.region_number,
|
|
dev)) {
|
|
goto out;
|
|
}
|
|
|
|
/* handle interrupt routing */
|
|
e_intx = dev->dev.config[PCI_INTERRUPT_PIN] - 1;
|
|
dev->intpin = e_intx;
|
|
dev->intx_route.mode = PCI_INTX_DISABLED;
|
|
dev->intx_route.irq = -1;
|
|
|
|
/* assign device to guest */
|
|
r = assign_device(dev);
|
|
if (r < 0) {
|
|
goto out;
|
|
}
|
|
|
|
/* assign legacy INTx to the device */
|
|
r = assign_intx(dev);
|
|
if (r < 0) {
|
|
goto assigned_out;
|
|
}
|
|
|
|
assigned_dev_load_option_rom(dev);
|
|
|
|
add_boot_device_path(dev->bootindex, &pci_dev->qdev, NULL);
|
|
|
|
return 0;
|
|
|
|
assigned_out:
|
|
deassign_device(dev);
|
|
out:
|
|
free_assigned_device(dev);
|
|
return -1;
|
|
}
|
|
|
|
static void assigned_exitfn(struct PCIDevice *pci_dev)
|
|
{
|
|
AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev);
|
|
|
|
deassign_device(dev);
|
|
free_assigned_device(dev);
|
|
}
|
|
|
|
static Property assigned_dev_properties[] = {
|
|
DEFINE_PROP_PCI_HOST_DEVADDR("host", AssignedDevice, host),
|
|
DEFINE_PROP_BIT("prefer_msi", AssignedDevice, features,
|
|
ASSIGNED_DEVICE_PREFER_MSI_BIT, false),
|
|
DEFINE_PROP_BIT("share_intx", AssignedDevice, features,
|
|
ASSIGNED_DEVICE_SHARE_INTX_BIT, true),
|
|
DEFINE_PROP_INT32("bootindex", AssignedDevice, bootindex, -1),
|
|
DEFINE_PROP_STRING("configfd", AssignedDevice, configfd_name),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void assign_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
k->init = assigned_initfn;
|
|
k->exit = assigned_exitfn;
|
|
k->config_read = assigned_dev_pci_read_config;
|
|
k->config_write = assigned_dev_pci_write_config;
|
|
dc->props = assigned_dev_properties;
|
|
dc->vmsd = &vmstate_assigned_device;
|
|
dc->reset = reset_assigned_device;
|
|
dc->desc = "KVM-based PCI passthrough";
|
|
}
|
|
|
|
static const TypeInfo assign_info = {
|
|
.name = "kvm-pci-assign",
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(AssignedDevice),
|
|
.class_init = assign_class_init,
|
|
};
|
|
|
|
static void assign_register_types(void)
|
|
{
|
|
type_register_static(&assign_info);
|
|
}
|
|
|
|
type_init(assign_register_types)
|
|
|
|
/*
|
|
* Scan the assigned devices for the devices that have an option ROM, and then
|
|
* load the corresponding ROM data to RAM. If an error occurs while loading an
|
|
* option ROM, we just ignore that option ROM and continue with the next one.
|
|
*/
|
|
static void assigned_dev_load_option_rom(AssignedDevice *dev)
|
|
{
|
|
char name[32], rom_file[64];
|
|
FILE *fp;
|
|
uint8_t val;
|
|
struct stat st;
|
|
void *ptr;
|
|
|
|
/* If loading ROM from file, pci handles it */
|
|
if (dev->dev.romfile || !dev->dev.rom_bar) {
|
|
return;
|
|
}
|
|
|
|
snprintf(rom_file, sizeof(rom_file),
|
|
"/sys/bus/pci/devices/%04x:%02x:%02x.%01x/rom",
|
|
dev->host.domain, dev->host.bus, dev->host.slot,
|
|
dev->host.function);
|
|
|
|
if (stat(rom_file, &st)) {
|
|
return;
|
|
}
|
|
|
|
if (access(rom_file, F_OK)) {
|
|
error_report("pci-assign: Insufficient privileges for %s", rom_file);
|
|
return;
|
|
}
|
|
|
|
/* Write "1" to the ROM file to enable it */
|
|
fp = fopen(rom_file, "r+");
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
val = 1;
|
|
if (fwrite(&val, 1, 1, fp) != 1) {
|
|
goto close_rom;
|
|
}
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
snprintf(name, sizeof(name), "%s.rom",
|
|
object_get_typename(OBJECT(dev)));
|
|
memory_region_init_ram(&dev->dev.rom, name, st.st_size);
|
|
vmstate_register_ram(&dev->dev.rom, &dev->dev.qdev);
|
|
ptr = memory_region_get_ram_ptr(&dev->dev.rom);
|
|
memset(ptr, 0xff, st.st_size);
|
|
|
|
if (!fread(ptr, 1, st.st_size, fp)) {
|
|
error_report("pci-assign: Cannot read from host %s\n"
|
|
"\tDevice option ROM contents are probably invalid "
|
|
"(check dmesg).\n\tSkip option ROM probe with rombar=0, "
|
|
"or load from file with romfile=", rom_file);
|
|
memory_region_destroy(&dev->dev.rom);
|
|
goto close_rom;
|
|
}
|
|
|
|
pci_register_bar(&dev->dev, PCI_ROM_SLOT, 0, &dev->dev.rom);
|
|
dev->dev.has_rom = true;
|
|
close_rom:
|
|
/* Write "0" to disable ROM */
|
|
fseek(fp, 0, SEEK_SET);
|
|
val = 0;
|
|
if (!fwrite(&val, 1, 1, fp)) {
|
|
DEBUG("%s\n", "Failed to disable pci-sysfs rom file");
|
|
}
|
|
fclose(fp);
|
|
}
|