diff mbox series

tests: qtest: Add virtio-iommu test

Message ID 20211007093438.657198-1-eric.auger@redhat.com (mailing list archive)
State New, archived
Headers show
Series tests: qtest: Add virtio-iommu test | expand

Commit Message

Eric Auger Oct. 7, 2021, 9:34 a.m. UTC
Add the framework to test the virtio-iommu-pci device
and tests exercising the attach/detach, map/unmap API.

Signed-off-by: Eric Auger <eric.auger@redhat.com>

---

This applies on top of jean-Philippe's
[PATCH v4 00/11] virtio-iommu: Add ACPI support
branch can be found at:
https://github.com/eauger/qemu.git
branch qtest-virtio-iommu-v1

To run the tests:
make tests/qtest/qos-test
QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64  tests/qtest/qos-test
---
 tests/qtest/libqos/meson.build    |   1 +
 tests/qtest/libqos/virtio-iommu.c | 177 ++++++++++++++++++
 tests/qtest/libqos/virtio-iommu.h |  45 +++++
 tests/qtest/meson.build           |   1 +
 tests/qtest/virtio-iommu-test.c   | 295 ++++++++++++++++++++++++++++++
 5 files changed, 519 insertions(+)
 create mode 100644 tests/qtest/libqos/virtio-iommu.c
 create mode 100644 tests/qtest/libqos/virtio-iommu.h
 create mode 100644 tests/qtest/virtio-iommu-test.c

Comments

Philippe Mathieu-Daudé Oct. 7, 2021, 10:56 a.m. UTC | #1
On 10/7/21 11:34, Eric Auger wrote:
> Add the framework to test the virtio-iommu-pci device
> and tests exercising the attach/detach, map/unmap API.
> 
> Signed-off-by: Eric Auger <eric.auger@redhat.com>
> 
> ---

> +static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
> +{
> +    QVirtioIOMMU *v_iommu = obj;
> +    QVirtioDevice *dev = v_iommu->vdev;
> +    uint64_t input_range_start = qvirtio_config_readq(dev, 8);
> +    uint64_t input_range_end = qvirtio_config_readq(dev, 16);
> +    uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
> +    uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
> +
> +    g_assert_cmpint(input_range_start, ==, 0);
> +    g_assert_cmphex(input_range_end, ==, 0xFFFFFFFFFFFFFFFF);

Maybe explicit ull suffix for for 32-bit hosts?

> +    g_assert_cmpint(domain_range_start, ==, 0);
> +    g_assert_cmpint(domain_range_end, ==, 32);
> +}
Thomas Huth Oct. 7, 2021, 12:22 p.m. UTC | #2
On 07/10/2021 11.34, Eric Auger wrote:
> Add the framework to test the virtio-iommu-pci device
> and tests exercising the attach/detach, map/unmap API.
> 
> Signed-off-by: Eric Auger <eric.auger@redhat.com>
> 
> ---
> 
> This applies on top of jean-Philippe's
> [PATCH v4 00/11] virtio-iommu: Add ACPI support
> branch can be found at:
> https://github.com/eauger/qemu.git
> branch qtest-virtio-iommu-v1
> 
> To run the tests:
> make tests/qtest/qos-test
> QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64  tests/qtest/qos-test
> ---
>   tests/qtest/libqos/meson.build    |   1 +
>   tests/qtest/libqos/virtio-iommu.c | 177 ++++++++++++++++++
>   tests/qtest/libqos/virtio-iommu.h |  45 +++++
>   tests/qtest/meson.build           |   1 +
>   tests/qtest/virtio-iommu-test.c   | 295 ++++++++++++++++++++++++++++++
>   5 files changed, 519 insertions(+)
>   create mode 100644 tests/qtest/libqos/virtio-iommu.c
>   create mode 100644 tests/qtest/libqos/virtio-iommu.h
>   create mode 100644 tests/qtest/virtio-iommu-test.c
> 
> diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
> index 1f5c8f10539..ba90bbe2b81 100644
> --- a/tests/qtest/libqos/meson.build
> +++ b/tests/qtest/libqos/meson.build
> @@ -40,6 +40,7 @@ libqos_srcs = files('../libqtest.c',
>           'virtio-rng.c',
>           'virtio-scsi.c',
>           'virtio-serial.c',
> +        'virtio-iommu.c',
>   
>           # qgraph machines:
>           'aarch64-xlnx-zcu102-machine.c',
> diff --git a/tests/qtest/libqos/virtio-iommu.c b/tests/qtest/libqos/virtio-iommu.c
> new file mode 100644
> index 00000000000..f0858978e5e
> --- /dev/null
> +++ b/tests/qtest/libqos/virtio-iommu.c
> @@ -0,0 +1,177 @@
> +/*
> + * libqos driver virtio-iommu-pci framework
> + *
> + * Copyright (c) 2021 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.

Hmm, where did you copy that license information from? ... that's the bogus 
version which we already tried to get rid of in the past...

It's either:

- GNU *Library* General Public License version *2.0*

or:

- GNU *Lesser* General Public License version *2.1*

... but there was never a "Lesser" version 2 (in the sense of 2.0), since it 
has been renamed from Library to Lesser with the move from 2.0 to 2.1.

Also, do you really want to limit to version 2/2.1 only? Or should this get 
the "or (at your option) any later version" statement for more flexibility?

Anyway, a SPDX identifier for clarification would certainly help here.

> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/module.h"
> +#include "qgraph.h"
> +#include "virtio-iommu.h"
> +#include "hw/virtio/virtio-iommu.h"
> +
> +static QGuestAllocator *alloc;
> +
> +/* virtio-iommu-device */
> +static void *qvirtio_iommu_get_driver(QVirtioIOMMU *v_iommu,
> +                                      const char *interface)
> +{
> +    if (!g_strcmp0(interface, "virtio-iommu")) {
> +        return v_iommu;
> +    }
> +    if (!g_strcmp0(interface, "virtio")) {
> +        return v_iommu->vdev;
> +    }
> +
> +    fprintf(stderr, "%s not present in virtio-iommu-device\n", interface);
> +    g_assert_not_reached();
> +}
> +
> +static void *qvirtio_iommu_device_get_driver(void *object,
> +                                             const char *interface)
> +{
> +    QVirtioIOMMUDevice *v_iommu = object;
> +    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
> +}
> +
> +static void virtio_iommu_cleanup(QVirtioIOMMU *interface)
> +{
> +    qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc);
> +}
> +
> +static void virtio_iommu_setup(QVirtioIOMMU *interface)
> +{
> +    QVirtioDevice *vdev = interface->vdev;
> +    uint64_t features;
> +
> +    features = qvirtio_get_features(vdev);
> +    features &= ~(QVIRTIO_F_BAD_FEATURE |
> +                  (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
> +                  (1ull << VIRTIO_RING_F_EVENT_IDX) |
> +                  (1ull << VIRTIO_IOMMU_F_BYPASS));
> +    qvirtio_set_features(vdev, features);
> +    interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0);
> +    qvirtio_set_driver_ok(interface->vdev);
> +}
> +
> +static void qvirtio_iommu_device_destructor(QOSGraphObject *obj)
> +{
> +    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
> +    QVirtioIOMMU *iommu = &v_iommu->iommu;
> +
> +    virtio_iommu_cleanup(iommu);
> +}
> +
> +static void qvirtio_iommu_device_start_hw(QOSGraphObject *obj)
> +{
> +    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
> +    QVirtioIOMMU *iommu = &v_iommu->iommu;
> +
> +    virtio_iommu_setup(iommu);
> +}
> +
> +static void *virtio_iommu_device_create(void *virtio_dev,
> +                                        QGuestAllocator *t_alloc,
> +                                        void *addr)
> +{
> +    QVirtioIOMMUDevice *virtio_rdevice = g_new0(QVirtioIOMMUDevice, 1);
> +    QVirtioIOMMU *interface = &virtio_rdevice->iommu;
> +
> +    interface->vdev = virtio_dev;
> +    alloc = t_alloc;
> +
> +    virtio_rdevice->obj.get_driver = qvirtio_iommu_device_get_driver;
> +    virtio_rdevice->obj.destructor = qvirtio_iommu_device_destructor;
> +    virtio_rdevice->obj.start_hw = qvirtio_iommu_device_start_hw;
> +
> +    return &virtio_rdevice->obj;
> +}
> +
> +/* virtio-iommu-pci */
> +static void *qvirtio_iommu_pci_get_driver(void *object, const char *interface)
> +{
> +    QVirtioIOMMUPCI *v_iommu = object;
> +    if (!g_strcmp0(interface, "pci-device")) {
> +        return v_iommu->pci_vdev.pdev;
> +    }
> +    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
> +}
> +
> +static void qvirtio_iommu_pci_destructor(QOSGraphObject *obj)
> +{
> +    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
> +    QVirtioIOMMU *interface = &iommu_pci->iommu;
> +    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
> +
> +    virtio_iommu_cleanup(interface);
> +    qvirtio_pci_destructor(pci_vobj);
> +}
> +
> +static void qvirtio_iommu_pci_start_hw(QOSGraphObject *obj)
> +{
> +    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
> +    QVirtioIOMMU *interface = &iommu_pci->iommu;
> +    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
> +
> +    qvirtio_pci_start_hw(pci_vobj);
> +    virtio_iommu_setup(interface);
> +}
> +
> +
> +static void *virtio_iommu_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
> +                                   void *addr)
> +{
> +    QVirtioIOMMUPCI *virtio_rpci = g_new0(QVirtioIOMMUPCI, 1);
> +    QVirtioIOMMU *interface = &virtio_rpci->iommu;
> +    QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj;
> +
> +    virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr);
> +    interface->vdev = &virtio_rpci->pci_vdev.vdev;
> +    alloc = t_alloc;
> +
> +    obj->get_driver = qvirtio_iommu_pci_get_driver;
> +    obj->start_hw = qvirtio_iommu_pci_start_hw;
> +    obj->destructor = qvirtio_iommu_pci_destructor;
> +
> +    return obj;
> +}
> +
> +static void virtio_iommu_register_nodes(void)
> +{
> +    QPCIAddress addr = {
> +        .devfn = QPCI_DEVFN(4, 0),
> +    };
> +
> +    QOSGraphEdgeOptions opts = {
> +        .extra_device_opts = "addr=04.0",
> +    };
> +
> +    /* virtio-iommu-device */
> +    qos_node_create_driver("virtio-iommu-device", virtio_iommu_device_create);
> +    qos_node_consumes("virtio-iommu-device", "virtio-bus", NULL);
> +    qos_node_produces("virtio-iommu-device", "virtio");
> +    qos_node_produces("virtio-iommu-device", "virtio-iommu");
> +
> +    /* virtio-iommu-pci */
> +    add_qpci_address(&opts, &addr);
> +    qos_node_create_driver("virtio-iommu-pci", virtio_iommu_pci_create);
> +    qos_node_consumes("virtio-iommu-pci", "pci-bus", &opts);
> +    qos_node_produces("virtio-iommu-pci", "pci-device");
> +    qos_node_produces("virtio-iommu-pci", "virtio");
> +    qos_node_produces("virtio-iommu-pci", "virtio-iommu");
> +}
> +
> +libqos_init(virtio_iommu_register_nodes);
> diff --git a/tests/qtest/libqos/virtio-iommu.h b/tests/qtest/libqos/virtio-iommu.h
> new file mode 100644
> index 00000000000..047e71cbe16
> --- /dev/null
> +++ b/tests/qtest/libqos/virtio-iommu.h
> @@ -0,0 +1,45 @@
> +/*
> + * libqos driver virtio-iommu-pci framework
> + *
> + * Copyright (c) 2021 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License version 2 as published by the Free Software Foundation.

Please also fix the license statement here.

> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#ifndef TESTS_LIBQOS_VIRTIO_IOMMU_H
> +#define TESTS_LIBQOS_VIRTIO_IOMMU_H
> +
> +#include "qgraph.h"
> +#include "virtio.h"
> +#include "virtio-pci.h"
> +
> +typedef struct QVirtioIOMMU QVirtioIOMMU;
> +typedef struct QVirtioIOMMUPCI QVirtioIOMMUPCI;
> +typedef struct QVirtioIOMMUDevice QVirtioIOMMUDevice;
> +
> +struct QVirtioIOMMU {
> +    QVirtioDevice *vdev;
> +    QVirtQueue *vq;
> +};
> +
> +struct QVirtioIOMMUPCI {
> +    QVirtioPCIDevice pci_vdev;
> +    QVirtioIOMMU iommu;
> +};
> +
> +struct QVirtioIOMMUDevice {
> +    QOSGraphObject obj;
> +    QVirtioIOMMU iommu;
> +};
> +
> +#endif
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index c9d8458062f..982ffb3e38d 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -230,6 +230,7 @@ qos_test_ss.add(
>     'virtio-rng-test.c',
>     'virtio-scsi-test.c',
>     'virtio-serial-test.c',
> +  'virtio-iommu-test.c',
>     'vmxnet3-test.c',
>   )
>   if have_virtfs
> diff --git a/tests/qtest/virtio-iommu-test.c b/tests/qtest/virtio-iommu-test.c
> new file mode 100644
> index 00000000000..d257d17d7d3
> --- /dev/null
> +++ b/tests/qtest/virtio-iommu-test.c
> @@ -0,0 +1,295 @@
> +/*
> + * QTest testcase for VirtIO IOMMU
> + *
> + * Copyright (c) 2020 Red Hat, Inc.

2021, maybe?

> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest-single.h"
> +#include "qemu/module.h"
> +#include "libqos/qgraph.h"
> +#include "libqos/virtio-iommu.h"
> +#include "hw/virtio/virtio-iommu.h"
> +
> +#define PCI_SLOT_HP             0x06
> +#define QVIRTIO_IOMMU_TIMEOUT_US (30 * 1000 * 1000)
> +
> +static QGuestAllocator *alloc;
> +
> +static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
> +{
> +    QVirtioIOMMU *v_iommu = obj;
> +    QVirtioDevice *dev = v_iommu->vdev;
> +    uint64_t input_range_start = qvirtio_config_readq(dev, 8);
> +    uint64_t input_range_end = qvirtio_config_readq(dev, 16);
> +    uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
> +    uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
> +
> +    g_assert_cmpint(input_range_start, ==, 0);
> +    g_assert_cmphex(input_range_end, ==, 0xFFFFFFFFFFFFFFFF);

I agree with Philippe, this needs an ULL suffix.

> +    g_assert_cmpint(domain_range_start, ==, 0);
> +    g_assert_cmpint(domain_range_end, ==, 32);
> +}
> +
> +/**
> + * send_attach_detach - Send an attach/detach command to the device
> + * @type: VIRTIO_IOMMU_T_ATTACH/VIRTIO_IOMMU_T_DETACH
> + * @domain: domain the end point is attached to
> + * @ep: end-point
> + */
> +static int send_attach_detach(QTestState *qts, QVirtioIOMMU *v_iommu,
> +                              uint8_t type, uint32_t domain, uint32_t ep)
> +{
> +    QVirtioDevice *dev = v_iommu->vdev;
> +    QVirtQueue *vq = v_iommu->vq;
> +    uint64_t ro_addr, wr_addr;
> +    uint32_t free_head;
> +    struct virtio_iommu_req_attach req; /* same layout as detach */
> +    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
> +    char buffer[64];

Why not simply using a "struct virtio_iommu_req_tail buffer" here?

> +    int ret;
> +
> +    req.head.type = type;
> +    req.domain = domain;
> +    req.endpoint = ep;
> +
> +    ro_addr = guest_alloc(alloc, ro_size);
> +    wr_addr = guest_alloc(alloc, wr_size);
> +
> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
> +    qvirtqueue_kick(qts, dev, vq, free_head);
> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
> +                           QVIRTIO_IOMMU_TIMEOUT_US);
> +    qtest_memread(qts, wr_addr, buffer, wr_size);
> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
> +    guest_free(alloc, ro_addr);
> +    guest_free(alloc, wr_addr);
> +    return ret;
> +}
> +
> +/**
> + * send_map - Send a map command to the device
> + * @domain: domain the new binding is attached to
> + * @virt_start: iova start
> + * @virt_end: iova end
> + * @phys_start: base physical address
> + * @flags: mapping flags
> + */
> +static int send_map(QTestState *qts, QVirtioIOMMU *v_iommu,
> +                    uint32_t domain, uint64_t virt_start, uint64_t virt_end,
> +                    uint64_t phys_start, uint32_t flags)
> +{
> +    QVirtioDevice *dev = v_iommu->vdev;
> +    QVirtQueue *vq = v_iommu->vq;
> +    uint64_t ro_addr, wr_addr;
> +    uint32_t free_head;
> +    struct virtio_iommu_req_map req;
> +    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
> +    char buffer[64];

dito?

> +    int ret;
> +
> +    req.head.type = VIRTIO_IOMMU_T_MAP;
> +    req.domain = domain;
> +    req.virt_start = virt_start;
> +    req.virt_end = virt_end;
> +    req.phys_start = phys_start;
> +    req.flags = flags;
> +
> +    ro_addr = guest_alloc(alloc, ro_size);
> +    wr_addr = guest_alloc(alloc, wr_size);
> +
> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
> +    qvirtqueue_kick(qts, dev, vq, free_head);
> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
> +                           QVIRTIO_IOMMU_TIMEOUT_US);
> +    memread(wr_addr, buffer, wr_size);

qtest_memread(), please.

> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
> +    guest_free(alloc, ro_addr);
> +    guest_free(alloc, wr_addr);
> +    return ret;
> +}
> +
> +/**
> + * send_unmap - Send an unmap command to the device
> + * @domain: domain the new binding is attached to
> + * @virt_start: iova start
> + * @virt_end: iova end
> + */
> +static int send_unmap(QTestState *qts, QVirtioIOMMU *v_iommu,
> +                      uint32_t domain, uint64_t virt_start, uint64_t virt_end)
> +{
> +    QVirtioDevice *dev = v_iommu->vdev;
> +    QVirtQueue *vq = v_iommu->vq;
> +    uint64_t ro_addr, wr_addr;
> +    uint32_t free_head;
> +    struct virtio_iommu_req_unmap req;
> +    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
> +    char buffer[64];

struct virtio_iommu_req_tail buffer ?

> +    int ret;
> +
> +    req.head.type = VIRTIO_IOMMU_T_UNMAP;
> +    req.domain = domain;
> +    req.virt_start = virt_start;
> +    req.virt_end = virt_end;
> +
> +    ro_addr = guest_alloc(alloc, ro_size);
> +    wr_addr = guest_alloc(alloc, wr_size);
> +
> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
> +    qvirtqueue_kick(qts, dev, vq, free_head);
> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
> +                           QVIRTIO_IOMMU_TIMEOUT_US);
> +    memread(wr_addr, buffer, wr_size);

qtest_memread(), please.

> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
> +    guest_free(alloc, ro_addr);
> +    guest_free(alloc, wr_addr);
> +    return ret;
> +}
> +
> +/* Test unmap scenari documented in the spec v0.12 */
> +static void test_attach_detach(void *obj, void *data, QGuestAllocator *t_alloc)
> +{
> +    QVirtioIOMMU *v_iommu = obj;
> +    QTestState *qts = global_qtest;
> +    int ret;
> +
> +    alloc = t_alloc;
> +
> +    /* type, domain, ep */
> +
> +    /* attach ep0 to domain 0 */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    /* attach a non existing device (1) */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 444);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
> +
> +    /* detach a non existing device (1) */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 1);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
> +
> +    /* move ep0 from domain 0 to domain 1 */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    /* detach ep0 to domain 0 */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 0);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
> +
> +    /* detach ep0 from domain 1 */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +    ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000,
> +                   VIRTIO_IOMMU_MAP_F_READ);
> +    g_assert_cmpint(ret, ==, 0);
> +    ret = send_map(qts, v_iommu, 1, 0x2000, 0x2FFF, 0xb1000,
> +                   VIRTIO_IOMMU_MAP_F_READ);
> +    g_assert_cmpint(ret, ==, 0);
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +}
> +
> +static void test_map_unmap(void *obj, void *data, QGuestAllocator *t_alloc)
> +{
> +    QVirtioIOMMU *v_iommu = obj;
> +    QTestState *qts = global_qtest;
> +    int ret;
> +
> +    alloc = t_alloc;
> +
> +    /* attach ep0 to domain 1 */
> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    ret = send_map(qts, v_iommu, 0, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
> +
> +    /* domain, virt start, virt end, phys start, flags */
> +    ret = send_map(qts, v_iommu, 1, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    ret = send_unmap(qts, v_iommu, 4, 0x10, 0xFFF);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
> +
> +    ret = send_unmap(qts, v_iommu, 1, 0x10, 0xFFF);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE);
> +
> +    ret = send_unmap(qts, v_iommu, 1, 0, 0x1000);
> +    g_assert_cmpint(ret, ==, 0); /* unmap everything */
> +
> +    /* Spec example sequence */
> +
> +    /* 1 */
> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
> +    g_assert_cmpint(ret, ==, 0); /* doesn't unmap anything */
> +
> +    /* 2 */
> +    send_map(qts, v_iommu, 1, 0, 9, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,9] */
> +
> +    /* 3 */
> +    send_map(qts, v_iommu, 1, 0, 4, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
> +    send_map(qts, v_iommu, 1, 5, 9, 0xb2000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [5,9] */
> +
> +    /* 4 */
> +    send_map(qts, v_iommu, 1, 0, 9, 0xc1000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE); /* doesn't unmap anything */
> +
> +    ret = send_unmap(qts, v_iommu, 1, 0, 10);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    /* 5 */
> +    send_map(qts, v_iommu, 1, 0, 4, 0xd1000, VIRTIO_IOMMU_MAP_F_READ);
> +    send_map(qts, v_iommu, 1, 5, 9, 0xd2000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
> +
> +    ret = send_unmap(qts, v_iommu, 1, 5, 9);
> +    g_assert_cmpint(ret, ==, 0);
> +
> +    /* 6 */
> +    send_map(qts, v_iommu, 1, 0, 4, 0xe2000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
> +
> +    /* 7 */
> +    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
> +    send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 14);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
> +
> +    send_unmap(qts, v_iommu, 1, 0, 100);
> +    send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
> +    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
> +}
> +
> +static void register_virtio_iommu_test(void)
> +{
> +    qos_add_test("config", "virtio-iommu", pci_config, NULL);
> +    qos_add_test("attach_detach", "virtio-iommu", test_attach_detach, NULL);
> +    qos_add_test("map_unmap", "virtio-iommu", test_map_unmap, NULL);
> +}
> +
> +libqos_init(register_virtio_iommu_test);
> 

  Thomas
Eric Auger Oct. 7, 2021, 12:43 p.m. UTC | #3
Hi,

On 10/7/21 2:22 PM, Thomas Huth wrote:
> On 07/10/2021 11.34, Eric Auger wrote:
>> Add the framework to test the virtio-iommu-pci device
>> and tests exercising the attach/detach, map/unmap API.
>>
>> Signed-off-by: Eric Auger <eric.auger@redhat.com>
>>
>> ---
>>
>> This applies on top of jean-Philippe's
>> [PATCH v4 00/11] virtio-iommu: Add ACPI support
>> branch can be found at:
>> https://github.com/eauger/qemu.git
>> branch qtest-virtio-iommu-v1
>>
>> To run the tests:
>> make tests/qtest/qos-test
>> QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon
>> QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64 
>> tests/qtest/qos-test
>> ---
>>   tests/qtest/libqos/meson.build    |   1 +
>>   tests/qtest/libqos/virtio-iommu.c | 177 ++++++++++++++++++
>>   tests/qtest/libqos/virtio-iommu.h |  45 +++++
>>   tests/qtest/meson.build           |   1 +
>>   tests/qtest/virtio-iommu-test.c   | 295 ++++++++++++++++++++++++++++++
>>   5 files changed, 519 insertions(+)
>>   create mode 100644 tests/qtest/libqos/virtio-iommu.c
>>   create mode 100644 tests/qtest/libqos/virtio-iommu.h
>>   create mode 100644 tests/qtest/virtio-iommu-test.c
>>
>> diff --git a/tests/qtest/libqos/meson.build
>> b/tests/qtest/libqos/meson.build
>> index 1f5c8f10539..ba90bbe2b81 100644
>> --- a/tests/qtest/libqos/meson.build
>> +++ b/tests/qtest/libqos/meson.build
>> @@ -40,6 +40,7 @@ libqos_srcs = files('../libqtest.c',
>>           'virtio-rng.c',
>>           'virtio-scsi.c',
>>           'virtio-serial.c',
>> +        'virtio-iommu.c',
>>             # qgraph machines:
>>           'aarch64-xlnx-zcu102-machine.c',
>> diff --git a/tests/qtest/libqos/virtio-iommu.c
>> b/tests/qtest/libqos/virtio-iommu.c
>> new file mode 100644
>> index 00000000000..f0858978e5e
>> --- /dev/null
>> +++ b/tests/qtest/libqos/virtio-iommu.c
>> @@ -0,0 +1,177 @@
>> +/*
>> + * libqos driver virtio-iommu-pci framework
>> + *
>> + * Copyright (c) 2021 Red Hat, Inc.
>> + *
>> + * This library is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License version 2 as published by the Free Software Foundation.
>
> Hmm, where did you copy that license information from? ... that's the
> bogus version which we already tried to get rid of in the past...
>
> It's either:
>
> - GNU *Library* General Public License version *2.0*
>
> or:
>
> - GNU *Lesser* General Public License version *2.1*
>
> ... but there was never a "Lesser" version 2 (in the sense of 2.0),
> since it has been renamed from Library to Lesser with the move from
> 2.0 to 2.1.
>
> Also, do you really want to limit to version 2/2.1 only? Or should
> this get the "or (at your option) any later version" statement for
> more flexibility?
>
> Anyway, a SPDX identifier for clarification would certainly help here.

sorry this code was written in 2020 when I developped the virtio-iommu
device. I will fix the license.
>
>> + * This library is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with this library; if not, see
>> <http://www.gnu.org/licenses/>
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "libqtest.h"
>> +#include "qemu/module.h"
>> +#include "qgraph.h"
>> +#include "virtio-iommu.h"
>> +#include "hw/virtio/virtio-iommu.h"
>> +
>> +static QGuestAllocator *alloc;
>> +
>> +/* virtio-iommu-device */
>> +static void *qvirtio_iommu_get_driver(QVirtioIOMMU *v_iommu,
>> +                                      const char *interface)
>> +{
>> +    if (!g_strcmp0(interface, "virtio-iommu")) {
>> +        return v_iommu;
>> +    }
>> +    if (!g_strcmp0(interface, "virtio")) {
>> +        return v_iommu->vdev;
>> +    }
>> +
>> +    fprintf(stderr, "%s not present in virtio-iommu-device\n",
>> interface);
>> +    g_assert_not_reached();
>> +}
>> +
>> +static void *qvirtio_iommu_device_get_driver(void *object,
>> +                                             const char *interface)
>> +{
>> +    QVirtioIOMMUDevice *v_iommu = object;
>> +    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
>> +}
>> +
>> +static void virtio_iommu_cleanup(QVirtioIOMMU *interface)
>> +{
>> +    qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc);
>> +}
>> +
>> +static void virtio_iommu_setup(QVirtioIOMMU *interface)
>> +{
>> +    QVirtioDevice *vdev = interface->vdev;
>> +    uint64_t features;
>> +
>> +    features = qvirtio_get_features(vdev);
>> +    features &= ~(QVIRTIO_F_BAD_FEATURE |
>> +                  (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
>> +                  (1ull << VIRTIO_RING_F_EVENT_IDX) |
>> +                  (1ull << VIRTIO_IOMMU_F_BYPASS));
>> +    qvirtio_set_features(vdev, features);
>> +    interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0);
>> +    qvirtio_set_driver_ok(interface->vdev);
>> +}
>> +
>> +static void qvirtio_iommu_device_destructor(QOSGraphObject *obj)
>> +{
>> +    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
>> +    QVirtioIOMMU *iommu = &v_iommu->iommu;
>> +
>> +    virtio_iommu_cleanup(iommu);
>> +}
>> +
>> +static void qvirtio_iommu_device_start_hw(QOSGraphObject *obj)
>> +{
>> +    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
>> +    QVirtioIOMMU *iommu = &v_iommu->iommu;
>> +
>> +    virtio_iommu_setup(iommu);
>> +}
>> +
>> +static void *virtio_iommu_device_create(void *virtio_dev,
>> +                                        QGuestAllocator *t_alloc,
>> +                                        void *addr)
>> +{
>> +    QVirtioIOMMUDevice *virtio_rdevice = g_new0(QVirtioIOMMUDevice, 1);
>> +    QVirtioIOMMU *interface = &virtio_rdevice->iommu;
>> +
>> +    interface->vdev = virtio_dev;
>> +    alloc = t_alloc;
>> +
>> +    virtio_rdevice->obj.get_driver = qvirtio_iommu_device_get_driver;
>> +    virtio_rdevice->obj.destructor = qvirtio_iommu_device_destructor;
>> +    virtio_rdevice->obj.start_hw = qvirtio_iommu_device_start_hw;
>> +
>> +    return &virtio_rdevice->obj;
>> +}
>> +
>> +/* virtio-iommu-pci */
>> +static void *qvirtio_iommu_pci_get_driver(void *object, const char
>> *interface)
>> +{
>> +    QVirtioIOMMUPCI *v_iommu = object;
>> +    if (!g_strcmp0(interface, "pci-device")) {
>> +        return v_iommu->pci_vdev.pdev;
>> +    }
>> +    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
>> +}
>> +
>> +static void qvirtio_iommu_pci_destructor(QOSGraphObject *obj)
>> +{
>> +    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
>> +    QVirtioIOMMU *interface = &iommu_pci->iommu;
>> +    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
>> +
>> +    virtio_iommu_cleanup(interface);
>> +    qvirtio_pci_destructor(pci_vobj);
>> +}
>> +
>> +static void qvirtio_iommu_pci_start_hw(QOSGraphObject *obj)
>> +{
>> +    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
>> +    QVirtioIOMMU *interface = &iommu_pci->iommu;
>> +    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
>> +
>> +    qvirtio_pci_start_hw(pci_vobj);
>> +    virtio_iommu_setup(interface);
>> +}
>> +
>> +
>> +static void *virtio_iommu_pci_create(void *pci_bus, QGuestAllocator
>> *t_alloc,
>> +                                   void *addr)
>> +{
>> +    QVirtioIOMMUPCI *virtio_rpci = g_new0(QVirtioIOMMUPCI, 1);
>> +    QVirtioIOMMU *interface = &virtio_rpci->iommu;
>> +    QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj;
>> +
>> +    virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr);
>> +    interface->vdev = &virtio_rpci->pci_vdev.vdev;
>> +    alloc = t_alloc;
>> +
>> +    obj->get_driver = qvirtio_iommu_pci_get_driver;
>> +    obj->start_hw = qvirtio_iommu_pci_start_hw;
>> +    obj->destructor = qvirtio_iommu_pci_destructor;
>> +
>> +    return obj;
>> +}
>> +
>> +static void virtio_iommu_register_nodes(void)
>> +{
>> +    QPCIAddress addr = {
>> +        .devfn = QPCI_DEVFN(4, 0),
>> +    };
>> +
>> +    QOSGraphEdgeOptions opts = {
>> +        .extra_device_opts = "addr=04.0",
>> +    };
>> +
>> +    /* virtio-iommu-device */
>> +    qos_node_create_driver("virtio-iommu-device",
>> virtio_iommu_device_create);
>> +    qos_node_consumes("virtio-iommu-device", "virtio-bus", NULL);
>> +    qos_node_produces("virtio-iommu-device", "virtio");
>> +    qos_node_produces("virtio-iommu-device", "virtio-iommu");
>> +
>> +    /* virtio-iommu-pci */
>> +    add_qpci_address(&opts, &addr);
>> +    qos_node_create_driver("virtio-iommu-pci",
>> virtio_iommu_pci_create);
>> +    qos_node_consumes("virtio-iommu-pci", "pci-bus", &opts);
>> +    qos_node_produces("virtio-iommu-pci", "pci-device");
>> +    qos_node_produces("virtio-iommu-pci", "virtio");
>> +    qos_node_produces("virtio-iommu-pci", "virtio-iommu");
>> +}
>> +
>> +libqos_init(virtio_iommu_register_nodes);
>> diff --git a/tests/qtest/libqos/virtio-iommu.h
>> b/tests/qtest/libqos/virtio-iommu.h
>> new file mode 100644
>> index 00000000000..047e71cbe16
>> --- /dev/null
>> +++ b/tests/qtest/libqos/virtio-iommu.h
>> @@ -0,0 +1,45 @@
>> +/*
>> + * libqos driver virtio-iommu-pci framework
>> + *
>> + * Copyright (c) 2021 Red Hat, Inc.
>> + *
>> + * This library is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License version 2 as published by the Free Software Foundation.
>
> Please also fix the license statement here.
sure
>
>> + * This library is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with this library; if not, see
>> <http://www.gnu.org/licenses/>
>> + */
>> +
>> +#ifndef TESTS_LIBQOS_VIRTIO_IOMMU_H
>> +#define TESTS_LIBQOS_VIRTIO_IOMMU_H
>> +
>> +#include "qgraph.h"
>> +#include "virtio.h"
>> +#include "virtio-pci.h"
>> +
>> +typedef struct QVirtioIOMMU QVirtioIOMMU;
>> +typedef struct QVirtioIOMMUPCI QVirtioIOMMUPCI;
>> +typedef struct QVirtioIOMMUDevice QVirtioIOMMUDevice;
>> +
>> +struct QVirtioIOMMU {
>> +    QVirtioDevice *vdev;
>> +    QVirtQueue *vq;
>> +};
>> +
>> +struct QVirtioIOMMUPCI {
>> +    QVirtioPCIDevice pci_vdev;
>> +    QVirtioIOMMU iommu;
>> +};
>> +
>> +struct QVirtioIOMMUDevice {
>> +    QOSGraphObject obj;
>> +    QVirtioIOMMU iommu;
>> +};
>> +
>> +#endif
>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index c9d8458062f..982ffb3e38d 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -230,6 +230,7 @@ qos_test_ss.add(
>>     'virtio-rng-test.c',
>>     'virtio-scsi-test.c',
>>     'virtio-serial-test.c',
>> +  'virtio-iommu-test.c',
>>     'vmxnet3-test.c',
>>   )
>>   if have_virtfs
>> diff --git a/tests/qtest/virtio-iommu-test.c
>> b/tests/qtest/virtio-iommu-test.c
>> new file mode 100644
>> index 00000000000..d257d17d7d3
>> --- /dev/null
>> +++ b/tests/qtest/virtio-iommu-test.c
>> @@ -0,0 +1,295 @@
>> +/*
>> + * QTest testcase for VirtIO IOMMU
>> + *
>> + * Copyright (c) 2020 Red Hat, Inc.
>
> 2021, maybe?
sure
>
>> + * This work is licensed under the terms of the GNU GPL, version 2
>> or later.
>> + * See the COPYING file in the top-level directory.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "libqtest-single.h"
>> +#include "qemu/module.h"
>> +#include "libqos/qgraph.h"
>> +#include "libqos/virtio-iommu.h"
>> +#include "hw/virtio/virtio-iommu.h"
>> +
>> +#define PCI_SLOT_HP             0x06
>> +#define QVIRTIO_IOMMU_TIMEOUT_US (30 * 1000 * 1000)
>> +
>> +static QGuestAllocator *alloc;
>> +
>> +static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
>> +{
>> +    QVirtioIOMMU *v_iommu = obj;
>> +    QVirtioDevice *dev = v_iommu->vdev;
>> +    uint64_t input_range_start = qvirtio_config_readq(dev, 8);
>> +    uint64_t input_range_end = qvirtio_config_readq(dev, 16);
>> +    uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
>> +    uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
>> +
>> +    g_assert_cmpint(input_range_start, ==, 0);
>> +    g_assert_cmphex(input_range_end, ==, 0xFFFFFFFFFFFFFFFF);
>
> I agree with Philippe, this needs an ULL suffix.
OK
>
>> +    g_assert_cmpint(domain_range_start, ==, 0);
>> +    g_assert_cmpint(domain_range_end, ==, 32);
>> +}
>> +
>> +/**
>> + * send_attach_detach - Send an attach/detach command to the device
>> + * @type: VIRTIO_IOMMU_T_ATTACH/VIRTIO_IOMMU_T_DETACH
>> + * @domain: domain the end point is attached to
>> + * @ep: end-point
>> + */
>> +static int send_attach_detach(QTestState *qts, QVirtioIOMMU *v_iommu,
>> +                              uint8_t type, uint32_t domain,
>> uint32_t ep)
>> +{
>> +    QVirtioDevice *dev = v_iommu->vdev;
>> +    QVirtQueue *vq = v_iommu->vq;
>> +    uint64_t ro_addr, wr_addr;
>> +    uint32_t free_head;
>> +    struct virtio_iommu_req_attach req; /* same layout as detach */
>> +    size_t ro_size = sizeof(req) - sizeof(struct
>> virtio_iommu_req_tail);
>> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
>> +    char buffer[64];
>
> Why not simply using a "struct virtio_iommu_req_tail buffer" here?
yep
>
>> +    int ret;
>> +
>> +    req.head.type = type;
>> +    req.domain = domain;
>> +    req.endpoint = ep;
>> +
>> +    ro_addr = guest_alloc(alloc, ro_size);
>> +    wr_addr = guest_alloc(alloc, wr_size);
>> +
>> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
>> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
>> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
>> +    qvirtqueue_kick(qts, dev, vq, free_head);
>> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
>> +                           QVIRTIO_IOMMU_TIMEOUT_US);
>> +    qtest_memread(qts, wr_addr, buffer, wr_size);
>> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
>> +    guest_free(alloc, ro_addr);
>> +    guest_free(alloc, wr_addr);
>> +    return ret;
>> +}
>> +
>> +/**
>> + * send_map - Send a map command to the device
>> + * @domain: domain the new binding is attached to
>> + * @virt_start: iova start
>> + * @virt_end: iova end
>> + * @phys_start: base physical address
>> + * @flags: mapping flags
>> + */
>> +static int send_map(QTestState *qts, QVirtioIOMMU *v_iommu,
>> +                    uint32_t domain, uint64_t virt_start, uint64_t
>> virt_end,
>> +                    uint64_t phys_start, uint32_t flags)
>> +{
>> +    QVirtioDevice *dev = v_iommu->vdev;
>> +    QVirtQueue *vq = v_iommu->vq;
>> +    uint64_t ro_addr, wr_addr;
>> +    uint32_t free_head;
>> +    struct virtio_iommu_req_map req;
>> +    size_t ro_size = sizeof(req) - sizeof(struct
>> virtio_iommu_req_tail);
>> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
>> +    char buffer[64];
>
> dito?
ok
>
>> +    int ret;
>> +
>> +    req.head.type = VIRTIO_IOMMU_T_MAP;
>> +    req.domain = domain;
>> +    req.virt_start = virt_start;
>> +    req.virt_end = virt_end;
>> +    req.phys_start = phys_start;
>> +    req.flags = flags;
>> +
>> +    ro_addr = guest_alloc(alloc, ro_size);
>> +    wr_addr = guest_alloc(alloc, wr_size);
>> +
>> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
>> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
>> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
>> +    qvirtqueue_kick(qts, dev, vq, free_head);
>> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
>> +                           QVIRTIO_IOMMU_TIMEOUT_US);
>> +    memread(wr_addr, buffer, wr_size);
>
> qtest_memread(), please.
ok
>
>> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
>> +    guest_free(alloc, ro_addr);
>> +    guest_free(alloc, wr_addr);
>> +    return ret;
>> +}
>> +
>> +/**
>> + * send_unmap - Send an unmap command to the device
>> + * @domain: domain the new binding is attached to
>> + * @virt_start: iova start
>> + * @virt_end: iova end
>> + */
>> +static int send_unmap(QTestState *qts, QVirtioIOMMU *v_iommu,
>> +                      uint32_t domain, uint64_t virt_start, uint64_t
>> virt_end)
>> +{
>> +    QVirtioDevice *dev = v_iommu->vdev;
>> +    QVirtQueue *vq = v_iommu->vq;
>> +    uint64_t ro_addr, wr_addr;
>> +    uint32_t free_head;
>> +    struct virtio_iommu_req_unmap req;
>> +    size_t ro_size = sizeof(req) - sizeof(struct
>> virtio_iommu_req_tail);
>> +    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
>> +    char buffer[64];
>
> struct virtio_iommu_req_tail buffer ?
>
>> +    int ret;
>> +
>> +    req.head.type = VIRTIO_IOMMU_T_UNMAP;
>> +    req.domain = domain;
>> +    req.virt_start = virt_start;
>> +    req.virt_end = virt_end;
>> +
>> +    ro_addr = guest_alloc(alloc, ro_size);
>> +    wr_addr = guest_alloc(alloc, wr_size);
>> +
>> +    qtest_memwrite(qts, ro_addr, &req, ro_size);
>> +    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
>> +    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
>> +    qvirtqueue_kick(qts, dev, vq, free_head);
>> +    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
>> +                           QVIRTIO_IOMMU_TIMEOUT_US);
>> +    memread(wr_addr, buffer, wr_size);
>
> qtest_memread(), please.
>
>> +    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
>> +    guest_free(alloc, ro_addr);
>> +    guest_free(alloc, wr_addr);
>> +    return ret;
>> +}
>> +
>> +/* Test unmap scenari documented in the spec v0.12 */
>> +static void test_attach_detach(void *obj, void *data,
>> QGuestAllocator *t_alloc)
>> +{
>> +    QVirtioIOMMU *v_iommu = obj;
>> +    QTestState *qts = global_qtest;
>> +    int ret;
>> +
>> +    alloc = t_alloc;
>> +
>> +    /* type, domain, ep */
>> +
>> +    /* attach ep0 to domain 0 */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    /* attach a non existing device (1) */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0,
>> 444);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
>> +
>> +    /* detach a non existing device (1) */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0,
>> 1);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
>> +
>> +    /* move ep0 from domain 0 to domain 1 */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    /* detach ep0 to domain 0 */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0,
>> 0);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
>> +
>> +    /* detach ep0 from domain 1 */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +    ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000,
>> +                   VIRTIO_IOMMU_MAP_F_READ);
>> +    g_assert_cmpint(ret, ==, 0);
>> +    ret = send_map(qts, v_iommu, 1, 0x2000, 0x2FFF, 0xb1000,
>> +                   VIRTIO_IOMMU_MAP_F_READ);
>> +    g_assert_cmpint(ret, ==, 0);
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +}
>> +
>> +static void test_map_unmap(void *obj, void *data, QGuestAllocator
>> *t_alloc)
>> +{
>> +    QVirtioIOMMU *v_iommu = obj;
>> +    QTestState *qts = global_qtest;
>> +    int ret;
>> +
>> +    alloc = t_alloc;
>> +
>> +    /* attach ep0 to domain 1 */
>> +    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1,
>> 0);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    ret = send_map(qts, v_iommu, 0, 0, 0xFFF, 0xa1000,
>> VIRTIO_IOMMU_MAP_F_READ);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
>> +
>> +    /* domain, virt start, virt end, phys start, flags */
>> +    ret = send_map(qts, v_iommu, 1, 0, 0xFFF, 0xa1000,
>> VIRTIO_IOMMU_MAP_F_READ);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    ret = send_unmap(qts, v_iommu, 4, 0x10, 0xFFF);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
>> +
>> +    ret = send_unmap(qts, v_iommu, 1, 0x10, 0xFFF);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE);
>> +
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 0x1000);
>> +    g_assert_cmpint(ret, ==, 0); /* unmap everything */
>> +
>> +    /* Spec example sequence */
>> +
>> +    /* 1 */
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
>> +    g_assert_cmpint(ret, ==, 0); /* doesn't unmap anything */
>> +
>> +    /* 2 */
>> +    send_map(qts, v_iommu, 1, 0, 9, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,9] */
>> +
>> +    /* 3 */
>> +    send_map(qts, v_iommu, 1, 0, 4, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
>> +    send_map(qts, v_iommu, 1, 5, 9, 0xb2000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [5,9] */
>> +
>> +    /* 4 */
>> +    send_map(qts, v_iommu, 1, 0, 9, 0xc1000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
>> +    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE); /* doesn't unmap
>> anything */
>> +
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 10);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    /* 5 */
>> +    send_map(qts, v_iommu, 1, 0, 4, 0xd1000, VIRTIO_IOMMU_MAP_F_READ);
>> +    send_map(qts, v_iommu, 1, 5, 9, 0xd2000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
>> +
>> +    ret = send_unmap(qts, v_iommu, 1, 5, 9);
>> +    g_assert_cmpint(ret, ==, 0);
>> +
>> +    /* 6 */
>> +    send_map(qts, v_iommu, 1, 0, 4, 0xe2000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 9);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
>> +
>> +    /* 7 */
>> +    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
>> +    send_map(qts, v_iommu, 1, 10, 14, 0xf3000,
>> VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 14);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
>> +
>> +    send_unmap(qts, v_iommu, 1, 0, 100);
>> +    send_map(qts, v_iommu, 1, 10, 14, 0xf3000,
>> VIRTIO_IOMMU_MAP_F_READ);
>> +    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
>> +    ret = send_unmap(qts, v_iommu, 1, 0, 4);
>> +    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
>> +}
>> +
>> +static void register_virtio_iommu_test(void)
>> +{
>> +    qos_add_test("config", "virtio-iommu", pci_config, NULL);
>> +    qos_add_test("attach_detach", "virtio-iommu",
>> test_attach_detach, NULL);
>> +    qos_add_test("map_unmap", "virtio-iommu", test_map_unmap, NULL);
>> +}
>> +
>> +libqos_init(register_virtio_iommu_test);
>>
>
>  Thomas
>
Thanks!

Eric
diff mbox series

Patch

diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index 1f5c8f10539..ba90bbe2b81 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -40,6 +40,7 @@  libqos_srcs = files('../libqtest.c',
         'virtio-rng.c',
         'virtio-scsi.c',
         'virtio-serial.c',
+        'virtio-iommu.c',
 
         # qgraph machines:
         'aarch64-xlnx-zcu102-machine.c',
diff --git a/tests/qtest/libqos/virtio-iommu.c b/tests/qtest/libqos/virtio-iommu.c
new file mode 100644
index 00000000000..f0858978e5e
--- /dev/null
+++ b/tests/qtest/libqos/virtio-iommu.c
@@ -0,0 +1,177 @@ 
+/*
+ * libqos driver virtio-iommu-pci framework
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "qgraph.h"
+#include "virtio-iommu.h"
+#include "hw/virtio/virtio-iommu.h"
+
+static QGuestAllocator *alloc;
+
+/* virtio-iommu-device */
+static void *qvirtio_iommu_get_driver(QVirtioIOMMU *v_iommu,
+                                      const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-iommu")) {
+        return v_iommu;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_iommu->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-iommu-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_iommu_device_get_driver(void *object,
+                                             const char *interface)
+{
+    QVirtioIOMMUDevice *v_iommu = object;
+    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
+}
+
+static void virtio_iommu_cleanup(QVirtioIOMMU *interface)
+{
+    qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc);
+}
+
+static void virtio_iommu_setup(QVirtioIOMMU *interface)
+{
+    QVirtioDevice *vdev = interface->vdev;
+    uint64_t features;
+
+    features = qvirtio_get_features(vdev);
+    features &= ~(QVIRTIO_F_BAD_FEATURE |
+                  (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
+                  (1ull << VIRTIO_RING_F_EVENT_IDX) |
+                  (1ull << VIRTIO_IOMMU_F_BYPASS));
+    qvirtio_set_features(vdev, features);
+    interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0);
+    qvirtio_set_driver_ok(interface->vdev);
+}
+
+static void qvirtio_iommu_device_destructor(QOSGraphObject *obj)
+{
+    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
+    QVirtioIOMMU *iommu = &v_iommu->iommu;
+
+    virtio_iommu_cleanup(iommu);
+}
+
+static void qvirtio_iommu_device_start_hw(QOSGraphObject *obj)
+{
+    QVirtioIOMMUDevice *v_iommu = (QVirtioIOMMUDevice *) obj;
+    QVirtioIOMMU *iommu = &v_iommu->iommu;
+
+    virtio_iommu_setup(iommu);
+}
+
+static void *virtio_iommu_device_create(void *virtio_dev,
+                                        QGuestAllocator *t_alloc,
+                                        void *addr)
+{
+    QVirtioIOMMUDevice *virtio_rdevice = g_new0(QVirtioIOMMUDevice, 1);
+    QVirtioIOMMU *interface = &virtio_rdevice->iommu;
+
+    interface->vdev = virtio_dev;
+    alloc = t_alloc;
+
+    virtio_rdevice->obj.get_driver = qvirtio_iommu_device_get_driver;
+    virtio_rdevice->obj.destructor = qvirtio_iommu_device_destructor;
+    virtio_rdevice->obj.start_hw = qvirtio_iommu_device_start_hw;
+
+    return &virtio_rdevice->obj;
+}
+
+/* virtio-iommu-pci */
+static void *qvirtio_iommu_pci_get_driver(void *object, const char *interface)
+{
+    QVirtioIOMMUPCI *v_iommu = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_iommu->pci_vdev.pdev;
+    }
+    return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
+}
+
+static void qvirtio_iommu_pci_destructor(QOSGraphObject *obj)
+{
+    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
+    QVirtioIOMMU *interface = &iommu_pci->iommu;
+    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
+
+    virtio_iommu_cleanup(interface);
+    qvirtio_pci_destructor(pci_vobj);
+}
+
+static void qvirtio_iommu_pci_start_hw(QOSGraphObject *obj)
+{
+    QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
+    QVirtioIOMMU *interface = &iommu_pci->iommu;
+    QOSGraphObject *pci_vobj =  &iommu_pci->pci_vdev.obj;
+
+    qvirtio_pci_start_hw(pci_vobj);
+    virtio_iommu_setup(interface);
+}
+
+
+static void *virtio_iommu_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                   void *addr)
+{
+    QVirtioIOMMUPCI *virtio_rpci = g_new0(QVirtioIOMMUPCI, 1);
+    QVirtioIOMMU *interface = &virtio_rpci->iommu;
+    QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj;
+
+    virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_rpci->pci_vdev.vdev;
+    alloc = t_alloc;
+
+    obj->get_driver = qvirtio_iommu_pci_get_driver;
+    obj->start_hw = qvirtio_iommu_pci_start_hw;
+    obj->destructor = qvirtio_iommu_pci_destructor;
+
+    return obj;
+}
+
+static void virtio_iommu_register_nodes(void)
+{
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "addr=04.0",
+    };
+
+    /* virtio-iommu-device */
+    qos_node_create_driver("virtio-iommu-device", virtio_iommu_device_create);
+    qos_node_consumes("virtio-iommu-device", "virtio-bus", NULL);
+    qos_node_produces("virtio-iommu-device", "virtio");
+    qos_node_produces("virtio-iommu-device", "virtio-iommu");
+
+    /* virtio-iommu-pci */
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-iommu-pci", virtio_iommu_pci_create);
+    qos_node_consumes("virtio-iommu-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-iommu-pci", "pci-device");
+    qos_node_produces("virtio-iommu-pci", "virtio");
+    qos_node_produces("virtio-iommu-pci", "virtio-iommu");
+}
+
+libqos_init(virtio_iommu_register_nodes);
diff --git a/tests/qtest/libqos/virtio-iommu.h b/tests/qtest/libqos/virtio-iommu.h
new file mode 100644
index 00000000000..047e71cbe16
--- /dev/null
+++ b/tests/qtest/libqos/virtio-iommu.h
@@ -0,0 +1,45 @@ 
+/*
+ * libqos driver virtio-iommu-pci framework
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef TESTS_LIBQOS_VIRTIO_IOMMU_H
+#define TESTS_LIBQOS_VIRTIO_IOMMU_H
+
+#include "qgraph.h"
+#include "virtio.h"
+#include "virtio-pci.h"
+
+typedef struct QVirtioIOMMU QVirtioIOMMU;
+typedef struct QVirtioIOMMUPCI QVirtioIOMMUPCI;
+typedef struct QVirtioIOMMUDevice QVirtioIOMMUDevice;
+
+struct QVirtioIOMMU {
+    QVirtioDevice *vdev;
+    QVirtQueue *vq;
+};
+
+struct QVirtioIOMMUPCI {
+    QVirtioPCIDevice pci_vdev;
+    QVirtioIOMMU iommu;
+};
+
+struct QVirtioIOMMUDevice {
+    QOSGraphObject obj;
+    QVirtioIOMMU iommu;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c9d8458062f..982ffb3e38d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -230,6 +230,7 @@  qos_test_ss.add(
   'virtio-rng-test.c',
   'virtio-scsi-test.c',
   'virtio-serial-test.c',
+  'virtio-iommu-test.c',
   'vmxnet3-test.c',
 )
 if have_virtfs
diff --git a/tests/qtest/virtio-iommu-test.c b/tests/qtest/virtio-iommu-test.c
new file mode 100644
index 00000000000..d257d17d7d3
--- /dev/null
+++ b/tests/qtest/virtio-iommu-test.c
@@ -0,0 +1,295 @@ 
+/*
+ * QTest testcase for VirtIO IOMMU
+ *
+ * Copyright (c) 2020 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-iommu.h"
+#include "hw/virtio/virtio-iommu.h"
+
+#define PCI_SLOT_HP             0x06
+#define QVIRTIO_IOMMU_TIMEOUT_US (30 * 1000 * 1000)
+
+static QGuestAllocator *alloc;
+
+static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+    QVirtioIOMMU *v_iommu = obj;
+    QVirtioDevice *dev = v_iommu->vdev;
+    uint64_t input_range_start = qvirtio_config_readq(dev, 8);
+    uint64_t input_range_end = qvirtio_config_readq(dev, 16);
+    uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
+    uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
+
+    g_assert_cmpint(input_range_start, ==, 0);
+    g_assert_cmphex(input_range_end, ==, 0xFFFFFFFFFFFFFFFF);
+    g_assert_cmpint(domain_range_start, ==, 0);
+    g_assert_cmpint(domain_range_end, ==, 32);
+}
+
+/**
+ * send_attach_detach - Send an attach/detach command to the device
+ * @type: VIRTIO_IOMMU_T_ATTACH/VIRTIO_IOMMU_T_DETACH
+ * @domain: domain the end point is attached to
+ * @ep: end-point
+ */
+static int send_attach_detach(QTestState *qts, QVirtioIOMMU *v_iommu,
+                              uint8_t type, uint32_t domain, uint32_t ep)
+{
+    QVirtioDevice *dev = v_iommu->vdev;
+    QVirtQueue *vq = v_iommu->vq;
+    uint64_t ro_addr, wr_addr;
+    uint32_t free_head;
+    struct virtio_iommu_req_attach req; /* same layout as detach */
+    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+    char buffer[64];
+    int ret;
+
+    req.head.type = type;
+    req.domain = domain;
+    req.endpoint = ep;
+
+    ro_addr = guest_alloc(alloc, ro_size);
+    wr_addr = guest_alloc(alloc, wr_size);
+
+    qtest_memwrite(qts, ro_addr, &req, ro_size);
+    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+    qvirtqueue_kick(qts, dev, vq, free_head);
+    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+                           QVIRTIO_IOMMU_TIMEOUT_US);
+    qtest_memread(qts, wr_addr, buffer, wr_size);
+    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
+    guest_free(alloc, ro_addr);
+    guest_free(alloc, wr_addr);
+    return ret;
+}
+
+/**
+ * send_map - Send a map command to the device
+ * @domain: domain the new binding is attached to
+ * @virt_start: iova start
+ * @virt_end: iova end
+ * @phys_start: base physical address
+ * @flags: mapping flags
+ */
+static int send_map(QTestState *qts, QVirtioIOMMU *v_iommu,
+                    uint32_t domain, uint64_t virt_start, uint64_t virt_end,
+                    uint64_t phys_start, uint32_t flags)
+{
+    QVirtioDevice *dev = v_iommu->vdev;
+    QVirtQueue *vq = v_iommu->vq;
+    uint64_t ro_addr, wr_addr;
+    uint32_t free_head;
+    struct virtio_iommu_req_map req;
+    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+    char buffer[64];
+    int ret;
+
+    req.head.type = VIRTIO_IOMMU_T_MAP;
+    req.domain = domain;
+    req.virt_start = virt_start;
+    req.virt_end = virt_end;
+    req.phys_start = phys_start;
+    req.flags = flags;
+
+    ro_addr = guest_alloc(alloc, ro_size);
+    wr_addr = guest_alloc(alloc, wr_size);
+
+    qtest_memwrite(qts, ro_addr, &req, ro_size);
+    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+    qvirtqueue_kick(qts, dev, vq, free_head);
+    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+                           QVIRTIO_IOMMU_TIMEOUT_US);
+    memread(wr_addr, buffer, wr_size);
+    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
+    guest_free(alloc, ro_addr);
+    guest_free(alloc, wr_addr);
+    return ret;
+}
+
+/**
+ * send_unmap - Send an unmap command to the device
+ * @domain: domain the new binding is attached to
+ * @virt_start: iova start
+ * @virt_end: iova end
+ */
+static int send_unmap(QTestState *qts, QVirtioIOMMU *v_iommu,
+                      uint32_t domain, uint64_t virt_start, uint64_t virt_end)
+{
+    QVirtioDevice *dev = v_iommu->vdev;
+    QVirtQueue *vq = v_iommu->vq;
+    uint64_t ro_addr, wr_addr;
+    uint32_t free_head;
+    struct virtio_iommu_req_unmap req;
+    size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+    size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+    char buffer[64];
+    int ret;
+
+    req.head.type = VIRTIO_IOMMU_T_UNMAP;
+    req.domain = domain;
+    req.virt_start = virt_start;
+    req.virt_end = virt_end;
+
+    ro_addr = guest_alloc(alloc, ro_size);
+    wr_addr = guest_alloc(alloc, wr_size);
+
+    qtest_memwrite(qts, ro_addr, &req, ro_size);
+    free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+    qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+    qvirtqueue_kick(qts, dev, vq, free_head);
+    qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+                           QVIRTIO_IOMMU_TIMEOUT_US);
+    memread(wr_addr, buffer, wr_size);
+    ret = ((struct virtio_iommu_req_tail *)buffer)->status;
+    guest_free(alloc, ro_addr);
+    guest_free(alloc, wr_addr);
+    return ret;
+}
+
+/* Test unmap scenari documented in the spec v0.12 */
+static void test_attach_detach(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+    QVirtioIOMMU *v_iommu = obj;
+    QTestState *qts = global_qtest;
+    int ret;
+
+    alloc = t_alloc;
+
+    /* type, domain, ep */
+
+    /* attach ep0 to domain 0 */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 0);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* attach a non existing device (1) */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 444);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+    /* detach a non existing device (1) */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 1);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+    /* move ep0 from domain 0 to domain 1 */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* detach ep0 to domain 0 */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 0);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
+
+    /* detach ep0 from domain 1 */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
+    g_assert_cmpint(ret, ==, 0);
+
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+    g_assert_cmpint(ret, ==, 0);
+    ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000,
+                   VIRTIO_IOMMU_MAP_F_READ);
+    g_assert_cmpint(ret, ==, 0);
+    ret = send_map(qts, v_iommu, 1, 0x2000, 0x2FFF, 0xb1000,
+                   VIRTIO_IOMMU_MAP_F_READ);
+    g_assert_cmpint(ret, ==, 0);
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
+    g_assert_cmpint(ret, ==, 0);
+}
+
+static void test_map_unmap(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+    QVirtioIOMMU *v_iommu = obj;
+    QTestState *qts = global_qtest;
+    int ret;
+
+    alloc = t_alloc;
+
+    /* attach ep0 to domain 1 */
+    ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+    g_assert_cmpint(ret, ==, 0);
+
+    ret = send_map(qts, v_iommu, 0, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+    /* domain, virt start, virt end, phys start, flags */
+    ret = send_map(qts, v_iommu, 1, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+    g_assert_cmpint(ret, ==, 0);
+
+    ret = send_unmap(qts, v_iommu, 4, 0x10, 0xFFF);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+    ret = send_unmap(qts, v_iommu, 1, 0x10, 0xFFF);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE);
+
+    ret = send_unmap(qts, v_iommu, 1, 0, 0x1000);
+    g_assert_cmpint(ret, ==, 0); /* unmap everything */
+
+    /* Spec example sequence */
+
+    /* 1 */
+    ret = send_unmap(qts, v_iommu, 1, 0, 4);
+    g_assert_cmpint(ret, ==, 0); /* doesn't unmap anything */
+
+    /* 2 */
+    send_map(qts, v_iommu, 1, 0, 9, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 9);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,9] */
+
+    /* 3 */
+    send_map(qts, v_iommu, 1, 0, 4, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
+    send_map(qts, v_iommu, 1, 5, 9, 0xb2000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 9);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [5,9] */
+
+    /* 4 */
+    send_map(qts, v_iommu, 1, 0, 9, 0xc1000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 4);
+    g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE); /* doesn't unmap anything */
+
+    ret = send_unmap(qts, v_iommu, 1, 0, 10);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* 5 */
+    send_map(qts, v_iommu, 1, 0, 4, 0xd1000, VIRTIO_IOMMU_MAP_F_READ);
+    send_map(qts, v_iommu, 1, 5, 9, 0xd2000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 4);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
+
+    ret = send_unmap(qts, v_iommu, 1, 5, 9);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* 6 */
+    send_map(qts, v_iommu, 1, 0, 4, 0xe2000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 9);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
+
+    /* 7 */
+    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
+    send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 14);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
+
+    send_unmap(qts, v_iommu, 1, 0, 100);
+    send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
+    send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
+    ret = send_unmap(qts, v_iommu, 1, 0, 4);
+    g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
+}
+
+static void register_virtio_iommu_test(void)
+{
+    qos_add_test("config", "virtio-iommu", pci_config, NULL);
+    qos_add_test("attach_detach", "virtio-iommu", test_attach_detach, NULL);
+    qos_add_test("map_unmap", "virtio-iommu", test_map_unmap, NULL);
+}
+
+libqos_init(register_virtio_iommu_test);