[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v3] tests: qtest: Add virtio-iommu test
From: |
Eric Auger |
Subject: |
Re: [PATCH v3] tests: qtest: Add virtio-iommu test |
Date: |
Fri, 15 Oct 2021 09:17:19 +0200 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.10.1 |
Hi Jean,
On 10/14/21 2:11 PM, Jean-Philippe Brucker wrote:
> Hi Eric,
>
> On Thu, Oct 14, 2021 at 04:34:05AM -0400, 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>
>> Acked-by: Thomas Huth <thuth@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-v3
>>
>> To run the tests:
>> make tests/qtest/qos-test
>> cd build
>> QTEST_QEMU_STORAGE_DAEMON_BINARY=./storage-daemon/qemu-storage-daemon
>> QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64 tests/qtest/qos-test
> Looks like some archs cannot run the test:
>
> $ make check # built with all targets
> qemu-system-arm: -device virtio-iommu-device: VIRTIO-IOMMU is not attached to
> any PCI bus!
> Broken pipe
> ERROR qtest-arm/qos-test - too few tests run (expected 80, got 75)
>
> Also, should the test run on aarch64?
I don't think it is possible to run on aarch64 because there is some
test infrastructure missing (in the past I started to work on this
enablement but struggled on some libqos subtleties and surrendered due
to lack of time
<https://www.linguee.fr/anglais-francais/traduction/subtlety.html>) and
that must be the same for arm. However I would have expected make check
to work and sort out the tests according to the arch. I need to further
look at it then.
I will address your other virtio-iommu specific comments below asap.
Thank you for the detailed review!
Eric
>
>
> [...]
>> diff --git a/tests/qtest/virtio-iommu-test.c
>> b/tests/qtest/virtio-iommu-test.c
>> new file mode 100644
>> index 0000000000..ac4d38c779
>> --- /dev/null
>> +++ b/tests/qtest/virtio-iommu-test.c
>> @@ -0,0 +1,299 @@
>> +/*
>> + * QTest testcase for VirtIO IOMMU
>> + *
>> + * Copyright (c) 2021 Red Hat, Inc.
>> + *
>> + * Authors:
>> + * Eric Auger <eric.auger@redhat.com>
>> + *
>> + * This work is licensed under the terms of the GNU GPL, version 2 or (at
>> your
>> + * option) any later version. 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, ==, UINT64_MAX);
>> + g_assert_cmpint(domain_range_start, ==, 0);
>> + g_assert_cmpint(domain_range_end, ==, 32);
> By the way, this value seems to be left from when the config declared a
> number of domain bits. It's now a range so the value could be UINT32_MAX.
> Right now the driver can't manage more than 32 endpoints at a time.
> I have a patch changing that but planning to send later, it doesn't feel
> urgent.
>
>> +}
>> +
>> +/**
>> + * 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
> ("endpoint"?)
>
>> + */
>> +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 */
> Should the reserved fields be initialized to zero?
>
> The test fails here with my recent bypass patch, which sanity-checks the
> new flags field. But I could change the test in the bypass series, since
> the test doesn't fail as is.
>
> On a related note, the spec says that the device MUST reject the request
> if field reserved is not zero. I can also send a patch for that.
>
>> + size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
>> + size_t wr_size = sizeof(struct virtio_iommu_req_tail);
>> + struct virtio_iommu_req_tail buffer;
>> + int ret;
>> +
>> + req.head.type = type;
>> + req.domain = domain;
>> + req.endpoint = ep;
> A driver would explicitly write little-endian in there with cpu_to_le*().
> I guess that also matters here if the test could run on a big-endian host?
>
>> +
>> + 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 = buffer.status;
> Could check that the rest of the buffer is still 0
>
>> + 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
> (what is the new binding?)
>
>> + * @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);
>> + struct virtio_iommu_req_tail buffer;
>> + 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);
>> + qtest_memread(qts, wr_addr, &buffer, wr_size);
>> + ret = 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);
>> + 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);
>> + qtest_memread(qts, wr_addr, &buffer, wr_size);
>> + ret = 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 */
> By the way, what endpoint is this, the host bridge? I'm still trying to
> understand the test framework, how do we know that ep 0 exists and ep 1
> doesn't?
>
>> + ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 0);
>> + g_assert_cmpint(ret, ==, 0);
>> +
>> + /* attach a non existing device (1) */
> (444)?
>
>> + 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 */
> from
>
>> + 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);
> I was going to say that success here depends on the minimum page size
> offered by the device (if the page size if 64k this request would fail),
> but there is no check for page alignment at the moment.
>
> It's just a SHOULD in the spec so we don't have to add the check, but it
> may help with the VFIO integration - even though VFIO will fail unaligned
> MAP ioctls, we report success to the driver. So I've added alignment
> checks to my TODO list, but nothing urgent
>
>> + 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);
> Could you also check that resending the map returns INVAL?
>
>> +
>> + 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);
> Same as above, the IOVAs aren't aligned on page size (the example assumes
> a 1-byte granule, which arguably isn't the best idea but seemed easier to
> read). The device currently accepts this but it would be better to write
> the test to comply with page alignment. I can also change that when I get
> to the alignment checks.
>
>> + 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);
> Could check the return values: INVAL here means the previous unmap didn't
> actually work
>
>> + 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);
> Shouldn't be needed?
>
>> + 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] */
> Only unmaps [0,4]
>
> Thanks,
> Jean
>
>> +}
>> +
>> +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);
>> --
>> 2.30.1
>>