From 6bb4515849315460bdea45fa56ca656cead7c8ed Mon Sep 17 00:00:00 2001 From: Liu Yuan Date: Mon, 1 Sep 2014 13:35:21 +0800 Subject: [PATCH 01/24] block: kill tail whitespace in block.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Kevin Wolf Cc: Stefan Hajnoczi Signed-off-by: Liu Yuan Reviewed-by: Benoît Canet Signed-off-by: Stefan Hajnoczi --- block.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block.c b/block.c index cb670fd54d..d06dd51632 100644 --- a/block.c +++ b/block.c @@ -2246,7 +2246,7 @@ int bdrv_commit(BlockDriverState *bs) if (!drv) return -ENOMEDIUM; - + if (!bs->backing_hd) { return -ENOTSUP; } From afeb25f9263e470ad715cab2b79b8965c0519fb7 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Sat, 23 Aug 2014 12:19:06 +0200 Subject: [PATCH 02/24] pflash_cfi01: fixup stale DPRINTF() calls Signed-off-by: Laszlo Ersek Signed-off-by: Stefan Hajnoczi --- hw/block/pflash_cfi01.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c index 2238f39579..fddef39982 100644 --- a/hw/block/pflash_cfi01.c +++ b/hw/block/pflash_cfi01.c @@ -209,11 +209,11 @@ static uint32_t pflash_devid_query(pflash_t *pfl, hwaddr offset) switch (boff & 0xFF) { case 0: resp = pfl->ident0; - DPRINTF("%s: Manufacturer Code %04x\n", __func__, ret); + DPRINTF("%s: Manufacturer Code %04x\n", __func__, resp); break; case 1: resp = pfl->ident1; - DPRINTF("%s: Device ID Code %04x\n", __func__, ret); + DPRINTF("%s: Device ID Code %04x\n", __func__, resp); break; default: DPRINTF("%s: Read Device Information offset=%x\n", __func__, From 4c0cfc72b31a79f737a64ebbe0411e4b83e25771 Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Sat, 23 Aug 2014 12:19:07 +0200 Subject: [PATCH 03/24] pflash_cfi01: write flash contents to bdrv on incoming migration A drive that backs a pflash device is special: - it is very small, - its entire contents are kept in a RAMBlock at all times, covering the guest-phys address range that provides the guest's view of the emulated flash chip. The pflash device model keeps the drive (the host-side file) and the guest-visible flash contents in sync. When migrating the guest, the guest-visible flash contents (the RAMBlock) is migrated by default, but on the target host, the drive (the host-side file) remains in full sync with the RAMBlock only if: - the source and target hosts share the storage underlying the pflash drive, - or the migration requests full or incremental block migration too, which then covers all drives. Due to the special nature of pflash drives, the following scenario makes sense as well: - no full nor incremental block migration, covering all drives, alongside the base migration (justified eg. by shared storage for "normal" (big) drives), - non-shared storage for pflash drives. In this case, currently only those portions of the flash drive are updated on the target disk that the guest reprograms while running on the target host. In order to restore accord, dump the entire flash contents to the bdrv in a post_load() callback. - The read-only check follows the other call-sites of pflash_update(); - both "pfl->ro" and pflash_update() reflect / consider the case when "pfl->bs" is NULL; - the total size of the flash device is calculated as in pflash_cfi01_realize(). When using shared storage, or requesting full or incremental block migration along with the normal migration, the patch should incur a harmless rewrite from the target side. It is assumed that, on the target host, RAM is loaded ahead of the call to pflash_post_load(). Suggested-by: Paolo Bonzini Signed-off-by: Laszlo Ersek Signed-off-by: Stefan Hajnoczi --- hw/block/pflash_cfi01.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c index fddef39982..593fbc5525 100644 --- a/hw/block/pflash_cfi01.c +++ b/hw/block/pflash_cfi01.c @@ -94,10 +94,13 @@ struct pflash_t { void *storage; }; +static int pflash_post_load(void *opaque, int version_id); + static const VMStateDescription vmstate_pflash = { .name = "pflash_cfi01", .version_id = 1, .minimum_version_id = 1, + .post_load = pflash_post_load, .fields = (VMStateField[]) { VMSTATE_UINT8(wcycle, pflash_t), VMSTATE_UINT8(cmd, pflash_t), @@ -982,3 +985,14 @@ MemoryRegion *pflash_cfi01_get_memory(pflash_t *fl) { return &fl->mem; } + +static int pflash_post_load(void *opaque, int version_id) +{ + pflash_t *pfl = opaque; + + if (!pfl->ro) { + DPRINTF("%s: updating bdrv for %s\n", __func__, pfl->name); + pflash_update(pfl, 0, pfl->sector_len * pfl->nb_blocs); + } + return 0; +} From 311e666aea7164b6d3b8a7e845fb32a509bfdf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:54 +0200 Subject: [PATCH 04/24] tests: Functions bus_foreach and device_find from libqos virtio API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Virtio header has been changed to compile and work with a real device. Functions bus_foreach and device_find have been implemented for PCI. Virtio-blk test case now opens a fake device. Reviewed-by: Stefan Hajnoczi Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/Makefile | 3 +- tests/libqos/virtio-pci.c | 75 +++++++++++++++++++++++++++++++++++++++ tests/libqos/virtio-pci.h | 24 +++++++++++++ tests/libqos/virtio.h | 23 ++++++++++++ tests/virtio-blk-test.c | 61 ++++++++++++++++++++++++++----- 5 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 tests/libqos/virtio-pci.c create mode 100644 tests/libqos/virtio-pci.h create mode 100644 tests/libqos/virtio.h diff --git a/tests/Makefile b/tests/Makefile index 469c0a5e44..0572633774 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -299,6 +299,7 @@ libqos-obj-y += tests/libqos/i2c.o libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o libqos-pc-obj-y += tests/libqos/malloc-pc.o libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o +libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio-pci.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o @@ -320,7 +321,7 @@ tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o -tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o +tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o tests/virtio-scsi-test$(EXESUF): tests/virtio-scsi-test.o diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c new file mode 100644 index 0000000000..fde1b1f747 --- /dev/null +++ b/tests/libqos/virtio-pci.c @@ -0,0 +1,75 @@ +/* + * 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 +#include "libqtest.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" + +#include "hw/pci/pci_regs.h" + +typedef struct QVirtioPCIForeachData { + void (*func)(QVirtioDevice *d, void *data); + uint16_t device_type; + void *user_data; +} QVirtioPCIForeachData; + +static QVirtioPCIDevice *qpcidevice_to_qvirtiodevice(QPCIDevice *pdev) +{ + QVirtioPCIDevice *vpcidev; + vpcidev = g_malloc0(sizeof(*vpcidev)); + + if (pdev) { + vpcidev->pdev = pdev; + vpcidev->vdev.device_type = + qpci_config_readw(vpcidev->pdev, PCI_SUBSYSTEM_ID); + } + + return vpcidev; +} + +static void qvirtio_pci_foreach_callback( + QPCIDevice *dev, int devfn, void *data) +{ + QVirtioPCIForeachData *d = data; + QVirtioPCIDevice *vpcidev = qpcidevice_to_qvirtiodevice(dev); + + if (vpcidev->vdev.device_type == d->device_type) { + d->func(&vpcidev->vdev, d->user_data); + } else { + g_free(vpcidev); + } +} + +static void qvirtio_pci_assign_device(QVirtioDevice *d, void *data) +{ + QVirtioPCIDevice **vpcidev = data; + *vpcidev = (QVirtioPCIDevice *)d; +} + +void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, + void (*func)(QVirtioDevice *d, void *data), void *data) +{ + QVirtioPCIForeachData d = { .func = func, + .device_type = device_type, + .user_data = data }; + + qpci_device_foreach(bus, QVIRTIO_VENDOR_ID, -1, + qvirtio_pci_foreach_callback, &d); +} + +QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type) +{ + QVirtioPCIDevice *dev = NULL; + qvirtio_pci_foreach(bus, device_type, qvirtio_pci_assign_device, &dev); + + return dev; +} diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h new file mode 100644 index 0000000000..5101abb0ec --- /dev/null +++ b/tests/libqos/virtio-pci.h @@ -0,0 +1,24 @@ +/* + * libqos virtio PCI definitions + * + * 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. + */ + +#ifndef LIBQOS_VIRTIO_PCI_H +#define LIBQOS_VIRTIO_PCI_H + +#include "libqos/virtio.h" +#include "libqos/pci.h" + +typedef struct QVirtioPCIDevice { + QVirtioDevice vdev; + QPCIDevice *pdev; +} QVirtioPCIDevice; + +void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, + void (*func)(QVirtioDevice *d, void *data), void *data); +QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type); +#endif diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h new file mode 100644 index 0000000000..2a05798ca7 --- /dev/null +++ b/tests/libqos/virtio.h @@ -0,0 +1,23 @@ +/* + * libqos virtio definitions + * + * 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. + */ + +#ifndef LIBQOS_VIRTIO_H +#define LIBQOS_VIRTIO_H + +#define QVIRTIO_VENDOR_ID 0x1AF4 + +#define QVIRTIO_NET_DEVICE_ID 0x1 +#define QVIRTIO_BLK_DEVICE_ID 0x2 + +typedef struct QVirtioDevice { + /* Device type */ + uint16_t device_type; +} QVirtioDevice; + +#endif diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index d53f875b89..4d87a3e538 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -2,6 +2,7 @@ * QTest testcase for VirtIO Block Device * * Copyright (c) 2014 SUSE LINUX Products GmbH + * 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. @@ -9,12 +10,59 @@ #include #include +#include +#include +#include #include "libqtest.h" -#include "qemu/osdep.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci-pc.h" -/* Tests only initialization so far. TODO: Replace with functional tests */ -static void pci_nop(void) +#define TEST_IMAGE_SIZE (64 * 1024 * 1024) +#define PCI_SLOT 0x04 +#define PCI_FN 0x00 + +static QPCIBus *test_start(void) { + char cmdline[100]; + char tmp_path[] = "/tmp/qtest.XXXXXX"; + int fd, ret; + + /* Create a temporary raw image */ + fd = mkstemp(tmp_path); + g_assert_cmpint(fd, >=, 0); + ret = ftruncate(fd, TEST_IMAGE_SIZE); + g_assert_cmpint(ret, ==, 0); + close(fd); + + snprintf(cmdline, 100, "-drive if=none,id=drive0,file=%s " + "-device virtio-blk-pci,drive=drive0,addr=%x.%x", + tmp_path, PCI_SLOT, PCI_FN); + qtest_start(cmdline); + unlink(tmp_path); + + return qpci_init_pc(); +} + +static void test_end(void) +{ + qtest_end(); +} + +static void pci_basic(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + + bus = test_start(); + + dev = qvirtio_pci_device_find(bus, QVIRTIO_BLK_DEVICE_ID); + g_assert(dev != NULL); + g_assert_cmphex(dev->vdev.device_type, ==, QVIRTIO_BLK_DEVICE_ID); + g_assert_cmphex(dev->pdev->devfn, ==, ((PCI_SLOT << 3) | PCI_FN)); + + g_free(dev); + test_end(); } int main(int argc, char **argv) @@ -22,13 +70,10 @@ int main(int argc, char **argv) int ret; g_test_init(&argc, &argv, NULL); - qtest_add_func("/virtio/blk/pci/nop", pci_nop); - qtest_start("-drive id=drv0,if=none,file=/dev/null " - "-device virtio-blk-pci,drive=drv0"); + g_test_add_func("/virtio/blk/pci/basic", pci_basic); + ret = g_test_run(); - qtest_end(); - return ret; } From 46e0cf762985e0a85529efd454402998c5021212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:55 +0200 Subject: [PATCH 05/24] tests: Add virtio device initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functions to read and write virtio header fields. Add status bit setting in virtio-blk-device. Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/Makefile | 2 +- tests/libqos/virtio-pci.c | 71 +++++++++++++++++++++++++++++++++++++++ tests/libqos/virtio-pci.h | 18 ++++++++++ tests/libqos/virtio.c | 55 ++++++++++++++++++++++++++++++ tests/libqos/virtio.h | 30 +++++++++++++++++ tests/libqtest.c | 48 ++++++++++++++++++++++++++ tests/libqtest.h | 7 ++++ tests/virtio-blk-test.c | 31 ++++++++++++++--- 8 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 tests/libqos/virtio.c diff --git a/tests/Makefile b/tests/Makefile index 0572633774..d5db97ba63 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -299,7 +299,7 @@ libqos-obj-y += tests/libqos/i2c.o libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o libqos-pc-obj-y += tests/libqos/malloc-pc.o libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o -libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio-pci.o +libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index fde1b1f747..1a37620001 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -8,6 +8,7 @@ */ #include +#include #include "libqtest.h" #include "libqos/virtio.h" #include "libqos/virtio-pci.h" @@ -55,6 +56,64 @@ static void qvirtio_pci_assign_device(QVirtioDevice *d, void *data) *vpcidev = (QVirtioPCIDevice *)d; } +static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readb(dev->pdev, addr); +} + +static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readw(dev->pdev, addr); +} + +static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, addr); +} + +static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + int i; + uint64_t u64 = 0; + + if (qtest_big_endian()) { + for (i = 0; i < 8; ++i) { + u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << (7 - i) * 8; + } + } else { + for (i = 0; i < 8; ++i) { + u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << i * 8; + } + } + + return u64; +} + +static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS); +} + +static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS, status); +} + +const QVirtioBus qvirtio_pci = { + .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_status = qvirtio_pci_get_status, + .set_status = qvirtio_pci_set_status, +}; + void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, void (*func)(QVirtioDevice *d, void *data), void *data) { @@ -73,3 +132,15 @@ QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type) return dev; } + +void qvirtio_pci_device_enable(QVirtioPCIDevice *d) +{ + qpci_device_enable(d->pdev); + d->addr = qpci_iomap(d->pdev, 0, NULL); + g_assert(d->addr != NULL); +} + +void qvirtio_pci_device_disable(QVirtioPCIDevice *d) +{ + qpci_iounmap(d->pdev, d->addr); +} diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h index 5101abb0ec..26f902ecb6 100644 --- a/tests/libqos/virtio-pci.h +++ b/tests/libqos/virtio-pci.h @@ -13,12 +13,30 @@ #include "libqos/virtio.h" #include "libqos/pci.h" +#define QVIRTIO_DEVICE_FEATURES 0x00 +#define QVIRTIO_GUEST_FEATURES 0x04 +#define QVIRTIO_QUEUE_ADDRESS 0x08 +#define QVIRTIO_QUEUE_SIZE 0x0C +#define QVIRTIO_QUEUE_SELECT 0x0E +#define QVIRTIO_QUEUE_NOTIFY 0x10 +#define QVIRTIO_DEVICE_STATUS 0x12 +#define QVIRTIO_ISR_STATUS 0x13 +#define QVIRTIO_MSIX_CONF_VECTOR 0x14 +#define QVIRTIO_MSIX_QUEUE_VECTOR 0x16 +#define QVIRTIO_DEVICE_SPECIFIC_MSIX 0x18 +#define QVIRTIO_DEVICE_SPECIFIC_NO_MSIX 0x14 + typedef struct QVirtioPCIDevice { QVirtioDevice vdev; QPCIDevice *pdev; + void *addr; } QVirtioPCIDevice; +extern const QVirtioBus qvirtio_pci; + void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, void (*func)(QVirtioDevice *d, void *data), void *data); QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type); +void qvirtio_pci_device_enable(QVirtioPCIDevice *d); +void qvirtio_pci_device_disable(QVirtioPCIDevice *d); #endif diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c new file mode 100644 index 0000000000..577d679136 --- /dev/null +++ b/tests/libqos/virtio.c @@ -0,0 +1,55 @@ +/* + * libqos virtio 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 +#include "libqtest.h" +#include "libqos/virtio.h" + +uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readb(d, addr); +} + +uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readw(d, addr); +} + +uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readl(d, addr); +} + +uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readq(d, addr); +} + +void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, QVIRTIO_RESET); + g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_RESET); +} + +void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_ACKNOWLEDGE); + g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_ACKNOWLEDGE); +} + +void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER); + g_assert_cmphex(bus->get_status(d), ==, + QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); +} diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h index 2a05798ca7..8d7238bd91 100644 --- a/tests/libqos/virtio.h +++ b/tests/libqos/virtio.h @@ -12,6 +12,10 @@ #define QVIRTIO_VENDOR_ID 0x1AF4 +#define QVIRTIO_RESET 0x0 +#define QVIRTIO_ACKNOWLEDGE 0x1 +#define QVIRTIO_DRIVER 0x2 + #define QVIRTIO_NET_DEVICE_ID 0x1 #define QVIRTIO_BLK_DEVICE_ID 0x2 @@ -20,4 +24,30 @@ typedef struct QVirtioDevice { uint16_t device_type; } QVirtioDevice; +typedef struct QVirtioBus { + uint8_t (*config_readb)(QVirtioDevice *d, void *addr); + uint16_t (*config_readw)(QVirtioDevice *d, void *addr); + uint32_t (*config_readl)(QVirtioDevice *d, void *addr); + uint64_t (*config_readq)(QVirtioDevice *d, void *addr); + + /* Get status of the device */ + uint8_t (*get_status)(QVirtioDevice *d); + + /* Set status of the device */ + void (*set_status)(QVirtioDevice *d, uint8_t status); +} QVirtioBus; + +uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); + +void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d); + #endif diff --git a/tests/libqtest.c b/tests/libqtest.c index 5e458e884e..9a92aa70e4 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -696,3 +696,51 @@ void qmp_discard_response(const char *fmt, ...) qtest_qmpv_discard_response(global_qtest, fmt, ap); va_end(ap); } + +bool qtest_big_endian(void) +{ + const char *arch = qtest_get_arch(); + int i; + + static const struct { + const char *arch; + bool big_endian; + } endianness[] = { + { "aarch64", false }, + { "alpha", false }, + { "arm", false }, + { "cris", false }, + { "i386", false }, + { "lm32", true }, + { "m68k", true }, + { "microblaze", true }, + { "microblazeel", false }, + { "mips", true }, + { "mips64", true }, + { "mips64el", false }, + { "mipsel", false }, + { "moxie", true }, + { "or32", true }, + { "ppc", true }, + { "ppc64", true }, + { "ppcemb", true }, + { "s390x", true }, + { "sh4", false }, + { "sh4eb", true }, + { "sparc", true }, + { "sparc64", true }, + { "unicore32", false }, + { "x86_64", false }, + { "xtensa", false }, + { "xtensaeb", true }, + {}, + }; + + for (i = 0; endianness[i].arch; i++) { + if (strcmp(endianness[i].arch, arch) == 0) { + return endianness[i].big_endian; + } + } + + return false; +} diff --git a/tests/libqtest.h b/tests/libqtest.h index 1be0934f07..3e12cab2f2 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -682,4 +682,11 @@ static inline int64_t clock_set(int64_t val) return qtest_clock_set(global_qtest, val); } +/** + * qtest_big_endian: + * + * Returns: True if the architecture under test has a big endian configuration. + */ +bool qtest_big_endian(void); + #endif diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 4d87a3e538..649f7cf94f 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -49,18 +49,41 @@ static void test_end(void) qtest_end(); } -static void pci_basic(void) +static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus) { QVirtioPCIDevice *dev; - QPCIBus *bus; - - bus = test_start(); dev = qvirtio_pci_device_find(bus, QVIRTIO_BLK_DEVICE_ID); g_assert(dev != NULL); g_assert_cmphex(dev->vdev.device_type, ==, QVIRTIO_BLK_DEVICE_ID); g_assert_cmphex(dev->pdev->devfn, ==, ((PCI_SLOT << 3) | PCI_FN)); + qvirtio_pci_device_enable(dev); + qvirtio_reset(&qvirtio_pci, &dev->vdev); + qvirtio_set_acknowledge(&qvirtio_pci, &dev->vdev); + qvirtio_set_driver(&qvirtio_pci, &dev->vdev); + + return dev; +} + +static void pci_basic(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + void *addr; + uint64_t capacity; + + bus = test_start(); + + dev = virtio_blk_init(bus); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + qvirtio_pci_device_disable(dev); g_free(dev); test_end(); } From bf3c63d2010c5ef52f8b988bee2a1486a056795f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:56 +0200 Subject: [PATCH 06/24] libqos: Added basic virtqueue support to virtio implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add status changing and feature negotiation. Add basic virtqueue support for adding and sending virtqueue requests. Add ISR checking. [Squashed request endianness fix by Greg Kurz --Stefan] Reviewed-by: Stefan Hajnoczi Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/libqos/virtio-pci.c | 82 ++++++++++++++++ tests/libqos/virtio-pci.h | 2 + tests/libqos/virtio.c | 100 +++++++++++++++++++ tests/libqos/virtio.h | 99 +++++++++++++++++++ tests/virtio-blk-test.c | 196 +++++++++++++++++++++++++++++++++++++- 5 files changed, 476 insertions(+), 3 deletions(-) diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index 1a37620001..12b06a2f53 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -14,6 +14,8 @@ #include "libqos/virtio-pci.h" #include "libqos/pci.h" #include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" #include "hw/pci/pci_regs.h" @@ -93,6 +95,18 @@ static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, void *addr) return u64; } +static uint32_t qvirtio_pci_get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_DEVICE_FEATURES); +} + +static void qvirtio_pci_set_features(QVirtioDevice *d, uint32_t features) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES, features); +} + static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) { QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; @@ -105,13 +119,81 @@ static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS, status); } +static uint8_t qvirtio_pci_get_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS); +} + +static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_QUEUE_SELECT, index); +} + +static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readw(dev->pdev, dev->addr + QVIRTIO_QUEUE_SIZE); +} + +static void qvirtio_pci_set_queue_address(QVirtioDevice *d, uint32_t pfn) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_QUEUE_ADDRESS, pfn); +} + +static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + uint64_t addr; + QVirtQueue *vq; + + vq = g_malloc0(sizeof(*vq)); + + qvirtio_pci_queue_select(d, index); + vq->index = index; + vq->size = qvirtio_pci_get_queue_size(d); + vq->free_head = 0; + vq->num_free = vq->size; + vq->align = QVIRTIO_PCI_ALIGN; + + /* Check different than 0 */ + g_assert_cmpint(vq->size, !=, 0); + + /* Check power of 2 */ + g_assert_cmpint(vq->size & (vq->size - 1), ==, 0); + + addr = guest_alloc(alloc, qvring_size(vq->size, QVIRTIO_PCI_ALIGN)); + qvring_init(alloc, vq, addr); + qvirtio_pci_set_queue_address(d, vq->desc / QVIRTIO_PCI_ALIGN); + + /* TODO: MSI-X configuration */ + + return vq; +} + +static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writew(dev->pdev, dev->addr + QVIRTIO_QUEUE_NOTIFY, vq->index); +} + const QVirtioBus qvirtio_pci = { .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_status = qvirtio_pci_get_status, .set_status = qvirtio_pci_set_status, + .get_isr_status = qvirtio_pci_get_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, + .virtqueue_kick = qvirtio_pci_virtqueue_kick, }; void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h index 26f902ecb6..40bd12db13 100644 --- a/tests/libqos/virtio-pci.h +++ b/tests/libqos/virtio-pci.h @@ -26,6 +26,8 @@ #define QVIRTIO_DEVICE_SPECIFIC_MSIX 0x18 #define QVIRTIO_DEVICE_SPECIFIC_NO_MSIX 0x14 +#define QVIRTIO_PCI_ALIGN 4096 + typedef struct QVirtioPCIDevice { QVirtioDevice vdev; QPCIDevice *pdev; diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c index 577d679136..de92642819 100644 --- a/tests/libqos/virtio.c +++ b/tests/libqos/virtio.c @@ -35,6 +35,23 @@ uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, return bus->config_readq(d, addr); } +uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d) +{ + return bus->get_features(d); +} + +void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d, + uint32_t features) +{ + bus->set_features(d, features); +} + +QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + return bus->virtqueue_setup(d, alloc, index); +} + void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d) { bus->set_status(d, QVIRTIO_RESET); @@ -53,3 +70,86 @@ void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d) g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); } + +void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER_OK); + g_assert_cmphex(bus->get_status(d), ==, + QVIRTIO_DRIVER_OK | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); +} + +bool qvirtio_wait_isr(const QVirtioBus *bus, QVirtioDevice *d, uint8_t mask, + uint64_t timeout) +{ + do { + clock_step(10); + if (bus->get_isr_status(d) & mask) { + break; /* It has ended */ + } + } while (--timeout); + + return timeout != 0; +} + +void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr) +{ + int i; + + vq->desc = addr; + vq->avail = vq->desc + vq->size*sizeof(QVRingDesc); + vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size) + + vq->align - 1) & ~(vq->align - 1)); + + for (i = 0; i < vq->size - 1; i++) { + /* vq->desc[i].addr */ + writew(vq->desc + (16 * i), 0); + /* vq->desc[i].next */ + writew(vq->desc + (16 * i) + 14, i + 1); + } + + /* vq->avail->flags */ + writew(vq->avail, 0); + /* vq->avail->idx */ + writew(vq->avail + 2, 0); + + /* vq->used->flags */ + writew(vq->used, 0); +} + +uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, + bool next) +{ + uint16_t flags = 0; + vq->num_free--; + + if (write) { + flags |= QVRING_DESC_F_WRITE; + } + + if (next) { + flags |= QVRING_DESC_F_NEXT; + } + + /* vq->desc[vq->free_head].addr */ + writeq(vq->desc + (16 * vq->free_head), data); + /* vq->desc[vq->free_head].len */ + writel(vq->desc + (16 * vq->free_head) + 8, len); + /* vq->desc[vq->free_head].flags */ + writew(vq->desc + (16 * vq->free_head) + 12, flags); + + return vq->free_head++; /* Return and increase, in this order */ +} + +void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head) +{ + /* vq->avail->idx */ + uint16_t idx = readl(vq->avail + 2); + + /* vq->avail->ring[idx % vq->size] */ + writel(vq->avail + 4 + (2 * (idx % vq->size)), free_head); + /* vq->avail->idx */ + writel(vq->avail + 2, idx + 1); + + bus->virtqueue_kick(d, vq); +} diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h index 8d7238bd91..aba5a1e12c 100644 --- a/tests/libqos/virtio.h +++ b/tests/libqos/virtio.h @@ -10,33 +10,117 @@ #ifndef LIBQOS_VIRTIO_H #define LIBQOS_VIRTIO_H +#include "libqos/malloc.h" + #define QVIRTIO_VENDOR_ID 0x1AF4 #define QVIRTIO_RESET 0x0 #define QVIRTIO_ACKNOWLEDGE 0x1 #define QVIRTIO_DRIVER 0x2 +#define QVIRTIO_DRIVER_OK 0x4 #define QVIRTIO_NET_DEVICE_ID 0x1 #define QVIRTIO_BLK_DEVICE_ID 0x2 +#define QVRING_DESC_F_NEXT 0x1 +#define QVRING_DESC_F_WRITE 0x2 +#define QVRING_DESC_F_INDIRECT 0x4 + +#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000 +#define QVIRTIO_F_ANY_LAYOUT 0x08000000 +#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000 +#define QVIRTIO_F_RING_EVENT_IDX 0x20000000 +#define QVIRTIO_F_BAD_FEATURE 0x40000000 + +#define QVRING_AVAIL_F_NO_INTERRUPT 1 + +#define QVRING_USED_F_NO_NOTIFY 1 + typedef struct QVirtioDevice { /* Device type */ uint16_t device_type; } QVirtioDevice; +typedef struct QVRingDesc { + uint64_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +} QVRingDesc; + +typedef struct QVRingAvail { + uint16_t flags; + uint16_t idx; + uint16_t ring[0]; /* This is an array of uint16_t */ +} QVRingAvail; + +typedef struct QVRingUsedElem { + uint32_t id; + uint32_t len; +} QVRingUsedElem; + +typedef struct QVRingUsed { + uint16_t flags; + uint16_t idx; + QVRingUsedElem ring[0]; /* This is an array of QVRingUsedElem structs */ +} QVRingUsed; + +typedef struct QVirtQueue { + uint64_t desc; /* This points to an array of QVRingDesc */ + uint64_t avail; /* This points to a QVRingAvail */ + uint64_t used; /* This points to a QVRingDesc */ + uint16_t index; + uint32_t size; + uint32_t free_head; + uint32_t num_free; + uint32_t align; +} QVirtQueue; + typedef struct QVirtioBus { uint8_t (*config_readb)(QVirtioDevice *d, void *addr); uint16_t (*config_readw)(QVirtioDevice *d, void *addr); uint32_t (*config_readl)(QVirtioDevice *d, void *addr); uint64_t (*config_readq)(QVirtioDevice *d, void *addr); + /* Get features of the device */ + uint32_t (*get_features)(QVirtioDevice *d); + + /* Get features of the device */ + void (*set_features)(QVirtioDevice *d, uint32_t features); + /* Get status of the device */ uint8_t (*get_status)(QVirtioDevice *d); /* Set status of the device */ void (*set_status)(QVirtioDevice *d, uint8_t status); + + /* Get the ISR status of the device */ + uint8_t (*get_isr_status)(QVirtioDevice *d); + + /* Select a queue to work on */ + void (*queue_select)(QVirtioDevice *d, uint16_t index); + + /* Get the size of the selected queue */ + uint16_t (*get_queue_size)(QVirtioDevice *d); + + /* Set the address of the selected queue */ + void (*set_queue_address)(QVirtioDevice *d, uint32_t pfn); + + /* Setup the virtqueue specified by index */ + QVirtQueue *(*virtqueue_setup)(QVirtioDevice *d, QGuestAllocator *alloc, + uint16_t index); + + /* Notify changes in virtqueue */ + void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq); } QVirtioBus; +static inline uint32_t qvring_size(uint32_t num, uint32_t align) +{ + return ((sizeof(struct QVRingDesc) * num + sizeof(uint16_t) * (3 + num) + + align - 1) & ~(align - 1)) + + sizeof(uint16_t) * 3 + sizeof(struct QVRingUsedElem) * num; +} + uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, void *addr); uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, @@ -45,9 +129,24 @@ uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, void *addr); uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, void *addr); +uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d, + uint32_t features); void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d); void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d); void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d); + +bool qvirtio_wait_isr(const QVirtioBus *bus, QVirtioDevice *d, uint8_t mask, + uint64_t timeout); +QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index); + +void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr); +uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, + bool next); +void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head); #endif diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 649f7cf94f..b048938083 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -17,10 +17,41 @@ #include "libqos/virtio.h" #include "libqos/virtio-pci.h" #include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" +#include "qemu/bswap.h" -#define TEST_IMAGE_SIZE (64 * 1024 * 1024) -#define PCI_SLOT 0x04 -#define PCI_FN 0x00 +#define QVIRTIO_BLK_F_BARRIER 0x00000001 +#define QVIRTIO_BLK_F_SIZE_MAX 0x00000002 +#define QVIRTIO_BLK_F_SEG_MAX 0x00000004 +#define QVIRTIO_BLK_F_GEOMETRY 0x00000010 +#define QVIRTIO_BLK_F_RO 0x00000020 +#define QVIRTIO_BLK_F_BLK_SIZE 0x00000040 +#define QVIRTIO_BLK_F_SCSI 0x00000080 +#define QVIRTIO_BLK_F_WCE 0x00000200 +#define QVIRTIO_BLK_F_TOPOLOGY 0x00000400 +#define QVIRTIO_BLK_F_CONFIG_WCE 0x00000800 + +#define QVIRTIO_BLK_T_IN 0 +#define QVIRTIO_BLK_T_OUT 1 +#define QVIRTIO_BLK_T_SCSI_CMD 2 +#define QVIRTIO_BLK_T_SCSI_CMD_OUT 3 +#define QVIRTIO_BLK_T_FLUSH 4 +#define QVIRTIO_BLK_T_FLUSH_OUT 5 +#define QVIRTIO_BLK_T_GET_ID 8 + +#define TEST_IMAGE_SIZE (64 * 1024 * 1024) +#define QVIRTIO_BLK_TIMEOUT 100 +#define PCI_SLOT 0x04 +#define PCI_FN 0x00 + +typedef struct QVirtioBlkReq { + uint32_t type; + uint32_t ioprio; + uint64_t sector; + char *data; + uint8_t status; +} QVirtioBlkReq; static QPCIBus *test_start(void) { @@ -66,12 +97,53 @@ static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus) return dev; } +static inline void virtio_blk_fix_request(QVirtioBlkReq *req) +{ +#ifdef HOST_WORDS_BIGENDIAN + bool host_endian = true; +#else + bool host_endian = false; +#endif + + if (qtest_big_endian() != host_endian) { + req->type = bswap32(req->type); + req->ioprio = bswap32(req->ioprio); + req->sector = bswap64(req->sector); + } +} + +static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioBlkReq *req, + uint64_t data_size) +{ + uint64_t addr; + uint8_t status = 0xFF; + + g_assert_cmpuint(data_size % 512, ==, 0); + addr = guest_alloc(alloc, sizeof(*req) + data_size); + + virtio_blk_fix_request(req); + + memwrite(addr, req, 16); + memwrite(addr + 16, req->data, data_size); + memwrite(addr + 16 + data_size, &status, sizeof(status)); + + return addr; +} + static void pci_basic(void) { QVirtioPCIDevice *dev; QPCIBus *bus; + QVirtQueue *vq; + QGuestAllocator *alloc; + QVirtioBlkReq req; void *addr; + uint64_t req_addr; uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; bus = test_start(); @@ -83,6 +155,124 @@ static void pci_basic(void) capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | QVIRTIO_F_RING_EVENT_IDX | + QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + alloc = pc_alloc_init(); + vq = qvirtqueue_setup(&qvirtio_pci, &dev->vdev, alloc, 0); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write and read with 2 descriptor layout */ + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(vq, req_addr, 528, false, true); + qvirtqueue_add(vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(vq, req_addr, 16, false, true); + qvirtqueue_add(vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* Write and read with 3 descriptor layout */ + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(vq, req_addr, 16, false, true); + qvirtqueue_add(vq, req_addr + 16, 512, false, true); + qvirtqueue_add(vq, req_addr + 528, 1, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(vq, req_addr, 16, false, true); + qvirtqueue_add(vq, req_addr + 16, 512, true, true); + qvirtqueue_add(vq, req_addr + 528, 1, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vq->desc); qvirtio_pci_device_disable(dev); g_free(dev); test_end(); From f294b029aa2beb1c67116e04bff5d331f0b18288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:57 +0200 Subject: [PATCH 07/24] libqos: Added indirect descriptor support to virtio implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functions necessary for working with indirect descriptors. Add test using new functions. Reviewed-by: Stefan Hajnoczi Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/libqos/virtio-pci.c | 10 ++++ tests/libqos/virtio.c | 64 ++++++++++++++++++++++++ tests/libqos/virtio.h | 22 ++++++++- tests/virtio-blk-test.c | 100 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 1 deletion(-) diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index 12b06a2f53..cf15f7a45b 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -107,6 +107,12 @@ static void qvirtio_pci_set_features(QVirtioDevice *d, uint32_t features) qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES, features); } +static uint32_t qvirtio_pci_get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES); +} + static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) { QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; @@ -146,10 +152,12 @@ static void qvirtio_pci_set_queue_address(QVirtioDevice *d, uint32_t pfn) static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, QGuestAllocator *alloc, uint16_t index) { + uint32_t feat; uint64_t addr; QVirtQueue *vq; vq = g_malloc0(sizeof(*vq)); + feat = qvirtio_pci_get_guest_features(d); qvirtio_pci_queue_select(d, index); vq->index = index; @@ -157,6 +165,7 @@ static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, vq->free_head = 0; vq->num_free = vq->size; vq->align = QVIRTIO_PCI_ALIGN; + vq->indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0; /* Check different than 0 */ g_assert_cmpint(vq->size, !=, 0); @@ -186,6 +195,7 @@ const QVirtioBus qvirtio_pci = { .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_isr_status = qvirtio_pci_get_isr_status, diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c index de92642819..b1cab1f699 100644 --- a/tests/libqos/virtio.c +++ b/tests/libqos/virtio.c @@ -116,6 +116,51 @@ void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr) writew(vq->used, 0); } +QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t elem) +{ + int i; + QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect)); + + indirect->index = 0; + indirect->elem = elem; + indirect->desc = guest_alloc(alloc, sizeof(QVRingDesc)*elem); + + for (i = 0; i < elem - 1; ++i) { + /* indirect->desc[i].addr */ + writeq(indirect->desc + (16 * i), 0); + /* indirect->desc[i].flags */ + writew(indirect->desc + (16 * i) + 12, QVRING_DESC_F_NEXT); + /* indirect->desc[i].next */ + writew(indirect->desc + (16 * i) + 14, i + 1); + } + + return indirect; +} + +void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data, + uint32_t len, bool write) +{ + uint16_t flags; + + g_assert_cmpint(indirect->index, <, indirect->elem); + + flags = readw(indirect->desc + (16 * indirect->index) + 12); + + if (write) { + flags |= QVRING_DESC_F_WRITE; + } + + /* indirect->desc[indirect->index].addr */ + writeq(indirect->desc + (16 * indirect->index), data); + /* indirect->desc[indirect->index].len */ + writel(indirect->desc + (16 * indirect->index) + 8, len); + /* indirect->desc[indirect->index].flags */ + writew(indirect->desc + (16 * indirect->index) + 12, flags); + + indirect->index++; +} + uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, bool next) { @@ -140,6 +185,25 @@ uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, return vq->free_head++; /* Return and increase, in this order */ } +uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect) +{ + g_assert(vq->indirect); + g_assert_cmpint(vq->size, >=, indirect->elem); + g_assert_cmpint(indirect->index, ==, indirect->elem); + + vq->num_free--; + + /* vq->desc[vq->free_head].addr */ + writeq(vq->desc + (16 * vq->free_head), indirect->desc); + /* vq->desc[vq->free_head].len */ + writel(vq->desc + (16 * vq->free_head) + 8, + sizeof(QVRingDesc) * indirect->elem); + /* vq->desc[vq->free_head].flags */ + writew(vq->desc + (16 * vq->free_head) + 12, QVRING_DESC_F_INDIRECT); + + return vq->free_head++; /* Return and increase, in this order */ +} + void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, uint32_t free_head) { diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h index aba5a1e12c..1860660dfd 100644 --- a/tests/libqos/virtio.h +++ b/tests/libqos/virtio.h @@ -22,6 +22,11 @@ #define QVIRTIO_NET_DEVICE_ID 0x1 #define QVIRTIO_BLK_DEVICE_ID 0x2 +#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000 +#define QVIRTIO_F_ANY_LAYOUT 0x08000000 +#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000 +#define QVIRTIO_F_RING_EVENT_IDX 0x20000000 + #define QVRING_DESC_F_NEXT 0x1 #define QVRING_DESC_F_WRITE 0x2 #define QVRING_DESC_F_INDIRECT 0x4 @@ -74,8 +79,15 @@ typedef struct QVirtQueue { uint32_t free_head; uint32_t num_free; uint32_t align; + bool indirect; } QVirtQueue; +typedef struct QVRingIndirectDesc { + uint64_t desc; /* This points to an array fo QVRingDesc */ + uint16_t index; + uint16_t elem; +} QVRingIndirectDesc; + typedef struct QVirtioBus { uint8_t (*config_readb)(QVirtioDevice *d, void *addr); uint16_t (*config_readw)(QVirtioDevice *d, void *addr); @@ -85,9 +97,12 @@ typedef struct QVirtioBus { /* Get features of the device */ uint32_t (*get_features)(QVirtioDevice *d); - /* Get features of the device */ + /* Set features of the device */ void (*set_features)(QVirtioDevice *d, uint32_t features); + /* Get features of the guest */ + uint32_t (*get_guest_features)(QVirtioDevice *d); + /* Get status of the device */ uint8_t (*get_status)(QVirtioDevice *d); @@ -144,8 +159,13 @@ QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, QGuestAllocator *alloc, uint16_t index); void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr); +QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t elem); +void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data, + uint32_t len, bool write); uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, bool next); +uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect); void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, uint32_t free_head); diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index b048938083..2f9cc2b7e7 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -278,6 +278,105 @@ static void pci_basic(void) test_end(); } +static void pci_indirect(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueue *vq; + QGuestAllocator *alloc; + QVirtioBlkReq req; + QVRingIndirectDesc *indirect; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + + dev = virtio_blk_init(bus); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + g_assert_cmphex(features & QVIRTIO_F_RING_INDIRECT_DESC, !=, 0); + features = features & ~(QVIRTIO_F_BAD_FEATURE | QVIRTIO_F_RING_EVENT_IDX | + QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + alloc = pc_alloc_init(); + vq = qvirtqueue_setup(&qvirtio_pci, &dev->vdev, alloc, 0); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); + qvring_indirect_desc_add(indirect, req_addr, 528, false); + qvring_indirect_desc_add(indirect, req_addr + 528, 1, true); + free_head = qvirtqueue_add_indirect(vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + g_free(indirect); + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); + qvring_indirect_desc_add(indirect, req_addr, 16, false); + qvring_indirect_desc_add(indirect, req_addr + 16, 513, true); + free_head = qvirtqueue_add_indirect(vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + QVIRTIO_BLK_TIMEOUT)); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + g_free(indirect); + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vq->desc); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + int main(int argc, char **argv) { int ret; @@ -285,6 +384,7 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); g_test_add_func("/virtio/blk/pci/basic", pci_basic); + g_test_add_func("/virtio/blk/pci/indirect", pci_indirect); ret = g_test_run(); From e11199554c568822c3ede3e3b4820ac3a146b1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:58 +0200 Subject: [PATCH 08/24] libqos: Added test case for configuration changes in virtio-blk test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Stefan Hajnoczi Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/virtio-blk-test.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 2f9cc2b7e7..672580b206 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -377,6 +377,39 @@ static void pci_indirect(void) test_end(); } +static void pci_config(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + int n_size = TEST_IMAGE_SIZE / 2; + void *addr; + uint64_t capacity; + + bus = test_start(); + + dev = virtio_blk_init(bus); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', " + " 'size': %d } }", n_size); + g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x2, + QVIRTIO_BLK_TIMEOUT)); + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, n_size / 512); + + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + int main(int argc, char **argv) { int ret; @@ -385,6 +418,7 @@ int main(int argc, char **argv) g_test_add_func("/virtio/blk/pci/basic", pci_basic); g_test_add_func("/virtio/blk/pci/indirect", pci_indirect); + g_test_add_func("/virtio/blk/pci/config", pci_config); ret = g_test_run(); From 58368113989403775496b3422f22094713703157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:07:59 +0200 Subject: [PATCH 09/24] libqos: Added MSI-X support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added MSI-X support for qtest PCI. Added MSI-X support for virtio-pci. Added MSI-X test case in virtio-blk-test. Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/libqos/pci.c | 111 ++++++++++++++++++++++- tests/libqos/pci.h | 10 +++ tests/libqos/virtio-pci.c | 142 ++++++++++++++++++++++++++---- tests/libqos/virtio-pci.h | 17 ++++ tests/libqos/virtio.c | 17 +++- tests/libqos/virtio.h | 11 ++- tests/virtio-blk-test.c | 180 ++++++++++++++++++++++++++++++-------- 7 files changed, 426 insertions(+), 62 deletions(-) diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c index ce0b308a83..d5ce683d77 100644 --- a/tests/libqos/pci.c +++ b/tests/libqos/pci.c @@ -15,8 +15,6 @@ #include "hw/pci/pci_regs.h" #include -#include - void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, void (*func)(QPCIDevice *dev, int devfn, void *data), void *data) @@ -75,6 +73,115 @@ void qpci_device_enable(QPCIDevice *dev) qpci_config_writew(dev, PCI_COMMAND, cmd); } +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id) +{ + uint8_t cap; + uint8_t addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST); + + do { + cap = qpci_config_readb(dev, addr); + if (cap != id) { + addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT); + } + } while (cap != id && addr != 0); + + return addr; +} + +void qpci_msix_enable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + uint32_t table; + uint8_t bir_table; + uint8_t bir_pba; + void *offset; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE); + + table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE); + bir_table = table & PCI_MSIX_FLAGS_BIRMASK; + offset = qpci_iomap(dev, bir_table, NULL); + dev->msix_table = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK); + + table = qpci_config_readl(dev, addr + PCI_MSIX_PBA); + bir_pba = table & PCI_MSIX_FLAGS_BIRMASK; + if (bir_pba != bir_table) { + offset = qpci_iomap(dev, bir_pba, NULL); + } + dev->msix_pba = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK); + + g_assert(dev->msix_table != NULL); + g_assert(dev->msix_pba != NULL); + dev->msix_enabled = true; +} + +void qpci_msix_disable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, + val & ~PCI_MSIX_FLAGS_ENABLE); + + qpci_iounmap(dev, dev->msix_table); + qpci_iounmap(dev, dev->msix_pba); + dev->msix_enabled = 0; + dev->msix_table = NULL; + dev->msix_pba = NULL; +} + +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry) +{ + uint32_t pba_entry; + uint8_t bit_n = entry % 32; + void *addr = dev->msix_pba + (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4; + + g_assert(dev->msix_enabled); + pba_entry = qpci_io_readl(dev, addr); + qpci_io_writel(dev, addr, pba_entry & ~(1 << bit_n)); + return (pba_entry & (1 << bit_n)) != 0; +} + +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry) +{ + uint8_t addr; + uint16_t val; + void *vector_addr = dev->msix_table + (entry * PCI_MSIX_ENTRY_SIZE); + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + + if (val & PCI_MSIX_FLAGS_MASKALL) { + return true; + } else { + return (qpci_io_readl(dev, vector_addr + PCI_MSIX_ENTRY_VECTOR_CTRL) + & PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0; + } +} + +uint16_t qpci_msix_table_size(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t control; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + + control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + return (control & PCI_MSIX_FLAGS_QSIZE) + 1; +} + uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset) { return dev->bus->config_readb(dev->bus, dev->devfn, offset); diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h index 9ee048b154..d51eb9e219 100644 --- a/tests/libqos/pci.h +++ b/tests/libqos/pci.h @@ -14,6 +14,7 @@ #define LIBQOS_PCI_H #include +#include "libqtest.h" #define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn)) @@ -49,6 +50,9 @@ struct QPCIDevice { QPCIBus *bus; int devfn; + bool msix_enabled; + void *msix_table; + void *msix_pba; }; void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, @@ -57,6 +61,12 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn); void qpci_device_enable(QPCIDevice *dev); +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id); +void qpci_msix_enable(QPCIDevice *dev); +void qpci_msix_disable(QPCIDevice *dev); +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry); +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry); +uint16_t qpci_msix_table_size(QPCIDevice *dev); uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset); uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset); diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index cf15f7a45b..ab28717305 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -36,6 +36,8 @@ static QVirtioPCIDevice *qpcidevice_to_qvirtiodevice(QPCIDevice *pdev) qpci_config_readw(vpcidev->pdev, PCI_SUBSYSTEM_ID); } + vpcidev->config_msix_entry = -1; + return vpcidev; } @@ -125,10 +127,45 @@ static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS, status); } -static uint8_t qvirtio_pci_get_isr_status(QVirtioDevice *d) +static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) { QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; - return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS); + 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 = readl(vqpci->msix_addr); + writel(vqpci->msix_addr, 0); + return data == vqpci->msix_data; + } + } else { + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 1; + } +} + +static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + 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 = readl(dev->config_msix_addr); + writel(dev->config_msix_addr, 0); + return data == dev->config_msix_data; + } + } else { + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 2; + } } static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) @@ -154,32 +191,34 @@ static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, { uint32_t feat; uint64_t addr; - QVirtQueue *vq; + QVirtQueuePCI *vqpci; - vq = g_malloc0(sizeof(*vq)); + vqpci = g_malloc0(sizeof(*vqpci)); feat = qvirtio_pci_get_guest_features(d); qvirtio_pci_queue_select(d, index); - vq->index = index; - vq->size = qvirtio_pci_get_queue_size(d); - vq->free_head = 0; - vq->num_free = vq->size; - vq->align = QVIRTIO_PCI_ALIGN; - vq->indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0; + vqpci->vq.index = index; + vqpci->vq.size = qvirtio_pci_get_queue_size(d); + vqpci->vq.free_head = 0; + vqpci->vq.num_free = vqpci->vq.size; + vqpci->vq.align = QVIRTIO_PCI_ALIGN; + vqpci->vq.indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0; + + vqpci->msix_entry = -1; + vqpci->msix_addr = 0; + vqpci->msix_data = 0x12345678; /* Check different than 0 */ - g_assert_cmpint(vq->size, !=, 0); + g_assert_cmpint(vqpci->vq.size, !=, 0); /* Check power of 2 */ - g_assert_cmpint(vq->size & (vq->size - 1), ==, 0); + g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0); - addr = guest_alloc(alloc, qvring_size(vq->size, QVIRTIO_PCI_ALIGN)); - qvring_init(alloc, vq, addr); - qvirtio_pci_set_queue_address(d, vq->desc / QVIRTIO_PCI_ALIGN); + addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, QVIRTIO_PCI_ALIGN)); + qvring_init(alloc, &vqpci->vq, addr); + qvirtio_pci_set_queue_address(d, vqpci->vq.desc / QVIRTIO_PCI_ALIGN); - /* TODO: MSI-X configuration */ - - return vq; + return &vqpci->vq; } static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) @@ -198,7 +237,8 @@ const QVirtioBus qvirtio_pci = { .get_guest_features = qvirtio_pci_get_guest_features, .get_status = qvirtio_pci_get_status, .set_status = qvirtio_pci_set_status, - .get_isr_status = qvirtio_pci_get_isr_status, + .get_queue_isr_status = qvirtio_pci_get_queue_isr_status, + .get_config_isr_status = qvirtio_pci_get_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, @@ -235,4 +275,68 @@ void qvirtio_pci_device_enable(QVirtioPCIDevice *d) void qvirtio_pci_device_disable(QVirtioPCIDevice *d) { qpci_iounmap(d->pdev, d->addr); + d->addr = NULL; +} + +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry) +{ + uint16_t vector; + uint32_t control; + void *addr; + + g_assert(d->pdev->msix_enabled); + addr = d->pdev->msix_table + (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, addr + PCI_MSIX_ENTRY_LOWER_ADDR, + vqpci->msix_addr & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR, + (vqpci->msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, vqpci->msix_data); + + control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + qvirtio_pci_queue_select(&d->vdev, vqpci->vq.index); + qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR); + g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR); +} + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry) +{ + uint16_t vector; + uint32_t control; + void *addr; + + g_assert(d->pdev->msix_enabled); + addr = d->pdev->msix_table + (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, addr + PCI_MSIX_ENTRY_LOWER_ADDR, + d->config_msix_addr & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR, + (d->config_msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, d->config_msix_data); + + control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR); + g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR); } diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h index 40bd12db13..883f7ff267 100644 --- a/tests/libqos/virtio-pci.h +++ b/tests/libqos/virtio-pci.h @@ -28,12 +28,24 @@ #define QVIRTIO_PCI_ALIGN 4096 +#define QVIRTIO_MSI_NO_VECTOR 0xFFFF + typedef struct QVirtioPCIDevice { QVirtioDevice vdev; QPCIDevice *pdev; void *addr; + uint16_t config_msix_entry; + uint64_t config_msix_addr; + uint32_t config_msix_data; } QVirtioPCIDevice; +typedef struct QVirtQueuePCI { + QVirtQueue vq; + uint16_t msix_entry; + uint64_t msix_addr; + uint32_t msix_data; +} QVirtQueuePCI; + extern const QVirtioBus qvirtio_pci; void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, @@ -41,4 +53,9 @@ void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type); void qvirtio_pci_device_enable(QVirtioPCIDevice *d); void qvirtio_pci_device_disable(QVirtioPCIDevice *d); + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry); +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry); #endif diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c index b1cab1f699..16eaf79425 100644 --- a/tests/libqos/virtio.c +++ b/tests/libqos/virtio.c @@ -78,12 +78,25 @@ void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d) QVIRTIO_DRIVER_OK | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); } -bool qvirtio_wait_isr(const QVirtioBus *bus, QVirtioDevice *d, uint8_t mask, +bool qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d, + QVirtQueue *vq, uint64_t timeout) +{ + do { + clock_step(10); + if (bus->get_queue_isr_status(d, vq)) { + break; /* It has ended */ + } + } while (--timeout); + + return timeout != 0; +} + +bool qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d, uint64_t timeout) { do { clock_step(10); - if (bus->get_isr_status(d) & mask) { + if (bus->get_config_isr_status(d)) { break; /* It has ended */ } } while (--timeout); diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h index 1860660dfd..cebccd21d1 100644 --- a/tests/libqos/virtio.h +++ b/tests/libqos/virtio.h @@ -109,8 +109,11 @@ typedef struct QVirtioBus { /* Set status of the device */ void (*set_status)(QVirtioDevice *d, uint8_t status); - /* Get the ISR status of the device */ - uint8_t (*get_isr_status)(QVirtioDevice *d); + /* Get the queue ISR status of the device */ + bool (*get_queue_isr_status)(QVirtioDevice *d, QVirtQueue *vq); + + /* Get the configuration ISR status of the device */ + bool (*get_config_isr_status)(QVirtioDevice *d); /* Select a queue to work on */ void (*queue_select)(QVirtioDevice *d, uint16_t index); @@ -153,7 +156,9 @@ void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d); void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d); void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d); -bool qvirtio_wait_isr(const QVirtioBus *bus, QVirtioDevice *d, uint8_t mask, +bool qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d, + QVirtQueue *vq, uint64_t timeout); +bool qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d, uint64_t timeout); QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, QGuestAllocator *alloc, uint16_t index); diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 672580b206..0100aaa950 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -134,7 +134,7 @@ static void pci_basic(void) { QVirtioPCIDevice *dev; QPCIBus *bus; - QVirtQueue *vq; + QVirtQueuePCI *vqpci; QGuestAllocator *alloc; QVirtioBlkReq req; void *addr; @@ -162,7 +162,8 @@ static void pci_basic(void) qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); alloc = pc_alloc_init(); - vq = qvirtqueue_setup(&qvirtio_pci, &dev->vdev, alloc, 0); + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); @@ -178,11 +179,11 @@ static void pci_basic(void) g_free(req.data); - free_head = qvirtqueue_add(vq, req_addr, 528, false, true); - qvirtqueue_add(vq, req_addr + 528, 1, true, false); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); @@ -199,12 +200,12 @@ static void pci_basic(void) g_free(req.data); - free_head = qvirtqueue_add(vq, req_addr, 16, false, true); - qvirtqueue_add(vq, req_addr + 16, 513, true, false); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); @@ -226,15 +227,13 @@ static void pci_basic(void) req_addr = virtio_blk_request(alloc, &req, 512); - g_free(req.data); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); - free_head = qvirtqueue_add(vq, req_addr, 16, false, true); - qvirtqueue_add(vq, req_addr + 16, 512, false, true); - qvirtqueue_add(vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); - - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); @@ -251,19 +250,17 @@ static void pci_basic(void) g_free(req.data); - free_head = qvirtqueue_add(vq, req_addr, 16, false, true); - qvirtqueue_add(vq, req_addr + 16, 512, true, true); - qvirtqueue_add(vq, req_addr + 528, 1, true, false); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, true, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); - guest_free(alloc, req_addr); - data = g_malloc0(512); memread(req_addr + 16, data, 512); g_assert_cmpstr(data, ==, "TEST"); @@ -272,7 +269,7 @@ static void pci_basic(void) guest_free(alloc, req_addr); /* End test */ - guest_free(alloc, vq->desc); + guest_free(alloc, vqpci->vq.desc); qvirtio_pci_device_disable(dev); g_free(dev); test_end(); @@ -282,7 +279,7 @@ static void pci_indirect(void) { QVirtioPCIDevice *dev; QPCIBus *bus; - QVirtQueue *vq; + QVirtQueuePCI *vqpci; QGuestAllocator *alloc; QVirtioBlkReq req; QVRingIndirectDesc *indirect; @@ -311,8 +308,8 @@ static void pci_indirect(void) qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); alloc = pc_alloc_init(); - vq = qvirtqueue_setup(&qvirtio_pci, &dev->vdev, alloc, 0); - + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); /* Write request */ @@ -329,10 +326,10 @@ static void pci_indirect(void) indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); qvring_indirect_desc_add(indirect, req_addr, 528, false); qvring_indirect_desc_add(indirect, req_addr + 528, 1, true); - free_head = qvirtqueue_add_indirect(vq, indirect); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); @@ -354,10 +351,10 @@ static void pci_indirect(void) indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); qvring_indirect_desc_add(indirect, req_addr, 16, false); qvring_indirect_desc_add(indirect, req_addr + 16, 513, true); - free_head = qvirtqueue_add_indirect(vq, indirect); - qvirtqueue_kick(&qvirtio_pci, &dev->vdev, vq, free_head); + free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x1, + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); status = readb(req_addr + 528); g_assert_cmpint(status, ==, 0); @@ -371,7 +368,7 @@ static void pci_indirect(void) guest_free(alloc, req_addr); /* End test */ - guest_free(alloc, vq->desc); + guest_free(alloc, vqpci->vq.desc); qvirtio_pci_device_disable(dev); g_free(dev); test_end(); @@ -399,7 +396,7 @@ static void pci_config(void) qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', " " 'size': %d } }", n_size); - g_assert(qvirtio_wait_isr(&qvirtio_pci, &dev->vdev, 0x2, + g_assert(qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev, QVIRTIO_BLK_TIMEOUT)); capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); @@ -410,6 +407,116 @@ static void pci_config(void) test_end(); } +static void pci_msix(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + int n_size = TEST_IMAGE_SIZE / 2; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + alloc = pc_alloc_init(); + + dev = virtio_blk_init(bus); + qpci_msix_enable(dev->pdev); + + qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); + + /* MSI-X is enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | + QVIRTIO_F_RING_EVENT_IDX | QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', " + " 'size': %d } }", n_size); + + g_assert(qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev, + QVIRTIO_BLK_TIMEOUT)); + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, n_size / 512); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT)); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT)); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vqpci->vq.desc); + qpci_msix_disable(dev->pdev); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + int main(int argc, char **argv) { int ret; @@ -419,6 +526,7 @@ int main(int argc, char **argv) g_test_add_func("/virtio/blk/pci/basic", pci_basic); g_test_add_func("/virtio/blk/pci/indirect", pci_indirect); g_test_add_func("/virtio/blk/pci/config", pci_config); + g_test_add_func("/virtio/blk/pci/msix", pci_msix); ret = g_test_run(); From 1053587c3fb50fb78e18a2e32b90e272c1796de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mar=C3=AD?= Date: Mon, 1 Sep 2014 12:08:00 +0200 Subject: [PATCH 10/24] libqos: Added EVENT_IDX support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added avail_event and NO_NOTIFY check before notifying. Added used_event setting. Signed-off-by: Marc Marí Signed-off-by: Stefan Hajnoczi --- tests/libqos/virtio-pci.c | 1 + tests/libqos/virtio.c | 27 ++++++++- tests/libqos/virtio.h | 5 ++ tests/virtio-blk-test.c | 124 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c index ab28717305..788ebaff46 100644 --- a/tests/libqos/virtio-pci.c +++ b/tests/libqos/virtio-pci.c @@ -203,6 +203,7 @@ static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, vqpci->vq.num_free = vqpci->vq.size; vqpci->vq.align = QVIRTIO_PCI_ALIGN; vqpci->vq.indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0; + vqpci->vq.event = (feat & QVIRTIO_F_RING_EVENT_IDX) != 0; vqpci->msix_entry = -1; vqpci->msix_addr = 0; diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c index 16eaf79425..128dbd0e9a 100644 --- a/tests/libqos/virtio.c +++ b/tests/libqos/virtio.c @@ -124,9 +124,13 @@ void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr) writew(vq->avail, 0); /* vq->avail->idx */ writew(vq->avail + 2, 0); + /* vq->avail->used_event */ + writew(vq->avail + 4 + (2 * vq->size), 0); /* vq->used->flags */ writew(vq->used, 0); + /* vq->used->avail_event */ + writew(vq->used+2+(sizeof(struct QVRingUsedElem)*vq->size), 0); } QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, @@ -222,11 +226,32 @@ void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, { /* vq->avail->idx */ uint16_t idx = readl(vq->avail + 2); + /* vq->used->flags */ + uint16_t flags; + /* vq->used->avail_event */ + uint16_t avail_event; /* vq->avail->ring[idx % vq->size] */ writel(vq->avail + 4 + (2 * (idx % vq->size)), free_head); /* vq->avail->idx */ writel(vq->avail + 2, idx + 1); - bus->virtqueue_kick(d, vq); + /* Must read after idx is updated */ + flags = readw(vq->avail); + avail_event = readw(vq->used + 4 + + (sizeof(struct QVRingUsedElem) * vq->size)); + + /* < 1 because we add elements to avail queue one by one */ + if ((flags & QVRING_USED_F_NO_NOTIFY) == 0 && + (!vq->event || (uint16_t)(idx-avail_event) < 1)) { + bus->virtqueue_kick(d, vq); + } +} + +void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx) +{ + g_assert(vq->event); + + /* vq->avail->used_event */ + writew(vq->avail + 4 + (2 * vq->size), idx); } diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h index cebccd21d1..70b3376360 100644 --- a/tests/libqos/virtio.h +++ b/tests/libqos/virtio.h @@ -26,6 +26,7 @@ #define QVIRTIO_F_ANY_LAYOUT 0x08000000 #define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000 #define QVIRTIO_F_RING_EVENT_IDX 0x20000000 +#define QVIRTIO_F_BAD_FEATURE 0x40000000 #define QVRING_DESC_F_NEXT 0x1 #define QVRING_DESC_F_WRITE 0x2 @@ -57,6 +58,7 @@ typedef struct QVRingAvail { uint16_t flags; uint16_t idx; uint16_t ring[0]; /* This is an array of uint16_t */ + uint16_t used_event; } QVRingAvail; typedef struct QVRingUsedElem { @@ -68,6 +70,7 @@ typedef struct QVRingUsed { uint16_t flags; uint16_t idx; QVRingUsedElem ring[0]; /* This is an array of QVRingUsedElem structs */ + uint16_t avail_event; } QVRingUsed; typedef struct QVirtQueue { @@ -80,6 +83,7 @@ typedef struct QVirtQueue { uint32_t num_free; uint32_t align; bool indirect; + bool event; } QVirtQueue; typedef struct QVRingIndirectDesc { @@ -174,4 +178,5 @@ uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect); void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, uint32_t free_head); +void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx); #endif diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 0100aaa950..588666cff1 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -496,6 +496,129 @@ static void pci_msix(void) qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT)); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, (uint64_t)vqpci->vq.desc); + qpci_msix_disable(dev->pdev); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void pci_idx(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + alloc = pc_alloc_init(); + + dev = virtio_blk_init(bus); + qpci_msix_enable(dev->pdev); + + qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); + + /* MSI-X is enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | + QVIRTIO_F_NOTIFY_ON_EMPTY | QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT)); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + /* Notify after processing the third request */ + qvirtqueue_set_used_event(&vqpci->vq, 2); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + /* No notification expected */ + g_assert(!qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT)); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, QVIRTIO_BLK_TIMEOUT)); @@ -527,6 +650,7 @@ int main(int argc, char **argv) g_test_add_func("/virtio/blk/pci/indirect", pci_indirect); g_test_add_func("/virtio/blk/pci/config", pci_config); g_test_add_func("/virtio/blk/pci/msix", pci_msix); + g_test_add_func("/virtio/blk/pci/idx", pci_idx); ret = g_test_run(); From bb87fdf871d321895b8f5c481977df7a3f74a765 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 2 Sep 2014 11:01:02 +0100 Subject: [PATCH 11/24] qemu-img: clarify src_cache option documentation The source cache option takes the same values as the cache option. The documentation reads a little strange because it starts with "In contrast the src_cache option ...". The fact that this is comparing with the previous documented option (the 'cache' option) is implicit. Readers may be confused, especially if they jump to src_cache without reading cache documentation first. Suggested-by: Jeff Nelson Signed-off-by: Stefan Hajnoczi Reviewed-by: Max Reitz --- qemu-img.c | 3 ++- qemu-img.texi | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qemu-img.c b/qemu-img.c index ff29ed1c67..91d1ac3d7d 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -95,7 +95,8 @@ static void QEMU_NORETURN help(void) " 'cache' is the cache mode used to write the output disk image, the valid\n" " options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n" " 'directsync' and 'unsafe' (default for convert)\n" - " 'src_cache' in contrast is the cache mode used to read input disk images\n" + " 'src_cache' is the cache mode used to read input disk images, the valid\n" + " options are the same as for the 'cache' option\n" " 'size' is the disk image size in bytes. Optional suffixes\n" " 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' (gigabyte, 1024M),\n" " 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' (exabyte, 1024P) are\n" diff --git a/qemu-img.texi b/qemu-img.texi index cb689483b6..4380d56889 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -73,8 +73,9 @@ specifies the cache mode that should be used with the (destination) file. See the documentation of the emulator's @code{-drive cache=...} option for allowed values. @item -T @var{src_cache} -in contrast specifies the cache mode that should be used with the source -file(s). +specifies the cache mode that should be used with the source file(s). See +the documentation of the emulator's @code{-drive cache=...} option for allowed +values. @end table Parameters to snapshot subcommand: From 3ba6796d080a90440573ef29d657e4902be7e238 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 2 Sep 2014 11:01:03 +0100 Subject: [PATCH 12/24] qemu-img: fix rebase src_cache option documentation The src_cache option (-T) specifies the cache mode for backing files. It applies both the image's old backing file as well as the new backing file: ret = bdrv_open(&bs_old_backing, backing_name, NULL, NULL, src_flags, old_backing_drv, &local_err); if (ret) { ... } if (out_baseimg[0]) { bs_new_backing = bdrv_new("new_backing", &error_abort); ret = bdrv_open(&bs_new_backing, out_baseimg, NULL, NULL, src_flags, new_backing_drv, &local_err); if (ret) { ... } } The documentation only mentions the new backing file but it really applies to both. Suggested-by: Jeff Nelson Signed-off-by: Stefan Hajnoczi Reviewed-by: Max Reitz --- qemu-img.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qemu-img.texi b/qemu-img.texi index 4380d56889..cc4668e64f 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -341,7 +341,7 @@ string), then the image is rebased onto no backing file (i.e. it will exist independently of any backing file). @var{cache} specifies the cache mode to be used for @var{filename}, whereas -@var{src_cache} specifies the cache mode for reading the new backing file. +@var{src_cache} specifies the cache mode for reading backing files. There are two different modes in which @code{rebase} can operate: @table @option From 072f9ac44aac54ee9ac006e8b42841abfde48a8b Mon Sep 17 00:00:00 2001 From: Chrysostomos Nanakos Date: Tue, 2 Sep 2014 15:41:34 +0300 Subject: [PATCH 13/24] block/archipelago: Use QEMU atomic builtins Replace __sync builtins with ones provided by QEMU for atomic operations. Special thanks goes to Paolo Bonzini for his refactoring suggestion in order to use the already existing atomic builtins interface. Signed-off-by: Chrysostomos Nanakos Signed-off-by: Stefan Hajnoczi --- block/archipelago.c | 76 ++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/block/archipelago.c b/block/archipelago.c index 34f72dc5a5..22a7daaa41 100644 --- a/block/archipelago.c +++ b/block/archipelago.c @@ -57,6 +57,7 @@ #include "qapi/qmp/qint.h" #include "qapi/qmp/qstring.h" #include "qapi/qmp/qjson.h" +#include "qemu/atomic.h" #include #include @@ -214,7 +215,7 @@ static void xseg_request_handler(void *state) xseg_put_request(s->xseg, req, s->srcport); - if ((__sync_add_and_fetch(&segreq->ref, -1)) == 0) { + if (atomic_fetch_dec(&segreq->ref) == 1) { if (!segreq->failed) { reqdata->aio_cb->ret = segreq->count; archipelago_finish_aiocb(reqdata); @@ -233,7 +234,7 @@ static void xseg_request_handler(void *state) segreq->count += req->serviced; xseg_put_request(s->xseg, req, s->srcport); - if ((__sync_add_and_fetch(&segreq->ref, -1)) == 0) { + if (atomic_fetch_dec(&segreq->ref) == 1) { if (!segreq->failed) { reqdata->aio_cb->ret = segreq->count; archipelago_finish_aiocb(reqdata); @@ -824,78 +825,47 @@ static int archipelago_aio_segmented_rw(BDRVArchipelagoState *s, ArchipelagoAIOCB *aio_cb, int op) { - int i, ret, segments_nr, last_segment_size; + int ret, segments_nr; + size_t pos = 0; ArchipelagoSegmentedRequest *segreq; - segreq = g_new(ArchipelagoSegmentedRequest, 1); + segreq = g_new0(ArchipelagoSegmentedRequest, 1); if (op == ARCHIP_OP_FLUSH) { segments_nr = 1; - segreq->ref = segments_nr; - segreq->total = count; - segreq->count = 0; - segreq->failed = 0; - ret = archipelago_submit_request(s, 0, count, offset, aio_cb, - segreq, ARCHIP_OP_FLUSH); - if (ret < 0) { - goto err_exit; - } - return 0; + } else { + segments_nr = (int)(count / MAX_REQUEST_SIZE) + \ + ((count % MAX_REQUEST_SIZE) ? 1 : 0); } - - segments_nr = (int)(count / MAX_REQUEST_SIZE) + \ - ((count % MAX_REQUEST_SIZE) ? 1 : 0); - last_segment_size = (int)(count % MAX_REQUEST_SIZE); - - segreq->ref = segments_nr; segreq->total = count; - segreq->count = 0; - segreq->failed = 0; + atomic_mb_set(&segreq->ref, segments_nr); - for (i = 0; i < segments_nr - 1; i++) { - ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE, - MAX_REQUEST_SIZE, - offset + i * MAX_REQUEST_SIZE, - aio_cb, segreq, op); + while (segments_nr > 1) { + ret = archipelago_submit_request(s, pos, + MAX_REQUEST_SIZE, + offset + pos, + aio_cb, segreq, op); if (ret < 0) { goto err_exit; } + count -= MAX_REQUEST_SIZE; + pos += MAX_REQUEST_SIZE; + segments_nr--; } - - if ((segments_nr > 1) && last_segment_size) { - ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE, - last_segment_size, - offset + i * MAX_REQUEST_SIZE, - aio_cb, segreq, op); - } else if ((segments_nr > 1) && !last_segment_size) { - ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE, - MAX_REQUEST_SIZE, - offset + i * MAX_REQUEST_SIZE, - aio_cb, segreq, op); - } else if (segments_nr == 1) { - ret = archipelago_submit_request(s, 0, count, offset, aio_cb, - segreq, op); - } + ret = archipelago_submit_request(s, pos, count, offset + pos, + aio_cb, segreq, op); if (ret < 0) { goto err_exit; } - return 0; err_exit: - __sync_add_and_fetch(&segreq->failed, 1); - if (segments_nr == 1) { - if (__sync_add_and_fetch(&segreq->ref, -1) == 0) { - g_free(segreq); - } - } else { - if ((__sync_add_and_fetch(&segreq->ref, -segments_nr + i)) == 0) { - g_free(segreq); - } + segreq->failed = 1; + if (atomic_fetch_sub(&segreq->ref, segments_nr) == segments_nr) { + g_free(segreq); } - return ret; } From 9e7dac7c6c6003ad9d4aca0125f0278233fcf761 Mon Sep 17 00:00:00 2001 From: Peter Lieven Date: Wed, 13 Aug 2014 19:20:17 +0200 Subject: [PATCH 14/24] rename parse_enum_option to qapi_enum_parse and make it public relaxing the license to LGPLv2+ is intentional. Suggested-by: Markus Armbruster Signed-off-by: Hu Tao Signed-off-by: Peter Lieven Reviewed-by: Eric Blake Reviewed-by: Benoit Canet Signed-off-by: Stefan Hajnoczi --- blockdev.c | 30 ++++++------------------------ include/qapi/util.h | 17 +++++++++++++++++ qapi/Makefile.objs | 2 +- qapi/qapi-util.c | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 include/qapi/util.h create mode 100644 qapi/qapi-util.c diff --git a/blockdev.c b/blockdev.c index e37b068e9e..e919566c16 100644 --- a/blockdev.c +++ b/blockdev.c @@ -39,6 +39,7 @@ #include "qapi/qmp/types.h" #include "qapi-visit.h" #include "qapi/qmp-output-visitor.h" +#include "qapi/util.h" #include "sysemu/sysemu.h" #include "block/block_int.h" #include "qmp-commands.h" @@ -274,25 +275,6 @@ static int parse_block_error_action(const char *buf, bool is_read, Error **errp) } } -static inline int parse_enum_option(const char *lookup[], const char *buf, - int max, int def, Error **errp) -{ - int i; - - if (!buf) { - return def; - } - - for (i = 0; i < max; i++) { - if (!strcmp(buf, lookup[i])) { - return i; - } - } - - error_setg(errp, "invalid parameter value: %s", buf); - return def; -} - static bool check_throttle_config(ThrottleConfig *cfg, Error **errp) { if (throttle_conflicting(cfg)) { @@ -456,11 +438,11 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts, } detect_zeroes = - parse_enum_option(BlockdevDetectZeroesOptions_lookup, - qemu_opt_get(opts, "detect-zeroes"), - BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX, - BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, - &error); + qapi_enum_parse(BlockdevDetectZeroesOptions_lookup, + qemu_opt_get(opts, "detect-zeroes"), + BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX, + BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, + &error); if (error) { error_propagate(errp, error); goto early_err; diff --git a/include/qapi/util.h b/include/qapi/util.h new file mode 100644 index 0000000000..de9238bf95 --- /dev/null +++ b/include/qapi/util.h @@ -0,0 +1,17 @@ +/* + * QAPI util functions + * + * Copyright Fujitsu, Inc. 2014 + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QAPI_UTIL_H +#define QAPI_UTIL_H + +int qapi_enum_parse(const char *lookup[], const char *buf, + int max, int def, Error **errp); + +#endif diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs index d14b769cff..2278970690 100644 --- a/qapi/Makefile.objs +++ b/qapi/Makefile.objs @@ -1,6 +1,6 @@ util-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o util-obj-y += string-input-visitor.o string-output-visitor.o - util-obj-y += opts-visitor.o util-obj-y += qmp-event.o +util-obj-y += qapi-util.o diff --git a/qapi/qapi-util.c b/qapi/qapi-util.c new file mode 100644 index 0000000000..1d8fb96eff --- /dev/null +++ b/qapi/qapi-util.c @@ -0,0 +1,34 @@ +/* + * QAPI util functions + * + * Authors: + * Hu Tao + * Peter Lieven + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu-common.h" +#include "qapi/error.h" +#include "qapi/util.h" + +int qapi_enum_parse(const char *lookup[], const char *buf, + int max, int def, Error **errp) +{ + int i; + + if (!buf) { + return def; + } + + for (i = 0; i < max; i++) { + if (!strcmp(buf, lookup[i])) { + return i; + } + } + + error_setg(errp, "invalid parameter value: %s", buf); + return def; +} From b3838a4088865d1767e137d2b7974b6885822eb2 Mon Sep 17 00:00:00 2001 From: Peter Lieven Date: Wed, 13 Aug 2014 19:20:18 +0200 Subject: [PATCH 15/24] qemu-nbd: add option to set detect-zeroes mode Signed-off-by: Peter Lieven Reviewed-by: Eric Blake Reviewed-by: Benoit Canet Signed-off-by: Stefan Hajnoczi --- qemu-nbd.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/qemu-nbd.c b/qemu-nbd.c index 626e5844f9..6ef8b10bcc 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -18,11 +18,13 @@ #include "qemu-common.h" #include "block/block.h" +#include "block/block_int.h" #include "block/nbd.h" #include "qemu/main-loop.h" #include "qemu/sockets.h" #include "qemu/error-report.h" #include "block/snapshot.h" +#include "qapi/util.h" #include #include @@ -41,6 +43,7 @@ #define QEMU_NBD_OPT_CACHE 1 #define QEMU_NBD_OPT_AIO 2 #define QEMU_NBD_OPT_DISCARD 3 +#define QEMU_NBD_OPT_DETECT_ZEROES 4 static NBDExport *exp; static int verbose; @@ -96,6 +99,8 @@ static void usage(const char *name) #ifdef CONFIG_LINUX_AIO " --aio=MODE set AIO mode (native or threads)\n" #endif +" --discard=MODE set discard mode (ignore, unmap)\n" +" --detect-zeroes=MODE set detect-zeroes mode (off, on, discard)\n" "\n" "Report bugs to \n" , name, NBD_DEFAULT_PORT, "DEVICE"); @@ -410,6 +415,7 @@ int main(int argc, char **argv) { "aio", 1, NULL, QEMU_NBD_OPT_AIO }, #endif { "discard", 1, NULL, QEMU_NBD_OPT_DISCARD }, + { "detect-zeroes", 1, NULL, QEMU_NBD_OPT_DETECT_ZEROES }, { "shared", 1, NULL, 'e' }, { "format", 1, NULL, 'f' }, { "persistent", 0, NULL, 't' }, @@ -432,6 +438,7 @@ int main(int argc, char **argv) pthread_t client_thread; const char *fmt = NULL; Error *local_err = NULL; + BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF; /* The client thread uses SIGTERM to interrupt the server. A signal * handler ensures that "qemu-nbd -v -c" exits with a nice status code. @@ -483,6 +490,23 @@ int main(int argc, char **argv) errx(EXIT_FAILURE, "Invalid discard mode `%s'", optarg); } break; + case QEMU_NBD_OPT_DETECT_ZEROES: + detect_zeroes = + qapi_enum_parse(BlockdevDetectZeroesOptions_lookup, + optarg, + BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX, + BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, + &local_err); + if (local_err) { + errx(EXIT_FAILURE, "Failed to parse detect_zeroes mode: %s", + error_get_pretty(local_err)); + } + if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP && + !(flags & BDRV_O_UNMAP)) { + errx(EXIT_FAILURE, "setting detect-zeroes to unmap is not allowed " + "without setting discard operation to unmap"); + } + break; case 'b': bindto = optarg; break; @@ -686,6 +710,7 @@ int main(int argc, char **argv) error_get_pretty(local_err)); } + bs->detect_zeroes = detect_zeroes; fd_size = bdrv_getlength(bs); if (partition != -1) { From 713cc671f107eb8f6ced6ff1e7cf088029619731 Mon Sep 17 00:00:00 2001 From: Peter Lieven Date: Wed, 13 Aug 2014 19:20:19 +0200 Subject: [PATCH 16/24] qemu-nbd: fix indentation and coding style Signed-off-by: Peter Lieven Reviewed-by: Eric Blake Reviewed-by: Benoit Canet Signed-off-by: Stefan Hajnoczi --- qemu-nbd.c | 75 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/qemu-nbd.c b/qemu-nbd.c index 6ef8b10bcc..9bc152e6c3 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -39,10 +39,10 @@ #include #include -#define SOCKET_PATH "/var/lock/qemu-nbd-%s" -#define QEMU_NBD_OPT_CACHE 1 -#define QEMU_NBD_OPT_AIO 2 -#define QEMU_NBD_OPT_DISCARD 3 +#define SOCKET_PATH "/var/lock/qemu-nbd-%s" +#define QEMU_NBD_OPT_CACHE 1 +#define QEMU_NBD_OPT_AIO 2 +#define QEMU_NBD_OPT_DISCARD 3 #define QEMU_NBD_OPT_DETECT_ZEROES 4 static NBDExport *exp; @@ -60,44 +60,44 @@ static void usage(const char *name) "Usage: %s [OPTIONS] FILE\n" "QEMU Disk Network Block Device Server\n" "\n" -" -h, --help display this help and exit\n" -" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" -V, --version output version information and exit\n" "\n" "Connection properties:\n" -" -p, --port=PORT port to listen on (default `%d')\n" -" -b, --bind=IFACE interface to bind to (default `0.0.0.0')\n" -" -k, --socket=PATH path to the unix socket\n" -" (default '"SOCKET_PATH"')\n" -" -e, --shared=NUM device can be shared by NUM clients (default '1')\n" -" -t, --persistent don't exit on the last connection\n" -" -v, --verbose display extra debugging information\n" +" -p, --port=PORT port to listen on (default `%d')\n" +" -b, --bind=IFACE interface to bind to (default `0.0.0.0')\n" +" -k, --socket=PATH path to the unix socket\n" +" (default '"SOCKET_PATH"')\n" +" -e, --shared=NUM device can be shared by NUM clients (default '1')\n" +" -t, --persistent don't exit on the last connection\n" +" -v, --verbose display extra debugging information\n" "\n" "Exposing part of the image:\n" -" -o, --offset=OFFSET offset into the image\n" -" -P, --partition=NUM only expose partition NUM\n" +" -o, --offset=OFFSET offset into the image\n" +" -P, --partition=NUM only expose partition NUM\n" "\n" #ifdef __linux__ "Kernel NBD client support:\n" -" -c, --connect=DEV connect FILE to the local NBD device DEV\n" -" -d, --disconnect disconnect the specified device\n" +" -c, --connect=DEV connect FILE to the local NBD device DEV\n" +" -d, --disconnect disconnect the specified device\n" "\n" #endif "\n" "Block device options:\n" -" -f, --format=FORMAT set image format (raw, qcow2, ...)\n" -" -r, --read-only export read-only\n" -" -s, --snapshot use FILE as an external snapshot, create a temporary\n" -" file with backing_file=FILE, redirect the write to\n" -" the temporary one\n" +" -f, --format=FORMAT set image format (raw, qcow2, ...)\n" +" -r, --read-only export read-only\n" +" -s, --snapshot use FILE as an external snapshot, create a temporary\n" +" file with backing_file=FILE, redirect the write to\n" +" the temporary one\n" " -l, --load-snapshot=SNAPSHOT_PARAM\n" -" load an internal snapshot inside FILE and export it\n" -" as an read-only device, SNAPSHOT_PARAM format is\n" -" 'snapshot.id=[ID],snapshot.name=[NAME]', or\n" -" '[ID_OR_NAME]'\n" -" -n, --nocache disable host cache\n" -" --cache=MODE set cache mode (none, writeback, ...)\n" +" load an internal snapshot inside FILE and export it\n" +" as an read-only device, SNAPSHOT_PARAM format is\n" +" 'snapshot.id=[ID],snapshot.name=[NAME]', or\n" +" '[ID_OR_NAME]'\n" +" -n, --nocache disable host cache\n" +" --cache=MODE set cache mode (none, writeback, ...)\n" #ifdef CONFIG_LINUX_AIO -" --aio=MODE set AIO mode (native or threads)\n" +" --aio=MODE set AIO mode (native or threads)\n" #endif " --discard=MODE set discard mode (ignore, unmap)\n" " --detect-zeroes=MODE set detect-zeroes mode (off, on, discard)\n" @@ -546,15 +546,18 @@ int main(int argc, char **argv) break; case 'P': partition = strtol(optarg, &end, 0); - if (*end) + if (*end) { errx(EXIT_FAILURE, "Invalid partition `%s'", optarg); - if (partition < 1 || partition > 8) + } + if (partition < 1 || partition > 8) { errx(EXIT_FAILURE, "Invalid partition %d", partition); + } break; case 'k': sockpath = optarg; - if (sockpath[0] != '/') + if (sockpath[0] != '/') { errx(EXIT_FAILURE, "socket path must be absolute\n"); + } break; case 'd': disconnect = true; @@ -574,9 +577,9 @@ int main(int argc, char **argv) case 'f': fmt = optarg; break; - case 't': - persistent = 1; - break; + case 't': + persistent = 1; + break; case 'v': verbose = 1; break; @@ -611,7 +614,7 @@ int main(int argc, char **argv) printf("%s disconnected\n", argv[optind]); - return 0; + return 0; } if (device && !verbose) { From 53b33231f76e85984eb18a4c63fd132d0d4e3f40 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Wed, 3 Sep 2014 22:13:17 +0900 Subject: [PATCH 17/24] MAINTAINERS: update sheepdog maintainer Hitoshi takes over sheepdog maintenance from me. Signed-off-by: MORITA Kazutaka Signed-off-by: Stefan Hajnoczi --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 8622e019fa..206bf7ea45 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -980,7 +980,7 @@ S: Supported F: block/rbd.c Sheepdog -M: MORITA Kazutaka +M: Hitoshi Mitake M: Liu Yuan L: sheepdog@lists.wpkg.org S: Supported From ec2f160538a24b72767e2afd1f0ba3085a35b79d Mon Sep 17 00:00:00 2001 From: John Snow Date: Fri, 1 Aug 2014 11:38:58 -0400 Subject: [PATCH 18/24] libqos: add a simple first-fit memory allocator Implement a simple first-fit memory allocator that attempts to keep track of leased blocks of memory in order to be able to re-use blocks. Additionally, allow the user to specify when initializing the device that upon cleanup, we would like to assert that there are no blocks in use. This may be useful for identifying problems in qtests that use more complicated set-up and tear-down routines. This functionality is used in my upcoming ahci-test v2 patch set, but I didn't see fit to enable it for any existing tests, which will continue to operate the same as they have prior. Signed-off-by: John Snow Signed-off-by: Stefan Hajnoczi --- tests/libqos/malloc-pc.c | 282 +++++++++++++++++++++++++++++++++++++-- tests/libqos/malloc-pc.h | 9 ++ 2 files changed, 280 insertions(+), 11 deletions(-) diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c index be1d97f8bb..f4218c6451 100644 --- a/tests/libqos/malloc-pc.c +++ b/tests/libqos/malloc-pc.c @@ -17,45 +17,294 @@ #include "hw/nvram/fw_cfg.h" #include "qemu-common.h" +#include "qemu/queue.h" #include #define PAGE_SIZE (4096) +#define MLIST_ENTNAME entries +typedef QTAILQ_HEAD(MemList, MemBlock) MemList; +typedef struct MemBlock { + QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME; + uint64_t size; + uint64_t addr; +} MemBlock; + typedef struct PCAlloc { QGuestAllocator alloc; - + PCAllocOpts opts; uint64_t start; uint64_t end; + + MemList used; + MemList free; } PCAlloc; +static MemBlock *mlist_new(uint64_t addr, uint64_t size) +{ + MemBlock *block; + + if (!size) { + return NULL; + } + block = g_malloc0(sizeof(MemBlock)); + + block->addr = addr; + block->size = size; + + return block; +} + +static void mlist_delete(MemList *list, MemBlock *node) +{ + g_assert(list && node); + QTAILQ_REMOVE(list, node, MLIST_ENTNAME); + g_free(node); +} + +static MemBlock *mlist_find_key(MemList *head, uint64_t addr) +{ + MemBlock *node; + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->addr == addr) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_find_space(MemList *head, uint64_t size) +{ + MemBlock *node; + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->size >= size) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr) +{ + MemBlock *node; + g_assert(head && insr); + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (insr->addr < node->addr) { + QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME); + return insr; + } + } + + QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME); + return insr; +} + +static inline uint64_t mlist_boundary(MemBlock *node) +{ + return node->size + node->addr; +} + +static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right) +{ + g_assert(head && left && right); + + left->size += right->size; + mlist_delete(head, right); + return left; +} + +static void mlist_coalesce(MemList *head, MemBlock *node) +{ + g_assert(node); + MemBlock *left; + MemBlock *right; + char merge; + + do { + merge = 0; + left = QTAILQ_PREV(node, MemList, MLIST_ENTNAME); + right = QTAILQ_NEXT(node, MLIST_ENTNAME); + + /* clowns to the left of me */ + if (left && mlist_boundary(left) == node->addr) { + node = mlist_join(head, left, node); + merge = 1; + } + + /* jokers to the right */ + if (right && mlist_boundary(node) == right->addr) { + node = mlist_join(head, node, right); + merge = 1; + } + + } while (merge); +} + +static uint64_t pc_mlist_fulfill(PCAlloc *s, MemBlock *freenode, uint64_t size) +{ + uint64_t addr; + MemBlock *usednode; + + g_assert(freenode); + g_assert_cmpint(freenode->size, >=, size); + + addr = freenode->addr; + if (freenode->size == size) { + /* re-use this freenode as our used node */ + QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME); + usednode = freenode; + } else { + /* adjust the free node and create a new used node */ + freenode->addr += size; + freenode->size -= size; + usednode = mlist_new(addr, size); + } + + mlist_sort_insert(&s->used, usednode); + return addr; +} + +/* To assert the correctness of the list. + * Used only if PC_ALLOC_PARANOID is set. */ +static void pc_mlist_check(PCAlloc *s) +{ + MemBlock *node; + uint64_t addr = s->start > 0 ? s->start - 1 : 0; + uint64_t next = s->start; + + QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } + + addr = s->start > 0 ? s->start - 1 : 0; + next = s->start; + QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } +} + +static uint64_t pc_mlist_alloc(PCAlloc *s, uint64_t size) +{ + MemBlock *node; + + node = mlist_find_space(&s->free, size); + if (!node) { + fprintf(stderr, "Out of guest memory.\n"); + g_assert_not_reached(); + } + return pc_mlist_fulfill(s, node, size); +} + +static void pc_mlist_free(PCAlloc *s, uint64_t addr) +{ + MemBlock *node; + + if (addr == 0) { + return; + } + + node = mlist_find_key(&s->used, addr); + if (!node) { + fprintf(stderr, "Error: no record found for an allocation at " + "0x%016" PRIx64 ".\n", + addr); + g_assert_not_reached(); + } + + /* Rip it out of the used list and re-insert back into the free list. */ + QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME); + mlist_sort_insert(&s->free, node); + mlist_coalesce(&s->free, node); +} + static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size) { PCAlloc *s = container_of(allocator, PCAlloc, alloc); - uint64_t addr; + uint64_t rsize = size; + uint64_t naddr; + rsize += (PAGE_SIZE - 1); + rsize &= -PAGE_SIZE; + g_assert_cmpint((s->start + rsize), <=, s->end); + g_assert_cmpint(rsize, >=, size); - size += (PAGE_SIZE - 1); - size &= -PAGE_SIZE; + naddr = pc_mlist_alloc(s, rsize); + if (s->opts & PC_ALLOC_PARANOID) { + pc_mlist_check(s); + } - g_assert_cmpint((s->start + size), <=, s->end); - - addr = s->start; - s->start += size; - - return addr; + return naddr; } static void pc_free(QGuestAllocator *allocator, uint64_t addr) { + PCAlloc *s = container_of(allocator, PCAlloc, alloc); + + pc_mlist_free(s, addr); + if (s->opts & PC_ALLOC_PARANOID) { + pc_mlist_check(s); + } } -QGuestAllocator *pc_alloc_init(void) +/* + * Mostly for valgrind happiness, but it does offer + * a chokepoint for debugging guest memory leaks, too. + */ +void pc_alloc_uninit(QGuestAllocator *allocator) +{ + PCAlloc *s = container_of(allocator, PCAlloc, alloc); + MemBlock *node; + MemBlock *tmp; + PCAllocOpts mask; + + /* Check for guest leaks, and destroy the list. */ + QTAILQ_FOREACH_SAFE(node, &s->used, MLIST_ENTNAME, tmp) { + if (s->opts & (PC_ALLOC_LEAK_WARN | PC_ALLOC_LEAK_ASSERT)) { + fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; " + "size 0x%016" PRIx64 ".\n", + node->addr, node->size); + } + if (s->opts & (PC_ALLOC_LEAK_ASSERT)) { + g_assert_not_reached(); + } + g_free(node); + } + + /* If we have previously asserted that there are no leaks, then there + * should be only one node here with a specific address and size. */ + mask = PC_ALLOC_LEAK_ASSERT | PC_ALLOC_PARANOID; + QTAILQ_FOREACH_SAFE(node, &s->free, MLIST_ENTNAME, tmp) { + if ((s->opts & mask) == mask) { + if ((node->addr != s->start) || + (node->size != s->end - s->start)) { + fprintf(stderr, "Free list is corrupted.\n"); + g_assert_not_reached(); + } + } + + g_free(node); + } + + g_free(s); +} + +QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags) { PCAlloc *s = g_malloc0(sizeof(*s)); uint64_t ram_size; QFWCFG *fw_cfg = pc_fw_cfg_init(); + MemBlock *node; + s->opts = flags; s->alloc.alloc = pc_alloc; s->alloc.free = pc_free; @@ -70,5 +319,16 @@ QGuestAllocator *pc_alloc_init(void) /* clean-up */ g_free(fw_cfg); + QTAILQ_INIT(&s->used); + QTAILQ_INIT(&s->free); + + node = mlist_new(s->start, s->end - s->start); + QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME); + return &s->alloc; } + +inline QGuestAllocator *pc_alloc_init(void) +{ + return pc_alloc_init_flags(PC_ALLOC_NO_FLAGS); +} diff --git a/tests/libqos/malloc-pc.h b/tests/libqos/malloc-pc.h index ff964abe53..9f525e3b99 100644 --- a/tests/libqos/malloc-pc.h +++ b/tests/libqos/malloc-pc.h @@ -15,6 +15,15 @@ #include "libqos/malloc.h" +typedef enum { + PC_ALLOC_NO_FLAGS = 0x00, + PC_ALLOC_LEAK_WARN = 0x01, + PC_ALLOC_LEAK_ASSERT = 0x02, + PC_ALLOC_PARANOID = 0x04 +} PCAllocOpts; + QGuestAllocator *pc_alloc_init(void); +QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags); +void pc_alloc_uninit(QGuestAllocator *allocator); #endif From 0142f88bff3dd5cb819c9900da1c1e0a4aae9c44 Mon Sep 17 00:00:00 2001 From: John Snow Date: Fri, 1 Aug 2014 11:38:59 -0400 Subject: [PATCH 19/24] qtest/ide: Uninitialize PC allocator Use the new call to pc_alloc_uninit as a test for the new pathways. The leak checking / assert pathways are not enabled in this patch, leaving this as an option to future test writers. Signed-off-by: John Snow Signed-off-by: Stefan Hajnoczi --- tests/ide-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ide-test.c b/tests/ide-test.c index ffce6ed669..b7a97e9362 100644 --- a/tests/ide-test.c +++ b/tests/ide-test.c @@ -126,6 +126,8 @@ static void ide_test_start(const char *cmdline_fmt, ...) static void ide_test_quit(void) { + pc_alloc_uninit(guest_malloc); + guest_malloc = NULL; qtest_end(); } From c5fe97e359bf03db9a005433092f25d27d57398f Mon Sep 17 00:00:00 2001 From: John Snow Date: Tue, 19 Aug 2014 14:57:55 -0400 Subject: [PATCH 20/24] ide: Add wwn support to IDE-ATAPI drive Although it is possible to specify the wwn property for cdrom devices on the command line, the underlying driver fails to relay this information to the guest operating system via IDENTIFY. This is a simple patch to correct that. See ATA8-ACS, Table 22 parts 5, 6, and 9. Signed-off-by: John Snow Reviewed-by: Fam Zheng Signed-off-by: Stefan Hajnoczi --- hw/ide/core.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hw/ide/core.c b/hw/ide/core.c index b48127f921..de0e5e95ba 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -230,9 +230,23 @@ static void ide_atapi_identify(IDEState *s) } put_le16(p + 80, 0x1e); /* support up to ATA/ATAPI-4 */ + if (s->wwn) { + put_le16(p + 84, (1 << 8)); /* supports WWN for words 108-111 */ + put_le16(p + 87, (1 << 8)); /* WWN enabled */ + } + #ifdef USE_DMA_CDROM put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ #endif + + if (s->wwn) { + /* LE 16-bit words 111-108 contain 64-bit World Wide Name */ + put_le16(p + 108, s->wwn >> 48); + put_le16(p + 109, s->wwn >> 32); + put_le16(p + 110, s->wwn >> 16); + put_le16(p + 111, s->wwn); + } + memcpy(s->identify_data, p, sizeof(s->identify_data)); s->identify_set = 1; } From ff74f33c310892c90c4439d963a6ce67f47ce18c Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 4 Sep 2014 21:04:42 +0100 Subject: [PATCH 21/24] vmdk: fix vmdk_parse_extents() extent_file leaks Signed-off-by: Stefan Hajnoczi Reviewed-by: Max Reitz Reviewed-by: Fam Zheng --- block/vmdk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/vmdk.c b/block/vmdk.c index 07cb62ceb7..9bf28f3390 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -834,6 +834,7 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, ret = vmdk_add_extent(bs, extent_file, true, sectors, 0, 0, 0, 0, 0, &extent, errp); if (ret < 0) { + bdrv_unref(extent_file); return ret; } extent->flat_start_offset = flat_offset << 9; @@ -853,6 +854,7 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, extent = &s->extents[s->num_extents - 1]; } else { error_setg(errp, "Unsupported extent type '%s'", type); + bdrv_unref(extent_file); return -ENOTSUP; } extent->type = g_strdup(type); From b6b1d31f098eef8cd13556d343e46c213fac972a Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 4 Sep 2014 21:04:43 +0100 Subject: [PATCH 22/24] vmdk: fix buf leak in vmdk_parse_extents() vmdk_open_sparse() does not take ownership of buf so the caller always needs to free it. Signed-off-by: Stefan Hajnoczi Reviewed-by: Max Reitz Reviewed-by: Fam Zheng --- block/vmdk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/vmdk.c b/block/vmdk.c index 9bf28f3390..a1cb91131e 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -846,8 +846,8 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, } else { ret = vmdk_open_sparse(bs, extent_file, bs->open_flags, buf, errp); } + g_free(buf); if (ret) { - g_free(buf); bdrv_unref(extent_file); return ret; } From 4bf6637d35723f92e03f427c78d7ad130be41e6f Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 4 Sep 2014 23:42:16 -0400 Subject: [PATCH 23/24] IDE: Fill the IDENTIFY request consistently IDE-HD, IDE-ATAPI and IDE-CFATA all fill the identify buffer in slightly different ways, this is a relatively minor patch to make them uniform, to emphasize that: (1) We build the s->identify_data cache first, then (2) We copy it to s->io_buffer to fulfill the request. Signed-off-by: John Snow Signed-off-by: Stefan Hajnoczi --- hw/ide/core.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c index de0e5e95ba..e0232d4e96 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -81,13 +81,12 @@ static void ide_identify(IDEState *s) unsigned int oldsize; IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master; + p = (uint16_t *)s->identify_data; if (s->identify_set) { - memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); - return; + goto fill_buffer; } + memset(p, 0, sizeof(s->identify_data)); - memset(s->io_buffer, 0, 512); - p = (uint16_t *)s->io_buffer; put_le16(p + 0, 0x0040); put_le16(p + 1, s->cylinders); put_le16(p + 3, s->heads); @@ -180,21 +179,22 @@ static void ide_identify(IDEState *s) put_le16(p + 169, 1); /* TRIM support */ } - memcpy(s->identify_data, p, sizeof(s->identify_data)); s->identify_set = 1; + +fill_buffer: + memcpy(s->io_buffer, p, sizeof(s->identify_data)); } static void ide_atapi_identify(IDEState *s) { uint16_t *p; + p = (uint16_t *)s->identify_data; if (s->identify_set) { - memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); - return; + goto fill_buffer; } + memset(p, 0, sizeof(s->identify_data)); - memset(s->io_buffer, 0, 512); - p = (uint16_t *)s->io_buffer; /* Removable CDROM, 50us response, 12 byte packets */ put_le16(p + 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0)); padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */ @@ -247,8 +247,10 @@ static void ide_atapi_identify(IDEState *s) put_le16(p + 111, s->wwn); } - memcpy(s->identify_data, p, sizeof(s->identify_data)); s->identify_set = 1; + +fill_buffer: + memcpy(s->io_buffer, p, sizeof(s->identify_data)); } static void ide_cfata_identify(IDEState *s) @@ -256,10 +258,10 @@ static void ide_cfata_identify(IDEState *s) uint16_t *p; uint32_t cur_sec; - p = (uint16_t *) s->identify_data; - if (s->identify_set) + p = (uint16_t *)s->identify_data; + if (s->identify_set) { goto fill_buffer; - + } memset(p, 0, sizeof(s->identify_data)); cur_sec = s->cylinders * s->heads * s->sectors; From 01ce352e62c3f86df6f4ad32c3ab9353e55af799 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 4 Sep 2014 23:42:17 -0400 Subject: [PATCH 24/24] ide: Add resize callback to ide/core Currently, if the block device backing the IDE drive is resized, the information about the device as cached inside of the IDEState structure is not updated, thus when a guest OS re-queries the drive, it is unable to see the expanded size. This patch adds a resize callback that updates the IDENTIFY data buffer in order to correct this. Lastly, a Linux guest as-is cannot resize a libata drive while in-use, but it can see the expanded size as part of a bus rescan event. This patch also allows guests such as Linux to see the new drive size after a soft reboot event, without having to exit the QEMU process. Signed-off-by: John Snow Signed-off-by: Stefan Hajnoczi --- hw/ide/core.c | 69 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c index e0232d4e96..191f89321e 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -75,6 +75,17 @@ static void put_le16(uint16_t *p, unsigned int v) *p = cpu_to_le16(v); } +static void ide_identify_size(IDEState *s) +{ + uint16_t *p = (uint16_t *)s->identify_data; + put_le16(p + 60, s->nb_sectors); + put_le16(p + 61, s->nb_sectors >> 16); + put_le16(p + 100, s->nb_sectors); + put_le16(p + 101, s->nb_sectors >> 16); + put_le16(p + 102, s->nb_sectors >> 32); + put_le16(p + 103, s->nb_sectors >> 48); +} + static void ide_identify(IDEState *s) { uint16_t *p; @@ -115,8 +126,8 @@ static void ide_identify(IDEState *s) put_le16(p + 58, oldsize >> 16); if (s->mult_sectors) put_le16(p + 59, 0x100 | s->mult_sectors); - put_le16(p + 60, s->nb_sectors); - put_le16(p + 61, s->nb_sectors >> 16); + /* *(p + 60) := nb_sectors -- see ide_identify_size */ + /* *(p + 61) := nb_sectors >> 16 -- see ide_identify_size */ put_le16(p + 62, 0x07); /* single word dma0-2 supported */ put_le16(p + 63, 0x07); /* mdma0-2 supported */ put_le16(p + 64, 0x03); /* pio3-4 supported */ @@ -161,10 +172,10 @@ static void ide_identify(IDEState *s) } put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ put_le16(p + 93, 1 | (1 << 14) | 0x2000); - put_le16(p + 100, s->nb_sectors); - put_le16(p + 101, s->nb_sectors >> 16); - put_le16(p + 102, s->nb_sectors >> 32); - put_le16(p + 103, s->nb_sectors >> 48); + /* *(p + 100) := nb_sectors -- see ide_identify_size */ + /* *(p + 101) := nb_sectors >> 16 -- see ide_identify_size */ + /* *(p + 102) := nb_sectors >> 32 -- see ide_identify_size */ + /* *(p + 103) := nb_sectors >> 48 -- see ide_identify_size */ if (dev && dev->conf.physical_block_size) put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf)); @@ -179,6 +190,7 @@ static void ide_identify(IDEState *s) put_le16(p + 169, 1); /* TRIM support */ } + ide_identify_size(s); s->identify_set = 1; fill_buffer: @@ -253,6 +265,15 @@ fill_buffer: memcpy(s->io_buffer, p, sizeof(s->identify_data)); } +static void ide_cfata_identify_size(IDEState *s) +{ + uint16_t *p = (uint16_t *)s->identify_data; + put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */ + put_le16(p + 8, s->nb_sectors); /* Sectors per card */ + put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */ + put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */ +} + static void ide_cfata_identify(IDEState *s) { uint16_t *p; @@ -270,8 +291,8 @@ static void ide_cfata_identify(IDEState *s) put_le16(p + 1, s->cylinders); /* Default cylinders */ put_le16(p + 3, s->heads); /* Default heads */ put_le16(p + 6, s->sectors); /* Default sectors per track */ - put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */ - put_le16(p + 8, s->nb_sectors); /* Sectors per card */ + /* *(p + 7) := nb_sectors >> 16 -- see ide_cfata_identify_size */ + /* *(p + 8) := nb_sectors -- see ide_cfata_identify_size */ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */ put_le16(p + 22, 0x0004); /* ECC bytes */ padstr((char *) (p + 23), s->version, 8); /* Firmware Revision */ @@ -292,8 +313,8 @@ static void ide_cfata_identify(IDEState *s) put_le16(p + 58, cur_sec >> 16); /* Current capacity */ if (s->mult_sectors) /* Multiple sector setting */ put_le16(p + 59, 0x100 | s->mult_sectors); - put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */ - put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */ + /* *(p + 60) := nb_sectors -- see ide_cfata_identify_size */ + /* *(p + 61) := nb_sectors >> 16 -- see ide_cfata_identify_size */ put_le16(p + 63, 0x0203); /* Multiword DMA capability */ put_le16(p + 64, 0x0001); /* Flow Control PIO support */ put_le16(p + 65, 0x0096); /* Min. Multiword DMA cycle */ @@ -313,6 +334,7 @@ static void ide_cfata_identify(IDEState *s) put_le16(p + 160, 0x8100); /* Power requirement */ put_le16(p + 161, 0x8001); /* CF command set */ + ide_cfata_identify_size(s); s->identify_set = 1; fill_buffer: @@ -2131,6 +2153,28 @@ static bool ide_cd_is_medium_locked(void *opaque) return ((IDEState *)opaque)->tray_locked; } +static void ide_resize_cb(void *opaque) +{ + IDEState *s = opaque; + uint64_t nb_sectors; + + if (!s->identify_set) { + return; + } + + bdrv_get_geometry(s->bs, &nb_sectors); + s->nb_sectors = nb_sectors; + + /* Update the identify data buffer. */ + if (s->drive_kind == IDE_CFATA) { + ide_cfata_identify_size(s); + } else { + /* IDE_CD uses a different set of callbacks entirely. */ + assert(s->drive_kind != IDE_CD); + ide_identify_size(s); + } +} + static const BlockDevOps ide_cd_block_ops = { .change_media_cb = ide_cd_change_cb, .eject_request_cb = ide_cd_eject_request_cb, @@ -2138,6 +2182,10 @@ static const BlockDevOps ide_cd_block_ops = { .is_medium_locked = ide_cd_is_medium_locked, }; +static const BlockDevOps ide_hd_block_ops = { + .resize_cb = ide_resize_cb, +}; + int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind, const char *version, const char *serial, const char *model, uint64_t wwn, @@ -2174,6 +2222,7 @@ int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind, error_report("Can't use a read-only drive"); return -1; } + bdrv_set_dev_ops(bs, &ide_hd_block_ops, s); } if (serial) { pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), serial);