mirror of
https://github.com/clearlinux/kvmtool.git
synced 2026-08-19 03:47:27 +00:00
71ca0fac39
On registering the ioeventfds for the virtio-pci device we cover both the I/O ports and the MMIO BAR. But as the current code advertises both as PIO, the host kernel gets the wrong bus number for the MMIO region. Fix the issue by marking only the actual PIO area as PIO. This fixes vhost-net on x86. Signed-off-by: Andre Przywara <andre.przywara@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
455 lines
12 KiB
C
455 lines
12 KiB
C
#include "kvm/virtio-pci.h"
|
|
|
|
#include "kvm/ioport.h"
|
|
#include "kvm/kvm.h"
|
|
#include "kvm/kvm-cpu.h"
|
|
#include "kvm/virtio-pci-dev.h"
|
|
#include "kvm/irq.h"
|
|
#include "kvm/virtio.h"
|
|
#include "kvm/ioeventfd.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <linux/virtio_pci.h>
|
|
#include <linux/byteorder.h>
|
|
#include <string.h>
|
|
|
|
static void virtio_pci__ioevent_callback(struct kvm *kvm, void *param)
|
|
{
|
|
struct virtio_pci_ioevent_param *ioeventfd = param;
|
|
struct virtio_pci *vpci = ioeventfd->vdev->virtio;
|
|
|
|
ioeventfd->vdev->ops->notify_vq(kvm, vpci->dev, ioeventfd->vq);
|
|
}
|
|
|
|
static int virtio_pci__init_ioeventfd(struct kvm *kvm, struct virtio_device *vdev, u32 vq)
|
|
{
|
|
struct ioevent ioevent;
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int i, r, flags = 0;
|
|
int fds[2];
|
|
|
|
vpci->ioeventfds[vq] = (struct virtio_pci_ioevent_param) {
|
|
.vdev = vdev,
|
|
.vq = vq,
|
|
};
|
|
|
|
ioevent = (struct ioevent) {
|
|
.fn = virtio_pci__ioevent_callback,
|
|
.fn_ptr = &vpci->ioeventfds[vq],
|
|
.datamatch = vq,
|
|
.fn_kvm = kvm,
|
|
};
|
|
|
|
/*
|
|
* Vhost will poll the eventfd in host kernel side, otherwise we
|
|
* need to poll in userspace.
|
|
*/
|
|
if (!vdev->use_vhost)
|
|
flags |= IOEVENTFD_FLAG_USER_POLL;
|
|
|
|
/* ioport */
|
|
ioevent.io_addr = vpci->port_addr + VIRTIO_PCI_QUEUE_NOTIFY;
|
|
ioevent.io_len = sizeof(u16);
|
|
ioevent.fd = fds[0] = eventfd(0, 0);
|
|
r = ioeventfd__add_event(&ioevent, flags | IOEVENTFD_FLAG_PIO);
|
|
if (r)
|
|
return r;
|
|
|
|
/* mmio */
|
|
ioevent.io_addr = vpci->mmio_addr + VIRTIO_PCI_QUEUE_NOTIFY;
|
|
ioevent.io_len = sizeof(u16);
|
|
ioevent.fd = fds[1] = eventfd(0, 0);
|
|
r = ioeventfd__add_event(&ioevent, flags);
|
|
if (r)
|
|
goto free_ioport_evt;
|
|
|
|
if (vdev->ops->notify_vq_eventfd)
|
|
for (i = 0; i < 2; ++i)
|
|
vdev->ops->notify_vq_eventfd(kvm, vpci->dev, vq,
|
|
fds[i]);
|
|
return 0;
|
|
|
|
free_ioport_evt:
|
|
ioeventfd__del_event(vpci->port_addr + VIRTIO_PCI_QUEUE_NOTIFY, vq);
|
|
return r;
|
|
}
|
|
|
|
static inline bool virtio_pci__msix_enabled(struct virtio_pci *vpci)
|
|
{
|
|
return vpci->pci_hdr.msix.ctrl & cpu_to_le16(PCI_MSIX_FLAGS_ENABLE);
|
|
}
|
|
|
|
static bool virtio_pci__specific_io_in(struct kvm *kvm, struct virtio_device *vdev, u16 port,
|
|
void *data, int size, int offset)
|
|
{
|
|
u32 config_offset;
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int type = virtio__get_dev_specific_field(offset - 20,
|
|
virtio_pci__msix_enabled(vpci),
|
|
&config_offset);
|
|
if (type == VIRTIO_PCI_O_MSIX) {
|
|
switch (offset) {
|
|
case VIRTIO_MSI_CONFIG_VECTOR:
|
|
ioport__write16(data, vpci->config_vector);
|
|
break;
|
|
case VIRTIO_MSI_QUEUE_VECTOR:
|
|
ioport__write16(data, vpci->vq_vector[vpci->queue_selector]);
|
|
break;
|
|
};
|
|
|
|
return true;
|
|
} else if (type == VIRTIO_PCI_O_CONFIG) {
|
|
u8 cfg;
|
|
|
|
cfg = vdev->ops->get_config(kvm, vpci->dev)[config_offset];
|
|
ioport__write8(data, cfg);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool virtio_pci__io_in(struct ioport *ioport, struct kvm_cpu *vcpu, u16 port, void *data, int size)
|
|
{
|
|
unsigned long offset;
|
|
bool ret = true;
|
|
struct virtio_device *vdev;
|
|
struct virtio_pci *vpci;
|
|
struct kvm *kvm;
|
|
u32 val;
|
|
|
|
kvm = vcpu->kvm;
|
|
vdev = ioport->priv;
|
|
vpci = vdev->virtio;
|
|
offset = port - vpci->port_addr;
|
|
|
|
switch (offset) {
|
|
case VIRTIO_PCI_HOST_FEATURES:
|
|
val = vdev->ops->get_host_features(kvm, vpci->dev);
|
|
ioport__write32(data, val);
|
|
break;
|
|
case VIRTIO_PCI_QUEUE_PFN:
|
|
val = vdev->ops->get_pfn_vq(kvm, vpci->dev, vpci->queue_selector);
|
|
ioport__write32(data, val);
|
|
break;
|
|
case VIRTIO_PCI_QUEUE_NUM:
|
|
val = vdev->ops->get_size_vq(kvm, vpci->dev, vpci->queue_selector);
|
|
ioport__write16(data, val);
|
|
break;
|
|
case VIRTIO_PCI_STATUS:
|
|
ioport__write8(data, vpci->status);
|
|
break;
|
|
case VIRTIO_PCI_ISR:
|
|
ioport__write8(data, vpci->isr);
|
|
kvm__irq_line(kvm, vpci->legacy_irq_line, VIRTIO_IRQ_LOW);
|
|
vpci->isr = VIRTIO_IRQ_LOW;
|
|
break;
|
|
default:
|
|
ret = virtio_pci__specific_io_in(kvm, vdev, port, data, size, offset);
|
|
break;
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool virtio_pci__specific_io_out(struct kvm *kvm, struct virtio_device *vdev, u16 port,
|
|
void *data, int size, int offset)
|
|
{
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
u32 config_offset, gsi, vec;
|
|
int type = virtio__get_dev_specific_field(offset - 20, virtio_pci__msix_enabled(vpci),
|
|
&config_offset);
|
|
if (type == VIRTIO_PCI_O_MSIX) {
|
|
switch (offset) {
|
|
case VIRTIO_MSI_CONFIG_VECTOR:
|
|
vec = vpci->config_vector = ioport__read16(data);
|
|
if (vec == VIRTIO_MSI_NO_VECTOR)
|
|
break;
|
|
|
|
gsi = irq__add_msix_route(kvm, &vpci->msix_table[vec].msg);
|
|
|
|
vpci->config_gsi = gsi;
|
|
break;
|
|
case VIRTIO_MSI_QUEUE_VECTOR:
|
|
vec = vpci->vq_vector[vpci->queue_selector] = ioport__read16(data);
|
|
|
|
if (vec == VIRTIO_MSI_NO_VECTOR)
|
|
break;
|
|
|
|
gsi = irq__add_msix_route(kvm, &vpci->msix_table[vec].msg);
|
|
vpci->gsis[vpci->queue_selector] = gsi;
|
|
if (vdev->ops->notify_vq_gsi)
|
|
vdev->ops->notify_vq_gsi(kvm, vpci->dev,
|
|
vpci->queue_selector, gsi);
|
|
break;
|
|
};
|
|
|
|
return true;
|
|
} else if (type == VIRTIO_PCI_O_CONFIG) {
|
|
vdev->ops->get_config(kvm, vpci->dev)[config_offset] = *(u8 *)data;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool virtio_pci__io_out(struct ioport *ioport, struct kvm_cpu *vcpu, u16 port, void *data, int size)
|
|
{
|
|
unsigned long offset;
|
|
bool ret = true;
|
|
struct virtio_device *vdev;
|
|
struct virtio_pci *vpci;
|
|
struct kvm *kvm;
|
|
u32 val;
|
|
|
|
kvm = vcpu->kvm;
|
|
vdev = ioport->priv;
|
|
vpci = vdev->virtio;
|
|
offset = port - vpci->port_addr;
|
|
|
|
switch (offset) {
|
|
case VIRTIO_PCI_GUEST_FEATURES:
|
|
val = ioport__read32(data);
|
|
vdev->ops->set_guest_features(kvm, vpci->dev, val);
|
|
break;
|
|
case VIRTIO_PCI_QUEUE_PFN:
|
|
val = ioport__read32(data);
|
|
virtio_pci__init_ioeventfd(kvm, vdev, vpci->queue_selector);
|
|
vdev->ops->init_vq(kvm, vpci->dev, vpci->queue_selector,
|
|
1 << VIRTIO_PCI_QUEUE_ADDR_SHIFT,
|
|
VIRTIO_PCI_VRING_ALIGN, val);
|
|
break;
|
|
case VIRTIO_PCI_QUEUE_SEL:
|
|
vpci->queue_selector = ioport__read16(data);
|
|
break;
|
|
case VIRTIO_PCI_QUEUE_NOTIFY:
|
|
val = ioport__read16(data);
|
|
vdev->ops->notify_vq(kvm, vpci->dev, val);
|
|
break;
|
|
case VIRTIO_PCI_STATUS:
|
|
vpci->status = ioport__read8(data);
|
|
if (!vpci->status) /* Sample endianness on reset */
|
|
vdev->endian = kvm_cpu__get_endianness(vcpu);
|
|
if (vdev->ops->notify_status)
|
|
vdev->ops->notify_status(kvm, vpci->dev, vpci->status);
|
|
break;
|
|
default:
|
|
ret = virtio_pci__specific_io_out(kvm, vdev, port, data, size, offset);
|
|
break;
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct ioport_operations virtio_pci__io_ops = {
|
|
.io_in = virtio_pci__io_in,
|
|
.io_out = virtio_pci__io_out,
|
|
};
|
|
|
|
static void virtio_pci__msix_mmio_callback(struct kvm_cpu *vcpu,
|
|
u64 addr, u8 *data, u32 len,
|
|
u8 is_write, void *ptr)
|
|
{
|
|
struct virtio_pci *vpci = ptr;
|
|
void *table;
|
|
u32 offset;
|
|
|
|
if (addr > vpci->msix_io_block + PCI_IO_SIZE) {
|
|
table = &vpci->msix_pba;
|
|
offset = vpci->msix_io_block + PCI_IO_SIZE;
|
|
} else {
|
|
table = &vpci->msix_table;
|
|
offset = vpci->msix_io_block;
|
|
}
|
|
|
|
if (is_write)
|
|
memcpy(table + addr - offset, data, len);
|
|
else
|
|
memcpy(data, table + addr - offset, len);
|
|
}
|
|
|
|
static void virtio_pci__signal_msi(struct kvm *kvm, struct virtio_pci *vpci, int vec)
|
|
{
|
|
struct kvm_msi msi = {
|
|
.address_lo = vpci->msix_table[vec].msg.address_lo,
|
|
.address_hi = vpci->msix_table[vec].msg.address_hi,
|
|
.data = vpci->msix_table[vec].msg.data,
|
|
};
|
|
|
|
ioctl(kvm->vm_fd, KVM_SIGNAL_MSI, &msi);
|
|
}
|
|
|
|
int virtio_pci__signal_vq(struct kvm *kvm, struct virtio_device *vdev, u32 vq)
|
|
{
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int tbl = vpci->vq_vector[vq];
|
|
|
|
if (virtio_pci__msix_enabled(vpci) && tbl != VIRTIO_MSI_NO_VECTOR) {
|
|
if (vpci->pci_hdr.msix.ctrl & cpu_to_le16(PCI_MSIX_FLAGS_MASKALL) ||
|
|
vpci->msix_table[tbl].ctrl & cpu_to_le16(PCI_MSIX_ENTRY_CTRL_MASKBIT)) {
|
|
|
|
vpci->msix_pba |= 1 << tbl;
|
|
return 0;
|
|
}
|
|
|
|
if (vpci->features & VIRTIO_PCI_F_SIGNAL_MSI)
|
|
virtio_pci__signal_msi(kvm, vpci, vpci->vq_vector[vq]);
|
|
else
|
|
kvm__irq_trigger(kvm, vpci->gsis[vq]);
|
|
} else {
|
|
vpci->isr = VIRTIO_IRQ_HIGH;
|
|
kvm__irq_trigger(kvm, vpci->legacy_irq_line);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int virtio_pci__signal_config(struct kvm *kvm, struct virtio_device *vdev)
|
|
{
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int tbl = vpci->config_vector;
|
|
|
|
if (virtio_pci__msix_enabled(vpci) && tbl != VIRTIO_MSI_NO_VECTOR) {
|
|
if (vpci->pci_hdr.msix.ctrl & cpu_to_le16(PCI_MSIX_FLAGS_MASKALL) ||
|
|
vpci->msix_table[tbl].ctrl & cpu_to_le16(PCI_MSIX_ENTRY_CTRL_MASKBIT)) {
|
|
|
|
vpci->msix_pba |= 1 << tbl;
|
|
return 0;
|
|
}
|
|
|
|
if (vpci->features & VIRTIO_PCI_F_SIGNAL_MSI)
|
|
virtio_pci__signal_msi(kvm, vpci, tbl);
|
|
else
|
|
kvm__irq_trigger(kvm, vpci->config_gsi);
|
|
} else {
|
|
vpci->isr = VIRTIO_PCI_ISR_CONFIG;
|
|
kvm__irq_trigger(kvm, vpci->legacy_irq_line);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void virtio_pci__io_mmio_callback(struct kvm_cpu *vcpu,
|
|
u64 addr, u8 *data, u32 len,
|
|
u8 is_write, void *ptr)
|
|
{
|
|
struct virtio_pci *vpci = ptr;
|
|
int direction = is_write ? KVM_EXIT_IO_OUT : KVM_EXIT_IO_IN;
|
|
u16 port = vpci->port_addr + (addr & (IOPORT_SIZE - 1));
|
|
|
|
kvm__emulate_io(vcpu, port, data, direction, len, 1);
|
|
}
|
|
|
|
int virtio_pci__init(struct kvm *kvm, void *dev, struct virtio_device *vdev,
|
|
int device_id, int subsys_id, int class)
|
|
{
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int r;
|
|
|
|
vpci->kvm = kvm;
|
|
vpci->dev = dev;
|
|
|
|
r = ioport__register(kvm, IOPORT_EMPTY, &virtio_pci__io_ops, IOPORT_SIZE, vdev);
|
|
if (r < 0)
|
|
return r;
|
|
vpci->port_addr = (u16)r;
|
|
|
|
vpci->mmio_addr = pci_get_io_space_block(IOPORT_SIZE);
|
|
r = kvm__register_mmio(kvm, vpci->mmio_addr, IOPORT_SIZE, false,
|
|
virtio_pci__io_mmio_callback, vpci);
|
|
if (r < 0)
|
|
goto free_ioport;
|
|
|
|
vpci->msix_io_block = pci_get_io_space_block(PCI_IO_SIZE * 2);
|
|
r = kvm__register_mmio(kvm, vpci->msix_io_block, PCI_IO_SIZE * 2, false,
|
|
virtio_pci__msix_mmio_callback, vpci);
|
|
if (r < 0)
|
|
goto free_mmio;
|
|
|
|
vpci->pci_hdr = (struct pci_device_header) {
|
|
.vendor_id = cpu_to_le16(PCI_VENDOR_ID_REDHAT_QUMRANET),
|
|
.device_id = cpu_to_le16(device_id),
|
|
.command = PCI_COMMAND_IO | PCI_COMMAND_MEMORY,
|
|
.header_type = PCI_HEADER_TYPE_NORMAL,
|
|
.revision_id = 0,
|
|
.class[0] = class & 0xff,
|
|
.class[1] = (class >> 8) & 0xff,
|
|
.class[2] = (class >> 16) & 0xff,
|
|
.subsys_vendor_id = cpu_to_le16(PCI_SUBSYSTEM_VENDOR_ID_REDHAT_QUMRANET),
|
|
.subsys_id = cpu_to_le16(subsys_id),
|
|
.bar[0] = cpu_to_le32(vpci->mmio_addr
|
|
| PCI_BASE_ADDRESS_SPACE_MEMORY),
|
|
.bar[1] = cpu_to_le32(vpci->port_addr
|
|
| PCI_BASE_ADDRESS_SPACE_IO),
|
|
.bar[2] = cpu_to_le32(vpci->msix_io_block
|
|
| PCI_BASE_ADDRESS_SPACE_MEMORY),
|
|
.status = cpu_to_le16(PCI_STATUS_CAP_LIST),
|
|
.capabilities = (void *)&vpci->pci_hdr.msix - (void *)&vpci->pci_hdr,
|
|
.bar_size[0] = cpu_to_le32(IOPORT_SIZE),
|
|
.bar_size[1] = cpu_to_le32(IOPORT_SIZE),
|
|
.bar_size[2] = cpu_to_le32(PCI_IO_SIZE*2),
|
|
};
|
|
|
|
vpci->dev_hdr = (struct device_header) {
|
|
.bus_type = DEVICE_BUS_PCI,
|
|
.data = &vpci->pci_hdr,
|
|
};
|
|
|
|
vpci->pci_hdr.msix.cap = PCI_CAP_ID_MSIX;
|
|
vpci->pci_hdr.msix.next = 0;
|
|
/*
|
|
* We at most have VIRTIO_PCI_MAX_VQ entries for virt queue,
|
|
* VIRTIO_PCI_MAX_CONFIG entries for config.
|
|
*
|
|
* To quote the PCI spec:
|
|
*
|
|
* System software reads this field to determine the
|
|
* MSI-X Table Size N, which is encoded as N-1.
|
|
* For example, a returned value of "00000000011"
|
|
* indicates a table size of 4.
|
|
*/
|
|
vpci->pci_hdr.msix.ctrl = cpu_to_le16(VIRTIO_PCI_MAX_VQ + VIRTIO_PCI_MAX_CONFIG - 1);
|
|
|
|
/* Both table and PBA are mapped to the same BAR (2) */
|
|
vpci->pci_hdr.msix.table_offset = cpu_to_le32(2);
|
|
vpci->pci_hdr.msix.pba_offset = cpu_to_le32(2 | PCI_IO_SIZE);
|
|
vpci->config_vector = 0;
|
|
|
|
if (kvm__supports_extension(kvm, KVM_CAP_SIGNAL_MSI))
|
|
vpci->features |= VIRTIO_PCI_F_SIGNAL_MSI;
|
|
|
|
r = device__register(&vpci->dev_hdr);
|
|
if (r < 0)
|
|
goto free_msix_mmio;
|
|
|
|
/* save the IRQ that device__register() has allocated */
|
|
vpci->legacy_irq_line = vpci->pci_hdr.irq_line;
|
|
|
|
return 0;
|
|
|
|
free_msix_mmio:
|
|
kvm__deregister_mmio(kvm, vpci->msix_io_block);
|
|
free_mmio:
|
|
kvm__deregister_mmio(kvm, vpci->mmio_addr);
|
|
free_ioport:
|
|
ioport__unregister(kvm, vpci->port_addr);
|
|
return r;
|
|
}
|
|
|
|
int virtio_pci__exit(struct kvm *kvm, struct virtio_device *vdev)
|
|
{
|
|
struct virtio_pci *vpci = vdev->virtio;
|
|
int i;
|
|
|
|
kvm__deregister_mmio(kvm, vpci->mmio_addr);
|
|
kvm__deregister_mmio(kvm, vpci->msix_io_block);
|
|
ioport__unregister(kvm, vpci->port_addr);
|
|
|
|
for (i = 0; i < VIRTIO_PCI_MAX_VQ; i++) {
|
|
ioeventfd__del_event(vpci->port_addr + VIRTIO_PCI_QUEUE_NOTIFY, i);
|
|
ioeventfd__del_event(vpci->mmio_addr + VIRTIO_PCI_QUEUE_NOTIFY, i);
|
|
}
|
|
|
|
return 0;
|
|
}
|