diff options
Diffstat (limited to 'tests/qtest/libqos/virtio-pci.c')
-rw-r--r-- | tests/qtest/libqos/virtio-pci.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/tests/qtest/libqos/virtio-pci.c b/tests/qtest/libqos/virtio-pci.c new file mode 100644 index 0000000000..62851c29bb --- /dev/null +++ b/tests/qtest/libqos/virtio-pci.c @@ -0,0 +1,435 @@ +/* + * libqos virtio PCI driver + * + * Copyright (c) 2014 Marc MarĂ + * + * 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 "libqtest.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" +#include "libqos/qgraph.h" +#include "standard-headers/linux/virtio_ring.h" +#include "standard-headers/linux/virtio_pci.h" + +#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 + * reached by any edge, not even as a "contains" edge. + * In facts, every device is a QVirtioPCIDevice with + * additional fields, since every one has its own + * number of queues and various attributes. + * Virtio-pci provides default functions to start the + * hw and destroy the object, and nodes that want to + * override them should always remember to call the + * original qvirtio_pci_destructor and qvirtio_pci_start_hw. + */ + +#define CONFIG_BASE(dev) (VIRTIO_PCI_CONFIG_OFF((dev)->pdev->msix_enabled)) + +static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); +} + +/* PCI is always read in little-endian order + * but virtio ( < 1.0) is in guest order + * so with a big-endian guest the order has been reversed, + * reverse it again + * virtio-1.0 is always little-endian, like PCI + */ + +static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint16_t value; + + value = qpci_io_readw(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + value = bswap16(value); + } + return value; +} + +static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint32_t value; + + value = qpci_io_readl(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + value = bswap32(value); + } + return value; +} + +static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t val; + + val = qpci_io_readq(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + val = bswap64(val); + } + + return val; +} + +static uint64_t qvirtio_pci_get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_HOST_FEATURES); +} + +static void qvirtio_pci_set_features(QVirtioDevice *d, uint64_t features) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES, features); +} + +static uint64_t qvirtio_pci_get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES); +} + +static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS); +} + +static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS, status); +} + +static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq; + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(vqpci->msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, vqpci->msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, vqpci->msix_entry); + } else { + data = qtest_readl(dev->pdev->bus->qts, vqpci->msix_addr); + if (data == vqpci->msix_data) { + qtest_writel(dev->pdev->bus->qts, vqpci->msix_addr, 0); + return true; + } else { + return false; + } + } + } else { + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 1; + } +} + +static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(dev->config_msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, dev->config_msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, dev->config_msix_entry); + } else { + data = qtest_readl(dev->pdev->bus->qts, dev->config_msix_addr); + if (data == dev->config_msix_data) { + qtest_writel(dev->pdev->bus->qts, dev->config_msix_addr, 0); + return true; + } else { + return false; + } + } + } else { + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 2; + } +} + +static void qvirtio_pci_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 (!qvirtio_pci_get_config_isr_status(d)); +} + +static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_SEL, index); +} + +static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readw(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NUM); +} + +static void qvirtio_pci_set_queue_address(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t pfn = vq->desc / VIRTIO_PCI_VRING_ALIGN; + + qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_PFN, pfn); +} + +QVirtQueue *qvirtio_pci_virtqueue_setup_common(QVirtioDevice *d, + QGuestAllocator *alloc, + uint16_t index) +{ + uint64_t feat; + uint64_t addr; + QVirtQueuePCI *vqpci; + QVirtioPCIDevice *qvpcidev = container_of(d, QVirtioPCIDevice, vdev); + + vqpci = g_malloc0(sizeof(*vqpci)); + feat = d->bus->get_guest_features(d); + + d->bus->queue_select(d, index); + vqpci->vq.vdev = d; + vqpci->vq.index = index; + vqpci->vq.size = d->bus->get_queue_size(d); + vqpci->vq.free_head = 0; + vqpci->vq.num_free = vqpci->vq.size; + vqpci->vq.align = VIRTIO_PCI_VRING_ALIGN; + vqpci->vq.indirect = feat & (1ull << VIRTIO_RING_F_INDIRECT_DESC); + vqpci->vq.event = feat & (1ull << VIRTIO_RING_F_EVENT_IDX); + + vqpci->msix_entry = -1; + vqpci->msix_addr = 0; + vqpci->msix_data = 0x12345678; + + /* Check different than 0 */ + g_assert_cmpint(vqpci->vq.size, !=, 0); + + /* Check power of 2 */ + g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0); + + addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, + VIRTIO_PCI_VRING_ALIGN)); + qvring_init(qvpcidev->pdev->bus->qts, alloc, &vqpci->vq, addr); + d->bus->set_queue_address(d, &vqpci->vq); + + return &vqpci->vq; +} + +void qvirtio_pci_virtqueue_cleanup_common(QVirtQueue *vq, + QGuestAllocator *alloc) +{ + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + guest_free(alloc, vq->desc); + g_free(vqpci); +} + +static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writew(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NOTIFY, vq->index); +} + +static const QVirtioBus qvirtio_pci_legacy = { + .config_readb = qvirtio_pci_config_readb, + .config_readw = qvirtio_pci_config_readw, + .config_readl = qvirtio_pci_config_readl, + .config_readq = qvirtio_pci_config_readq, + .get_features = qvirtio_pci_get_features, + .set_features = qvirtio_pci_set_features, + .get_guest_features = qvirtio_pci_get_guest_features, + .get_status = qvirtio_pci_get_status, + .set_status = qvirtio_pci_set_status, + .get_queue_isr_status = qvirtio_pci_get_queue_isr_status, + .wait_config_isr_status = qvirtio_pci_wait_config_isr_status, + .queue_select = qvirtio_pci_queue_select, + .get_queue_size = qvirtio_pci_get_queue_size, + .set_queue_address = qvirtio_pci_set_queue_address, + .virtqueue_setup = qvirtio_pci_virtqueue_setup_common, + .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, + .virtqueue_kick = qvirtio_pci_virtqueue_kick, +}; + +static void qvirtio_pci_set_config_vector(QVirtioPCIDevice *d, uint16_t entry) +{ + uint16_t vector; + + qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static void qvirtio_pci_set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, + uint16_t entry) +{ + uint16_t vector; + + qvirtio_pci_queue_select(&d->vdev, vq_idx); + qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_legacy = { + .set_config_vector = qvirtio_pci_set_config_vector, + .set_queue_vector = qvirtio_pci_set_queue_vector, +}; + +void qvirtio_pci_device_enable(QVirtioPCIDevice *d) +{ + qpci_device_enable(d->pdev); + d->bar = qpci_iomap(d->pdev, d->bar_idx, NULL); +} + +void qvirtio_pci_device_disable(QVirtioPCIDevice *d) +{ + qpci_iounmap(d->pdev, d->bar); +} + +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry) +{ + uint32_t control; + uint64_t off; + + g_assert(d->pdev->msix_enabled); + off = d->pdev->msix_table_off + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + vqpci->msix_entry = entry; + + vqpci->msix_addr = guest_alloc(alloc, 4); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_LOWER_ADDR, vqpci->msix_addr & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_UPPER_ADDR, + (vqpci->msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_DATA, vqpci->msix_data); + + control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + d->msix_ops->set_queue_vector(d, vqpci->vq.index, entry); +} + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry) +{ + uint32_t control; + uint64_t off; + + g_assert(d->pdev->msix_enabled); + off = d->pdev->msix_table_off + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + d->config_msix_entry = entry; + + d->config_msix_data = 0x12345678; + d->config_msix_addr = guest_alloc(alloc, 4); + + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_LOWER_ADDR, d->config_msix_addr & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_UPPER_ADDR, + (d->config_msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_DATA, d->config_msix_data); + + control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + d->msix_ops->set_config_vector(d, entry); +} + +void qvirtio_pci_destructor(QOSGraphObject *obj) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; + qvirtio_pci_device_disable(dev); + g_free(dev->pdev); +} + +void qvirtio_pci_start_hw(QOSGraphObject *obj) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; + qvirtio_pci_device_enable(dev); + qvirtio_start_device(&dev->vdev); +} + +static void qvirtio_pci_init_legacy(QVirtioPCIDevice *dev) +{ + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); + dev->bar_idx = 0; + dev->vdev.bus = &qvirtio_pci_legacy; + dev->msix_ops = &qvirtio_pci_msix_ops_legacy; + dev->vdev.big_endian = qtest_big_endian(dev->pdev->bus->qts); +} + +static void qvirtio_pci_init_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_dev) +{ + dev->pdev = pci_dev; + dev->config_msix_entry = -1; + + 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; + dev->obj.start_hw = qvirtio_pci_start_hw; + dev->obj.destructor = qvirtio_pci_destructor; +} + +void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr) +{ + QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); + g_assert_nonnull(pci_dev); + qvirtio_pci_init_from_pcidev(dev, pci_dev); +} + +QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr) +{ + QVirtioPCIDevice *dev; + QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); + if (!pci_dev) { + return NULL; + } + + dev = g_new0(QVirtioPCIDevice, 1); + qvirtio_pci_init_from_pcidev(dev, pci_dev); + dev->obj.free = g_free; + return dev; +} |