Message ID | 20191011085611.4194-8-stefanha@redhat.com |
---|---|
State | New |
Headers | show |
Series | libqos: add VIRTIO PCI 1.0 support | expand |
Stefan Hajnoczi <stefanha@redhat.com> writes: > Implement the VIRTIO 1.0 virtio-pci interface. The main change here is > that the register layout is no longer a fixed layout in BAR 0. Instead > we have to iterate of PCI Capabilities to find descriptions of where > various registers are located. The vring registers are also more > fine-grained, allowing for more flexible vring layouts, but we don't > take advantage of that. > > Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are > therefore not running in VIRTIO 1.0 mode. > > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> > --- > tests/Makefile.include | 1 + > tests/libqos/virtio-pci-modern.h | 17 ++ > tests/libqos/virtio-pci.h | 10 + > tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ > tests/libqos/virtio-pci.c | 6 +- > 5 files changed, 445 insertions(+), 1 deletion(-) > create mode 100644 tests/libqos/virtio-pci-modern.h > create mode 100644 tests/libqos/virtio-pci-modern.c > > diff --git a/tests/Makefile.include b/tests/Makefile.include > index 3543451ed3..3f633c8313 100644 > --- a/tests/Makefile.include > +++ b/tests/Makefile.include > @@ -715,6 +715,7 @@ qos-test-obj-y += tests/libqos/virtio-blk.o > qos-test-obj-y += tests/libqos/virtio-mmio.o > qos-test-obj-y += tests/libqos/virtio-net.o > qos-test-obj-y += tests/libqos/virtio-pci.o > +qos-test-obj-y += tests/libqos/virtio-pci-modern.o > qos-test-obj-y += tests/libqos/virtio-rng.o > qos-test-obj-y += tests/libqos/virtio-scsi.o > qos-test-obj-y += tests/libqos/virtio-serial.o > diff --git a/tests/libqos/virtio-pci-modern.h b/tests/libqos/virtio-pci-modern.h > new file mode 100644 > index 0000000000..6bf2b207c3 > --- /dev/null > +++ b/tests/libqos/virtio-pci-modern.h > @@ -0,0 +1,17 @@ > +/* > + * libqos virtio PCI VIRTIO 1.0 definitions > + * > + * Copyright (c) 2019 Red Hat, Inc > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or later. > + * See the COPYING file in the top-level directory. > + */ > + > +#ifndef LIBQOS_VIRTIO_PCI_MODERN_H > +#define LIBQOS_VIRTIO_PCI_MODERN_H > + > +#include "virtio-pci.h" > + > +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev); > + > +#endif /* LIBQOS_VIRTIO_PCI_MODERN_H */ > diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h > index f2d53aa377..29d4e9bf79 100644 > --- a/tests/libqos/virtio-pci.h > +++ b/tests/libqos/virtio-pci.h > @@ -27,6 +27,13 @@ typedef struct QVirtioPCIDevice { > uint32_t config_msix_data; > > uint8_t bar_idx; > + > + /* VIRTIO 1.0 */ > + uint32_t common_cfg_offset; > + uint32_t notify_cfg_offset; > + uint32_t notify_off_multiplier; > + uint32_t isr_cfg_offset; > + uint32_t device_cfg_offset; > } QVirtioPCIDevice; > > struct QVirtioPCIMSIXOps { > @@ -43,6 +50,9 @@ typedef struct QVirtQueuePCI { > uint16_t msix_entry; > uint64_t msix_addr; > uint32_t msix_data; > + > + /* VIRTIO 1.0 */ > + uint64_t notify_offset; > } QVirtQueuePCI; > > void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr); > diff --git a/tests/libqos/virtio-pci-modern.c b/tests/libqos/virtio-pci-modern.c > new file mode 100644 > index 0000000000..f23c876290 > --- /dev/null > +++ b/tests/libqos/virtio-pci-modern.c > @@ -0,0 +1,412 @@ > +/* > + * libqos VIRTIO 1.0 PCI driver > + * > + * Copyright (c) 2019 Red Hat, Inc > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or later. > + * See the COPYING file in the top-level directory. > + */ > + > +#include "qemu/osdep.h" > +#include "standard-headers/linux/pci_regs.h" > +#include "standard-headers/linux/virtio_pci.h" > +#include "virtio-pci-modern.h" > + > +static uint8_t config_readb(QVirtioDevice *d, uint64_t addr) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + return qpci_io_readb(dev->pdev, dev->bar, dev->device_cfg_offset + addr); > +} > + > +static uint16_t config_readw(QVirtioDevice *d, uint64_t addr) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + return qpci_io_readw(dev->pdev, dev->bar, dev->device_cfg_offset + addr); > +} > + > +static uint32_t config_readl(QVirtioDevice *d, uint64_t addr) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + return qpci_io_readl(dev->pdev, dev->bar, dev->device_cfg_offset + addr); > +} > + > +static uint64_t config_readq(QVirtioDevice *d, uint64_t addr) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + return qpci_io_readq(dev->pdev, dev->bar, dev->device_cfg_offset + addr); > +} > + > +static uint32_t get_features(QVirtioDevice *d) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + device_feature_select), > + 0); > + return qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + device_feature)); > +} > + > +static void set_features(QVirtioDevice *d, uint32_t features) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + guest_feature_select), > + 0); > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + guest_feature), > + features); > +} > + > +static uint32_t get_guest_features(QVirtioDevice *d) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + guest_feature_select), > + 0); > + return qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + guest_feature)); > +} > + > +static uint8_t get_status(QVirtioDevice *d) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + return qpci_io_readb(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + device_status)); > +} > + > +static void set_status(QVirtioDevice *d, uint8_t status) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + return qpci_io_writeb(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + device_status), > + status); > +} > + > +static bool get_msix_status(QVirtioPCIDevice *dev, uint32_t msix_entry, > + uint32_t msix_addr, uint32_t msix_data) > +{ > + uint32_t data; > + > + g_assert_cmpint(msix_entry, !=, -1); > + if (qpci_msix_masked(dev->pdev, msix_entry)) { > + /* No ISR checking should be done if masked, but read anyway */ > + return qpci_msix_pending(dev->pdev, msix_entry); > + } > + > + data = qtest_readl(dev->pdev->bus->qts, msix_addr); > + if (data == msix_data) { > + qtest_writel(dev->pdev->bus->qts, msix_addr, 0); > + return true; > + } else { > + return false; > + } > +} > + > +static bool get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + if (dev->pdev->msix_enabled) { > + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); > + > + return get_msix_status(dev, vqpci->msix_entry, vqpci->msix_addr, > + vqpci->msix_data); > + } > + > + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 1; > +} > + > +static bool get_config_isr_status(QVirtioDevice *d) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + if (dev->pdev->msix_enabled) { > + return get_msix_status(dev, dev->config_msix_entry, > + dev->config_msix_addr, dev->config_msix_data); > + } > + > + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 2; > +} > + > +static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + gint64 start_time = g_get_monotonic_time(); > + > + do { > + g_assert(g_get_monotonic_time() - start_time <= timeout_us); > + qtest_clock_step(dev->pdev->bus->qts, 100); > + } while (!get_config_isr_status(d)); > +} > + > +static void queue_select(QVirtioDevice *d, uint16_t index) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_select), > + index); > +} > + > +static uint16_t get_queue_size(QVirtioDevice *d) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + return qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_size)); > +} > + > +static void set_queue_address(QVirtioDevice *d, QVirtQueue *vq) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_desc_lo), > + vq->desc); > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_desc_hi), > + vq->desc >> 32); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_avail_lo), > + vq->avail); > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_avail_hi), > + vq->avail >> 32); > + > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_used_lo), > + vq->used); > + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_used_hi), > + vq->used >> 32); > +} > + > +static QVirtQueue *virtqueue_setup(QVirtioDevice *d, QGuestAllocator *alloc, > + uint16_t index) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + QVirtQueue *vq; > + QVirtQueuePCI *vqpci; > + uint16_t notify_off; > + > + vq = qvirtio_pci_virtqueue_setup_common(d, alloc, index); > + vqpci = container_of(vq, QVirtQueuePCI, vq); > + > + notify_off = qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + queue_notify_off)); > + > + vqpci->notify_offset = dev->notify_cfg_offset + > + notify_off * dev->notify_off_multiplier; > + > + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_enable), 1); > + > + return vq; > +} > + > +static void virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) > +{ > + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); > + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); > + > + qpci_io_writew(dev->pdev, dev->bar, vqpci->notify_offset, vq->index); > +} > + > +static const QVirtioBus qvirtio_pci_virtio_1 = { > + .config_readb = config_readb, > + .config_readw = config_readw, > + .config_readl = config_readl, > + .config_readq = config_readq, > + .get_features = get_features, > + .set_features = set_features, > + .get_guest_features = get_guest_features, > + .get_status = get_status, > + .set_status = set_status, > + .get_queue_isr_status = get_queue_isr_status, > + .wait_config_isr_status = wait_config_isr_status, > + .queue_select = queue_select, > + .get_queue_size = get_queue_size, > + .set_queue_address = set_queue_address, > + .virtqueue_setup = virtqueue_setup, > + .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, > + .virtqueue_kick = virtqueue_kick, > +}; > + > +static void set_config_vector(QVirtioPCIDevice *d, uint16_t entry) > +{ > + uint16_t vector; > + > + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, msix_config), entry); > + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + msix_config)); > + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); > +} > + > +static void set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, > + uint16_t entry) > +{ > + uint16_t vector; > + > + queue_select(&d->vdev, vq_idx); > + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, queue_msix_vector), > + entry); > + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + > + offsetof(struct virtio_pci_common_cfg, > + queue_msix_vector)); > + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); > +} > + > +static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_virtio_1 = { > + .set_config_vector = set_config_vector, > + .set_queue_vector = set_queue_vector, > +}; > + > +static bool probe_device_type(QVirtioPCIDevice *dev) > +{ > + uint16_t vendor_id; > + uint16_t device_id; > + > + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ > + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); > + if (vendor_id != 0x1af4) { > + return false; > + } > + > + /* > + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive > + * is a virtio device" > + */ > + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); > + if (device_id < 0x1000 || device_id > 0x107f) { > + return false; > + } > + > + /* > + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to > + * 0x103F depending on the device type" > + */ > + if (device_id < 0x1040) { > + /* > + * "Transitional devices MUST have the PCI Subsystem Device ID matching > + * the Virtio Device ID" > + */ > + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); > + } else { > + /* > + * "The PCI Device ID is calculated by adding 0x1040 to the Virtio > + * Device ID" > + */ > + dev->vdev.device_type = device_id - 0x1040; > + } > + > + return true; > +} > + > +/* Find the first VIRTIO 1.0 PCI structure for a given type */ > +static bool find_structure(QVirtioPCIDevice *dev, uint8_t cfg_type, > + uint8_t *bar, uint32_t *offset, uint32_t *length, > + uint8_t *cfg_addr) > +{ > + uint8_t addr = 0; > + > + while ((addr = qpci_find_capability(dev->pdev, PCI_CAP_ID_VNDR, > + addr)) != 0) { > + uint8_t type; > + > + type = qpci_config_readb(dev->pdev, > + addr + offsetof(struct virtio_pci_cap, cfg_type)); > + if (type != cfg_type) { > + continue; > + } > + > + *bar = qpci_config_readb(dev->pdev, > + addr + offsetof(struct virtio_pci_cap, bar)); > + *offset = qpci_config_readl(dev->pdev, > + addr + offsetof(struct virtio_pci_cap, offset)); > + *length = qpci_config_readl(dev->pdev, > + addr + offsetof(struct virtio_pci_cap, length)); > + if (cfg_addr) { > + *cfg_addr = addr; > + } > + > + return true; > + } > + > + return false; > +} > + > +static bool probe_device_layout(QVirtioPCIDevice *dev) > +{ > + uint8_t bar; > + uint8_t cfg_addr; > + uint32_t length; > + > + /* > + * Due to the qpci_iomap() API we only support devices that put all > + * structures in the same PCI BAR. Luckily this is true with QEMU. > + */ > + > + if (!find_structure(dev, VIRTIO_PCI_CAP_COMMON_CFG, &dev->bar_idx, > + &dev->common_cfg_offset, &length, NULL)) { > + return false; > + } > + > + if (!find_structure(dev, VIRTIO_PCI_CAP_NOTIFY_CFG, &bar, > + &dev->notify_cfg_offset, &length, &cfg_addr)) { > + return false; > + } > + g_assert_cmphex(bar, ==, dev->bar_idx); > + > + dev->notify_off_multiplier = qpci_config_readl(dev->pdev, > + cfg_addr + offsetof(struct virtio_pci_notify_cap, > + notify_off_multiplier)); > + > + if (!find_structure(dev, VIRTIO_PCI_CAP_ISR_CFG, &bar, > + &dev->isr_cfg_offset, &length, NULL)) { > + return false; > + } > + g_assert_cmphex(bar, ==, dev->bar_idx); > + > + if (!find_structure(dev, VIRTIO_PCI_CAP_DEVICE_CFG, &bar, > + &dev->device_cfg_offset, &length, NULL)) { > + return false; > + } > + g_assert_cmphex(bar, ==, dev->bar_idx); > + > + return true; > +} > + > +/* Probe a VIRTIO 1.0 device */ > +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev) > +{ > + if (!probe_device_type(dev)) { > + return false; > + } > + > + if (!probe_device_layout(dev)) { > + return false; > + } > + > + dev->vdev.bus = &qvirtio_pci_virtio_1; > + dev->msix_ops = &qvirtio_pci_msix_ops_virtio_1; > + dev->vdev.big_endian = false; > + return true; > +} > diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c > index efd8caee18..5bdf403351 100644 > --- a/tests/libqos/virtio-pci.c > +++ b/tests/libqos/virtio-pci.c > @@ -22,6 +22,8 @@ > #include "hw/pci/pci.h" > #include "hw/pci/pci_regs.h" > > +#include "virtio-pci-modern.h" > + > /* virtio-pci is a superclass of all virtio-xxx-pci devices; > * the relation between virtio-pci and virtio-xxx-pci is implicit, > * and therefore virtio-pci does not produce virtio and is not > @@ -400,7 +402,9 @@ static void qvirtio_pci_init_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_ > dev->pdev = pci_dev; > dev->config_msix_entry = -1; > > - qvirtio_pci_init_legacy(dev); > + if (!qvirtio_pci_init_virtio_1(dev)) { > + qvirtio_pci_init_legacy(dev); > + } > > /* each virtio-xxx-pci device should override at least this function */ > dev->obj.get_driver = NULL; Reviewed-by: Sergio Lopez <slp@redhat.com>
On 11/10/2019 10.56, Stefan Hajnoczi wrote: > Implement the VIRTIO 1.0 virtio-pci interface. The main change here is > that the register layout is no longer a fixed layout in BAR 0. Instead > we have to iterate of PCI Capabilities to find descriptions of where > various registers are located. The vring registers are also more > fine-grained, allowing for more flexible vring layouts, but we don't > take advantage of that. > > Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are > therefore not running in VIRTIO 1.0 mode. > > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> > --- > tests/Makefile.include | 1 + > tests/libqos/virtio-pci-modern.h | 17 ++ > tests/libqos/virtio-pci.h | 10 + > tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ > tests/libqos/virtio-pci.c | 6 +- > 5 files changed, 445 insertions(+), 1 deletion(-) > create mode 100644 tests/libqos/virtio-pci-modern.h > create mode 100644 tests/libqos/virtio-pci-modern.c [...] > +static bool probe_device_type(QVirtioPCIDevice *dev) > +{ > + uint16_t vendor_id; > + uint16_t device_id; > + > + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ > + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); > + if (vendor_id != 0x1af4) { > + return false; > + } > + > + /* > + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive > + * is a virtio device" > + */ > + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); > + if (device_id < 0x1000 || device_id > 0x107f) { > + return false; > + } > + > + /* > + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to > + * 0x103F depending on the device type" > + */ > + if (device_id < 0x1040) { > + /* > + * "Transitional devices MUST have the PCI Subsystem Device ID matching > + * the Virtio Device ID" > + */ > + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); Shouldn't you return "false" here in case the device_type is 0 ? Which likely means that it is a legacy or broken device ...? > + } else { > + /* > + * "The PCI Device ID is calculated by adding 0x1040 to the Virtio > + * Device ID" > + */ > + dev->vdev.device_type = device_id - 0x1040; > + } > + > + return true; > +} Thomas
On Thu, Oct 17, 2019 at 04:52:54PM +0200, Thomas Huth wrote: > On 11/10/2019 10.56, Stefan Hajnoczi wrote: > > Implement the VIRTIO 1.0 virtio-pci interface. The main change here is > > that the register layout is no longer a fixed layout in BAR 0. Instead > > we have to iterate of PCI Capabilities to find descriptions of where > > various registers are located. The vring registers are also more > > fine-grained, allowing for more flexible vring layouts, but we don't > > take advantage of that. > > > > Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are > > therefore not running in VIRTIO 1.0 mode. > > > > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> > > --- > > tests/Makefile.include | 1 + > > tests/libqos/virtio-pci-modern.h | 17 ++ > > tests/libqos/virtio-pci.h | 10 + > > tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ > > tests/libqos/virtio-pci.c | 6 +- > > 5 files changed, 445 insertions(+), 1 deletion(-) > > create mode 100644 tests/libqos/virtio-pci-modern.h > > create mode 100644 tests/libqos/virtio-pci-modern.c > [...] > > +static bool probe_device_type(QVirtioPCIDevice *dev) > > +{ > > + uint16_t vendor_id; > > + uint16_t device_id; > > + > > + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ > > + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); > > + if (vendor_id != 0x1af4) { > > + return false; > > + } > > + > > + /* > > + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive > > + * is a virtio device" > > + */ > > + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); > > + if (device_id < 0x1000 || device_id > 0x107f) { > > + return false; > > + } > > + > > + /* > > + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to > > + * 0x103F depending on the device type" > > + */ > > + if (device_id < 0x1040) { > > + /* > > + * "Transitional devices MUST have the PCI Subsystem Device ID matching > > + * the Virtio Device ID" > > + */ > > + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); > > Shouldn't you return "false" here in case the device_type is 0 ? Which > likely means that it is a legacy or broken device ...? The real decision whether to use this PCI device or not happens in probe_device_layout(). If it's broken or a legacy device then that function will fail. Stefan
On 17/10/2019 18.07, Stefan Hajnoczi wrote: > On Thu, Oct 17, 2019 at 04:52:54PM +0200, Thomas Huth wrote: >> On 11/10/2019 10.56, Stefan Hajnoczi wrote: >>> Implement the VIRTIO 1.0 virtio-pci interface. The main change here is >>> that the register layout is no longer a fixed layout in BAR 0. Instead >>> we have to iterate of PCI Capabilities to find descriptions of where >>> various registers are located. The vring registers are also more >>> fine-grained, allowing for more flexible vring layouts, but we don't >>> take advantage of that. >>> >>> Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are >>> therefore not running in VIRTIO 1.0 mode. >>> >>> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> >>> --- >>> tests/Makefile.include | 1 + >>> tests/libqos/virtio-pci-modern.h | 17 ++ >>> tests/libqos/virtio-pci.h | 10 + >>> tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ >>> tests/libqos/virtio-pci.c | 6 +- >>> 5 files changed, 445 insertions(+), 1 deletion(-) >>> create mode 100644 tests/libqos/virtio-pci-modern.h >>> create mode 100644 tests/libqos/virtio-pci-modern.c >> [...] >>> +static bool probe_device_type(QVirtioPCIDevice *dev) >>> +{ >>> + uint16_t vendor_id; >>> + uint16_t device_id; >>> + >>> + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ >>> + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); >>> + if (vendor_id != 0x1af4) { >>> + return false; >>> + } >>> + >>> + /* >>> + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive >>> + * is a virtio device" >>> + */ >>> + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); >>> + if (device_id < 0x1000 || device_id > 0x107f) { >>> + return false; >>> + } >>> + >>> + /* >>> + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to >>> + * 0x103F depending on the device type" >>> + */ >>> + if (device_id < 0x1040) { >>> + /* >>> + * "Transitional devices MUST have the PCI Subsystem Device ID matching >>> + * the Virtio Device ID" >>> + */ >>> + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); >> >> Shouldn't you return "false" here in case the device_type is 0 ? Which >> likely means that it is a legacy or broken device ...? > > The real decision whether to use this PCI device or not happens in > probe_device_layout(). If it's broken or a legacy device then that > function will fail. Ok, fair. I've added the patches to my qtest-next branch: https://gitlab.com/huth/qemu/tree/qtest-next Thomas
On 17/10/2019 18.18, Thomas Huth wrote: > On 17/10/2019 18.07, Stefan Hajnoczi wrote: >> On Thu, Oct 17, 2019 at 04:52:54PM +0200, Thomas Huth wrote: >>> On 11/10/2019 10.56, Stefan Hajnoczi wrote: >>>> Implement the VIRTIO 1.0 virtio-pci interface. The main change here is >>>> that the register layout is no longer a fixed layout in BAR 0. Instead >>>> we have to iterate of PCI Capabilities to find descriptions of where >>>> various registers are located. The vring registers are also more >>>> fine-grained, allowing for more flexible vring layouts, but we don't >>>> take advantage of that. >>>> >>>> Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are >>>> therefore not running in VIRTIO 1.0 mode. >>>> >>>> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> >>>> --- >>>> tests/Makefile.include | 1 + >>>> tests/libqos/virtio-pci-modern.h | 17 ++ >>>> tests/libqos/virtio-pci.h | 10 + >>>> tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ >>>> tests/libqos/virtio-pci.c | 6 +- >>>> 5 files changed, 445 insertions(+), 1 deletion(-) >>>> create mode 100644 tests/libqos/virtio-pci-modern.h >>>> create mode 100644 tests/libqos/virtio-pci-modern.c >>> [...] >>>> +static bool probe_device_type(QVirtioPCIDevice *dev) >>>> +{ >>>> + uint16_t vendor_id; >>>> + uint16_t device_id; >>>> + >>>> + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ >>>> + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); >>>> + if (vendor_id != 0x1af4) { >>>> + return false; >>>> + } >>>> + >>>> + /* >>>> + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive >>>> + * is a virtio device" >>>> + */ >>>> + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); >>>> + if (device_id < 0x1000 || device_id > 0x107f) { >>>> + return false; >>>> + } >>>> + >>>> + /* >>>> + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to >>>> + * 0x103F depending on the device type" >>>> + */ >>>> + if (device_id < 0x1040) { >>>> + /* >>>> + * "Transitional devices MUST have the PCI Subsystem Device ID matching >>>> + * the Virtio Device ID" >>>> + */ >>>> + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); >>> >>> Shouldn't you return "false" here in case the device_type is 0 ? Which >>> likely means that it is a legacy or broken device ...? >> >> The real decision whether to use this PCI device or not happens in >> probe_device_layout(). If it's broken or a legacy device then that >> function will fail. > > Ok, fair. > > I've added the patches to my qtest-next branch: > > https://gitlab.com/huth/qemu/tree/qtest-next Hi Stephan, looks like this is breaking the virtio-blk-test in certain configurations: https://gitlab.com/huth/qemu/-/jobs/324085741 and: https://cirrus-ci.com/task/4511314474434560 Could you please have a look? Thanks, Thomas
On 18/10/2019 08.48, Thomas Huth wrote: > On 17/10/2019 18.18, Thomas Huth wrote: >> On 17/10/2019 18.07, Stefan Hajnoczi wrote: >>> On Thu, Oct 17, 2019 at 04:52:54PM +0200, Thomas Huth wrote: >>>> On 11/10/2019 10.56, Stefan Hajnoczi wrote: >>>>> Implement the VIRTIO 1.0 virtio-pci interface. The main change here is >>>>> that the register layout is no longer a fixed layout in BAR 0. Instead >>>>> we have to iterate of PCI Capabilities to find descriptions of where >>>>> various registers are located. The vring registers are also more >>>>> fine-grained, allowing for more flexible vring layouts, but we don't >>>>> take advantage of that. >>>>> >>>>> Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are >>>>> therefore not running in VIRTIO 1.0 mode. >>>>> >>>>> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> >>>>> --- >>>>> tests/Makefile.include | 1 + >>>>> tests/libqos/virtio-pci-modern.h | 17 ++ >>>>> tests/libqos/virtio-pci.h | 10 + >>>>> tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ >>>>> tests/libqos/virtio-pci.c | 6 +- >>>>> 5 files changed, 445 insertions(+), 1 deletion(-) >>>>> create mode 100644 tests/libqos/virtio-pci-modern.h >>>>> create mode 100644 tests/libqos/virtio-pci-modern.c >>>> [...] >>>>> +static bool probe_device_type(QVirtioPCIDevice *dev) >>>>> +{ >>>>> + uint16_t vendor_id; >>>>> + uint16_t device_id; >>>>> + >>>>> + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ >>>>> + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); >>>>> + if (vendor_id != 0x1af4) { >>>>> + return false; >>>>> + } >>>>> + >>>>> + /* >>>>> + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive >>>>> + * is a virtio device" >>>>> + */ >>>>> + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); >>>>> + if (device_id < 0x1000 || device_id > 0x107f) { >>>>> + return false; >>>>> + } >>>>> + >>>>> + /* >>>>> + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to >>>>> + * 0x103F depending on the device type" >>>>> + */ >>>>> + if (device_id < 0x1040) { >>>>> + /* >>>>> + * "Transitional devices MUST have the PCI Subsystem Device ID matching >>>>> + * the Virtio Device ID" >>>>> + */ >>>>> + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); >>>> >>>> Shouldn't you return "false" here in case the device_type is 0 ? Which >>>> likely means that it is a legacy or broken device ...? >>> >>> The real decision whether to use this PCI device or not happens in >>> probe_device_layout(). If it's broken or a legacy device then that >>> function will fail. >> >> Ok, fair. >> >> I've added the patches to my qtest-next branch: >> >> https://gitlab.com/huth/qemu/tree/qtest-next > > Hi Stephan, > > looks like this is breaking the virtio-blk-test in certain configurations: > > https://gitlab.com/huth/qemu/-/jobs/324085741 > > and: > > https://cirrus-ci.com/task/4511314474434560 > > Could you please have a look? FWIW, the assertion failed (capacity == TEST_IMAGE_SIZE / 512): (2199023255552 == 131072) looks like an endianess bug to me. 2199023255552 is 131072 byte-swapped. Thomas
On Fri, Oct 18, 2019 at 08:48:23AM +0200, Thomas Huth wrote: > On 17/10/2019 18.18, Thomas Huth wrote: > > On 17/10/2019 18.07, Stefan Hajnoczi wrote: > >> On Thu, Oct 17, 2019 at 04:52:54PM +0200, Thomas Huth wrote: > >>> On 11/10/2019 10.56, Stefan Hajnoczi wrote: > >>>> Implement the VIRTIO 1.0 virtio-pci interface. The main change here is > >>>> that the register layout is no longer a fixed layout in BAR 0. Instead > >>>> we have to iterate of PCI Capabilities to find descriptions of where > >>>> various registers are located. The vring registers are also more > >>>> fine-grained, allowing for more flexible vring layouts, but we don't > >>>> take advantage of that. > >>>> > >>>> Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are > >>>> therefore not running in VIRTIO 1.0 mode. > >>>> > >>>> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> > >>>> --- > >>>> tests/Makefile.include | 1 + > >>>> tests/libqos/virtio-pci-modern.h | 17 ++ > >>>> tests/libqos/virtio-pci.h | 10 + > >>>> tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ > >>>> tests/libqos/virtio-pci.c | 6 +- > >>>> 5 files changed, 445 insertions(+), 1 deletion(-) > >>>> create mode 100644 tests/libqos/virtio-pci-modern.h > >>>> create mode 100644 tests/libqos/virtio-pci-modern.c > >>> [...] > >>>> +static bool probe_device_type(QVirtioPCIDevice *dev) > >>>> +{ > >>>> + uint16_t vendor_id; > >>>> + uint16_t device_id; > >>>> + > >>>> + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ > >>>> + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); > >>>> + if (vendor_id != 0x1af4) { > >>>> + return false; > >>>> + } > >>>> + > >>>> + /* > >>>> + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive > >>>> + * is a virtio device" > >>>> + */ > >>>> + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); > >>>> + if (device_id < 0x1000 || device_id > 0x107f) { > >>>> + return false; > >>>> + } > >>>> + > >>>> + /* > >>>> + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to > >>>> + * 0x103F depending on the device type" > >>>> + */ > >>>> + if (device_id < 0x1040) { > >>>> + /* > >>>> + * "Transitional devices MUST have the PCI Subsystem Device ID matching > >>>> + * the Virtio Device ID" > >>>> + */ > >>>> + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); > >>> > >>> Shouldn't you return "false" here in case the device_type is 0 ? Which > >>> likely means that it is a legacy or broken device ...? > >> > >> The real decision whether to use this PCI device or not happens in > >> probe_device_layout(). If it's broken or a legacy device then that > >> function will fail. > > > > Ok, fair. > > > > I've added the patches to my qtest-next branch: > > > > https://gitlab.com/huth/qemu/tree/qtest-next > > Hi Stephan, > > looks like this is breaking the virtio-blk-test in certain configurations: > > https://gitlab.com/huth/qemu/-/jobs/324085741 > > and: > > https://cirrus-ci.com/task/4511314474434560 > > Could you please have a look? On reading the VIRTIO specification again, I think my idea of supporting the VIRTIO 1.0 PCI interface without actually negotiating the VIRTIO_F_VERSION_1 feature bit is non-compliant: 2.2.3 Legacy Interface: A Note on Feature Bits Transitional Drivers MUST detect Legacy Devices by detecting that the feature bit VIRTIO_F_VERSION_1 is not offered. [...] In this case device is used through the legacy interface. Please drop this patch series for now. Additional patches are required to implement VIRTIO_F_VERSION_1 and then the endianness issue will go away. I will send a v2. Stefan
diff --git a/tests/Makefile.include b/tests/Makefile.include index 3543451ed3..3f633c8313 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -715,6 +715,7 @@ qos-test-obj-y += tests/libqos/virtio-blk.o qos-test-obj-y += tests/libqos/virtio-mmio.o qos-test-obj-y += tests/libqos/virtio-net.o qos-test-obj-y += tests/libqos/virtio-pci.o +qos-test-obj-y += tests/libqos/virtio-pci-modern.o qos-test-obj-y += tests/libqos/virtio-rng.o qos-test-obj-y += tests/libqos/virtio-scsi.o qos-test-obj-y += tests/libqos/virtio-serial.o diff --git a/tests/libqos/virtio-pci-modern.h b/tests/libqos/virtio-pci-modern.h new file mode 100644 index 0000000000..6bf2b207c3 --- /dev/null +++ b/tests/libqos/virtio-pci-modern.h @@ -0,0 +1,17 @@ +/* + * libqos virtio PCI VIRTIO 1.0 definitions + * + * Copyright (c) 2019 Red Hat, Inc + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef LIBQOS_VIRTIO_PCI_MODERN_H +#define LIBQOS_VIRTIO_PCI_MODERN_H + +#include "virtio-pci.h" + +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev); + +#endif /* LIBQOS_VIRTIO_PCI_MODERN_H */ diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h index f2d53aa377..29d4e9bf79 100644 --- a/tests/libqos/virtio-pci.h +++ b/tests/libqos/virtio-pci.h @@ -27,6 +27,13 @@ typedef struct QVirtioPCIDevice { uint32_t config_msix_data; uint8_t bar_idx; + + /* VIRTIO 1.0 */ + uint32_t common_cfg_offset; + uint32_t notify_cfg_offset; + uint32_t notify_off_multiplier; + uint32_t isr_cfg_offset; + uint32_t device_cfg_offset; } QVirtioPCIDevice; struct QVirtioPCIMSIXOps { @@ -43,6 +50,9 @@ typedef struct QVirtQueuePCI { uint16_t msix_entry; uint64_t msix_addr; uint32_t msix_data; + + /* VIRTIO 1.0 */ + uint64_t notify_offset; } QVirtQueuePCI; void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr); diff --git a/tests/libqos/virtio-pci-modern.c b/tests/libqos/virtio-pci-modern.c new file mode 100644 index 0000000000..f23c876290 --- /dev/null +++ b/tests/libqos/virtio-pci-modern.c @@ -0,0 +1,412 @@ +/* + * libqos VIRTIO 1.0 PCI driver + * + * Copyright (c) 2019 Red Hat, Inc + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "standard-headers/linux/pci_regs.h" +#include "standard-headers/linux/virtio_pci.h" +#include "virtio-pci-modern.h" + +static uint8_t config_readb(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint16_t config_readw(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readw(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint32_t config_readl(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint64_t config_readq(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readq(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint32_t get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_feature_select), + 0); + return qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_feature)); +} + +static void set_features(QVirtioDevice *d, uint32_t features) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 0); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature), + features); +} + +static uint32_t get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 0); + return qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature)); +} + +static uint8_t get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_readb(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_status)); +} + +static void set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_writeb(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_status), + status); +} + +static bool get_msix_status(QVirtioPCIDevice *dev, uint32_t msix_entry, + uint32_t msix_addr, uint32_t msix_data) +{ + uint32_t data; + + g_assert_cmpint(msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, msix_entry); + } + + data = qtest_readl(dev->pdev->bus->qts, msix_addr); + if (data == msix_data) { + qtest_writel(dev->pdev->bus->qts, msix_addr, 0); + return true; + } else { + return false; + } +} + +static bool get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + if (dev->pdev->msix_enabled) { + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + return get_msix_status(dev, vqpci->msix_entry, vqpci->msix_addr, + vqpci->msix_data); + } + + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 1; +} + +static bool get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + if (dev->pdev->msix_enabled) { + return get_msix_status(dev, dev->config_msix_entry, + dev->config_msix_addr, dev->config_msix_data); + } + + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 2; +} + +static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + gint64 start_time = g_get_monotonic_time(); + + do { + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + qtest_clock_step(dev->pdev->bus->qts, 100); + } while (!get_config_isr_status(d)); +} + +static void queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_select), + index); +} + +static uint16_t get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_size)); +} + +static void set_queue_address(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_desc_lo), + vq->desc); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_desc_hi), + vq->desc >> 32); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_avail_lo), + vq->avail); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_avail_hi), + vq->avail >> 32); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_used_lo), + vq->used); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_used_hi), + vq->used >> 32); +} + +static QVirtQueue *virtqueue_setup(QVirtioDevice *d, QGuestAllocator *alloc, + uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueue *vq; + QVirtQueuePCI *vqpci; + uint16_t notify_off; + + vq = qvirtio_pci_virtqueue_setup_common(d, alloc, index); + vqpci = container_of(vq, QVirtQueuePCI, vq); + + notify_off = qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + queue_notify_off)); + + vqpci->notify_offset = dev->notify_cfg_offset + + notify_off * dev->notify_off_multiplier; + + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_enable), 1); + + return vq; +} + +static void virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + qpci_io_writew(dev->pdev, dev->bar, vqpci->notify_offset, vq->index); +} + +static const QVirtioBus qvirtio_pci_virtio_1 = { + .config_readb = config_readb, + .config_readw = config_readw, + .config_readl = config_readl, + .config_readq = config_readq, + .get_features = get_features, + .set_features = set_features, + .get_guest_features = get_guest_features, + .get_status = get_status, + .set_status = set_status, + .get_queue_isr_status = get_queue_isr_status, + .wait_config_isr_status = wait_config_isr_status, + .queue_select = queue_select, + .get_queue_size = get_queue_size, + .set_queue_address = set_queue_address, + .virtqueue_setup = virtqueue_setup, + .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, + .virtqueue_kick = virtqueue_kick, +}; + +static void set_config_vector(QVirtioPCIDevice *d, uint16_t entry) +{ + uint16_t vector; + + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, msix_config), entry); + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + msix_config)); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static void set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, + uint16_t entry) +{ + uint16_t vector; + + queue_select(&d->vdev, vq_idx); + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_msix_vector), + entry); + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + queue_msix_vector)); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_virtio_1 = { + .set_config_vector = set_config_vector, + .set_queue_vector = set_queue_vector, +}; + +static bool probe_device_type(QVirtioPCIDevice *dev) +{ + uint16_t vendor_id; + uint16_t device_id; + + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); + if (vendor_id != 0x1af4) { + return false; + } + + /* + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive + * is a virtio device" + */ + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); + if (device_id < 0x1000 || device_id > 0x107f) { + return false; + } + + /* + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to + * 0x103F depending on the device type" + */ + if (device_id < 0x1040) { + /* + * "Transitional devices MUST have the PCI Subsystem Device ID matching + * the Virtio Device ID" + */ + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); + } else { + /* + * "The PCI Device ID is calculated by adding 0x1040 to the Virtio + * Device ID" + */ + dev->vdev.device_type = device_id - 0x1040; + } + + return true; +} + +/* Find the first VIRTIO 1.0 PCI structure for a given type */ +static bool find_structure(QVirtioPCIDevice *dev, uint8_t cfg_type, + uint8_t *bar, uint32_t *offset, uint32_t *length, + uint8_t *cfg_addr) +{ + uint8_t addr = 0; + + while ((addr = qpci_find_capability(dev->pdev, PCI_CAP_ID_VNDR, + addr)) != 0) { + uint8_t type; + + type = qpci_config_readb(dev->pdev, + addr + offsetof(struct virtio_pci_cap, cfg_type)); + if (type != cfg_type) { + continue; + } + + *bar = qpci_config_readb(dev->pdev, + addr + offsetof(struct virtio_pci_cap, bar)); + *offset = qpci_config_readl(dev->pdev, + addr + offsetof(struct virtio_pci_cap, offset)); + *length = qpci_config_readl(dev->pdev, + addr + offsetof(struct virtio_pci_cap, length)); + if (cfg_addr) { + *cfg_addr = addr; + } + + return true; + } + + return false; +} + +static bool probe_device_layout(QVirtioPCIDevice *dev) +{ + uint8_t bar; + uint8_t cfg_addr; + uint32_t length; + + /* + * Due to the qpci_iomap() API we only support devices that put all + * structures in the same PCI BAR. Luckily this is true with QEMU. + */ + + if (!find_structure(dev, VIRTIO_PCI_CAP_COMMON_CFG, &dev->bar_idx, + &dev->common_cfg_offset, &length, NULL)) { + return false; + } + + if (!find_structure(dev, VIRTIO_PCI_CAP_NOTIFY_CFG, &bar, + &dev->notify_cfg_offset, &length, &cfg_addr)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + dev->notify_off_multiplier = qpci_config_readl(dev->pdev, + cfg_addr + offsetof(struct virtio_pci_notify_cap, + notify_off_multiplier)); + + if (!find_structure(dev, VIRTIO_PCI_CAP_ISR_CFG, &bar, + &dev->isr_cfg_offset, &length, NULL)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + if (!find_structure(dev, VIRTIO_PCI_CAP_DEVICE_CFG, &bar, + &dev->device_cfg_offset, &length, NULL)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + return true; +} + +/* Probe a VIRTIO 1.0 device */ +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev) +{ + if (!probe_device_type(dev)) { + return false; + } + + if (!probe_device_layout(dev)) { + return false; + } + + dev->vdev.bus = &qvirtio_pci_virtio_1; + dev->msix_ops = &qvirtio_pci_msix_ops_virtio_1; + dev->vdev.big_endian = false; + return true; +} diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index efd8caee18..5bdf403351 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -22,6 +22,8 @@ #include "hw/pci/pci.h" #include "hw/pci/pci_regs.h" +#include "virtio-pci-modern.h" + /* virtio-pci is a superclass of all virtio-xxx-pci devices; * the relation between virtio-pci and virtio-xxx-pci is implicit, * and therefore virtio-pci does not produce virtio and is not @@ -400,7 +402,9 @@ static void qvirtio_pci_init_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_ dev->pdev = pci_dev; dev->config_msix_entry = -1; - qvirtio_pci_init_legacy(dev); + if (!qvirtio_pci_init_virtio_1(dev)) { + qvirtio_pci_init_legacy(dev); + } /* each virtio-xxx-pci device should override at least this function */ dev->obj.get_driver = NULL;
Implement the VIRTIO 1.0 virtio-pci interface. The main change here is that the register layout is no longer a fixed layout in BAR 0. Instead we have to iterate of PCI Capabilities to find descriptions of where various registers are located. The vring registers are also more fine-grained, allowing for more flexible vring layouts, but we don't take advantage of that. Note that test cases do not negotiate VIRTIO_F_VERSION_1 yet and are therefore not running in VIRTIO 1.0 mode. Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> --- tests/Makefile.include | 1 + tests/libqos/virtio-pci-modern.h | 17 ++ tests/libqos/virtio-pci.h | 10 + tests/libqos/virtio-pci-modern.c | 412 +++++++++++++++++++++++++++++++ tests/libqos/virtio-pci.c | 6 +- 5 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 tests/libqos/virtio-pci-modern.h create mode 100644 tests/libqos/virtio-pci-modern.c