Extend the fake vhost-user backend with configurable memory slot
support and add a virtio-mem test that places a vring across adjacent
dynamic memslots.

Activate another memslot after configuring the boundary-crossing vring
to trigger a vhost memory table update. Verify that the new slot is
advertised as a separate region instead of extending the merge required
by the vring.

Buglink: https://redhat.atlassian.net/browse/RHEL-146583

Signed-off-by: Alex Fishman <[email protected]>
---
 tests/qtest/vhost-user-test.c | 395 +++++++++++++++++++++++++++++++++-
 1 file changed, 385 insertions(+), 10 deletions(-)

diff --git a/tests/qtest/vhost-user-test.c b/tests/qtest/vhost-user-test.c
index c8b5f8ff71..db57d3695f 100644
--- a/tests/qtest/vhost-user-test.c
+++ b/tests/qtest/vhost-user-test.c
@@ -13,16 +13,19 @@
 #include "libqtest-single.h"
 #include "qapi/error.h"
 #include "qobject/qdict.h"
+#include "qemu/bswap.h"
 #include "qemu/config-file.h"
 #include "qemu/option.h"
 #include "qemu/range.h"
 #include "qemu/sockets.h"
+#include "qemu/units.h"
 #include "chardev/char-fe.h"
 #include "qemu/memfd.h"
 #include "qemu/module.h"
 #include "system/system.h"
 #include "libqos/libqos.h"
 #include "libqos/pci-pc.h"
+#include "libqos/virtio-net.h"
 #include "libqos/virtio-pci.h"
 
 #include "libqos/malloc-pc.h"
@@ -31,6 +34,8 @@
 #include "standard-headers/linux/vhost_types.h"
 #include "standard-headers/linux/virtio_ids.h"
 #include "standard-headers/linux/virtio_net.h"
+#include "standard-headers/linux/virtio_mem.h"
+#include "standard-headers/linux/virtio_pci.h"
 #include "standard-headers/linux/virtio_gpio.h"
 #include "standard-headers/linux/virtio_scmi.h"
 
@@ -39,11 +44,13 @@
 #endif
 
 
-#define QEMU_CMD_MEM    " -m %d -object memory-backend-file,id=mem,size=%dM," \
-                        "mem-path=%s,share=on -numa node,memdev=mem"
-#define QEMU_CMD_MEMFD  " -m %d -object memory-backend-memfd,id=mem,size=%dM," 
\
-                        " -numa node,memdev=mem"
-#define QEMU_CMD_SHM    " -m %d -object memory-backend-shm,id=mem,size=%dM," \
+#define QEMU_CMD_MEM \
+    " -m %d%s -object memory-backend-file,id=mem,size=%dM," \
+    "mem-path=%s,share=on -numa node,memdev=mem"
+#define QEMU_CMD_MEMFD \
+    " -m %d%s -object memory-backend-memfd,id=mem,size=%dM," \
+    " -numa node,memdev=mem"
+#define QEMU_CMD_SHM    " -m %d%s -object memory-backend-shm,id=mem,size=%dM," 
\
                         " -numa node,memdev=mem"
 #define QEMU_CMD_CHR    " -chardev socket,id=%s,path=%s%s"
 #define QEMU_CMD_NETDEV " -netdev vhost-user,id=hs0,chardev=%s,vhostforce=on"
@@ -62,8 +69,11 @@
 #define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1
 #define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN   6
 #define VHOST_USER_PROTOCOL_F_CONFIG 9
+#define VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS 15
 
 #define VHOST_LOG_PAGE 0x1000
+#define TEST_VHOST_USER_MAX_MEM_SLOTS 1024
+#define TEST_VHOST_USER_MEM_REGS 64
 
 typedef enum VhostUserRequest {
     VHOST_USER_NONE = 0,
@@ -87,6 +97,9 @@ typedef enum VhostUserRequest {
     VHOST_USER_SET_VRING_ENABLE = 18,
     VHOST_USER_GET_CONFIG = 24,
     VHOST_USER_SET_CONFIG = 25,
+    VHOST_USER_GET_MAX_MEM_SLOTS = 36,
+    VHOST_USER_ADD_MEM_REG = 37,
+    VHOST_USER_REM_MEM_REG = 38,
     VHOST_USER_MAX
 } VhostUserRequest;
 
@@ -103,6 +116,11 @@ typedef struct VhostUserMemory {
     VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
 } VhostUserMemory;
 
+typedef struct VhostUserMemRegMsg {
+    uint64_t padding;
+    VhostUserMemoryRegion region;
+} VhostUserMemRegMsg;
+
 typedef struct VhostUserLog {
     uint64_t mmap_size;
     uint64_t mmap_offset;
@@ -122,6 +140,7 @@ typedef struct VhostUserMsg {
         struct vhost_vring_state state;
         struct vhost_vring_addr addr;
         VhostUserMemory memory;
+        VhostUserMemRegMsg mem_reg;
         VhostUserLog log;
     } payload;
 } QEMU_PACKED VhostUserMsg;
@@ -169,6 +188,11 @@ typedef struct TestServer {
     bool test_fail;
     int test_flags;
     int queues;
+    bool configure_mem_slots;
+    unsigned int get_max_mem_slots_count;
+    unsigned int add_mem_reg_count;
+    unsigned int rem_mem_reg_count;
+    VhostUserMemoryRegion add_mem_regs[TEST_VHOST_USER_MEM_REGS];
     struct vhost_user_ops *vu_ops;
 } TestServer;
 
@@ -220,8 +244,9 @@ static void append_vhost_gpio_opts(TestServer *s, GString 
*cmd_line,
                            chr_opts);
 }
 
-static void append_mem_opts(TestServer *server, GString *cmd_line,
-                            int size, enum test_memfd memfd)
+static void append_mem_opts_full(TestServer *server, GString *cmd_line,
+                                 int size, enum test_memfd memfd,
+                                 const char *size_opts)
 {
     if (memfd == TEST_MEMFD_AUTO) {
         memfd = qemu_memfd_check(MFD_ALLOW_SEALING) ? TEST_MEMFD_YES
@@ -229,16 +254,25 @@ static void append_mem_opts(TestServer *server, GString 
*cmd_line,
     }
 
     if (memfd == TEST_MEMFD_YES) {
-        g_string_append_printf(cmd_line, QEMU_CMD_MEMFD, size, size);
+        g_string_append_printf(cmd_line, QEMU_CMD_MEMFD,
+                               size, size_opts, size);
     } else if (memfd == TEST_MEMFD_SHM) {
-        g_string_append_printf(cmd_line, QEMU_CMD_SHM, size, size);
+        g_string_append_printf(cmd_line, QEMU_CMD_SHM,
+                               size, size_opts, size);
     } else {
         const char *root = init_hugepagefs() ? : server->tmpfs;
 
-        g_string_append_printf(cmd_line, QEMU_CMD_MEM, size, size, root);
+        g_string_append_printf(cmd_line, QEMU_CMD_MEM,
+                               size, size_opts, size, root);
     }
 }
 
+static void append_mem_opts(TestServer *server, GString *cmd_line,
+                            int size, enum test_memfd memfd)
+{
+    append_mem_opts_full(server, cmd_line, size, memfd, "");
+}
+
 static bool wait_for_fds(TestServer *s)
 {
     gint64 end_time;
@@ -501,6 +535,40 @@ static void chr_read(void *opaque, const uint8_t *buf, int 
size)
         qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
         break;
 
+    case VHOST_USER_GET_MAX_MEM_SLOTS:
+        s->get_max_mem_slots_count++;
+        msg.flags |= VHOST_USER_REPLY_MASK;
+        msg.size = sizeof(m.payload.u64);
+        msg.payload.u64 = TEST_VHOST_USER_MAX_MEM_SLOTS;
+        p = (uint8_t *) &msg;
+        qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
+        g_cond_broadcast(&s->data_cond);
+        break;
+
+    case VHOST_USER_ADD_MEM_REG:
+        g_assert_cmpuint(msg.size, ==, sizeof(msg.payload.mem_reg));
+        g_assert_cmpint(qemu_chr_fe_get_msgfds(chr, &fd, 1), ==, 1);
+        g_assert_cmpint(fd, >=, 0);
+        close(fd);
+        g_assert_cmpuint(s->add_mem_reg_count, <,
+                         G_N_ELEMENTS(s->add_mem_regs));
+        s->add_mem_regs[s->add_mem_reg_count] = msg.payload.mem_reg.region;
+        s->add_mem_reg_count++;
+        g_test_message("add_mem_reg: gpa=0x%" PRIx64 " size=0x%" PRIx64,
+                       msg.payload.mem_reg.region.guest_phys_addr,
+                       msg.payload.mem_reg.region.memory_size);
+        g_cond_broadcast(&s->data_cond);
+        break;
+
+    case VHOST_USER_REM_MEM_REG:
+        g_assert_cmpuint(msg.size, ==, sizeof(msg.payload.mem_reg));
+        s->rem_mem_reg_count++;
+        g_test_message("rem_mem_reg: gpa=0x%" PRIx64 " size=0x%" PRIx64,
+                       msg.payload.mem_reg.region.guest_phys_addr,
+                       msg.payload.mem_reg.region.memory_size);
+        g_cond_broadcast(&s->data_cond);
+        break;
+
     case VHOST_USER_SET_VRING_ENABLE:
         /*
          * Another case we ignore as we don't need to respond. With a
@@ -1048,6 +1116,301 @@ static void *vhost_user_test_setup_multiqueue(GString 
*cmd_line, void *arg)
     return s;
 }
 
+static void *vhost_user_test_setup_mem_slots(GString *cmd_line, void *arg)
+{
+    TestServer *s = test_server_new("mem-slots", arg);
+
+    s->configure_mem_slots = true;
+    test_server_listen(s);
+
+    append_mem_opts_full(s, cmd_line, 256, TEST_MEMFD_YES,
+                         ",maxmem=4G,slots=32");
+    g_string_append(cmd_line,
+                    " -object memory-backend-memfd,id=vmem,size=3G,share=on"
+                    " -device virtio-mem-pci,memdev=vmem,dynamic-memslots=on,"
+                    "requested-size=3G,unplugged-inaccessible=on,addr=05.0");
+    s->vu_ops->append_opts(s, cmd_line, "");
+
+    g_test_queue_destroy(vhost_user_test_cleanup, s);
+
+    return s;
+}
+
+static QVirtioPCIDevice *virtio_mem_init(QPCIBus *bus,
+                                         QGuestAllocator *alloc,
+                                         QVirtQueue **vq)
+{
+    QPCIAddress addr = { .devfn = QPCI_DEVFN(5, 0) };
+    QVirtioPCIDevice *dev = virtio_pci_new(bus, &addr);
+    uint64_t features;
+
+    g_assert_nonnull(dev);
+    g_assert_cmpuint(dev->vdev.device_type, ==, VIRTIO_ID_MEM);
+
+    qvirtio_pci_device_enable(dev);
+    qvirtio_start_device(&dev->vdev);
+
+    features = qvirtio_get_features(&dev->vdev);
+    g_assert_true(features & (1ULL << VIRTIO_F_VERSION_1));
+    g_assert_true(features &
+                  (1ULL << VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE));
+    features = (1ULL << VIRTIO_F_VERSION_1) |
+               (1ULL << VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE);
+    qvirtio_set_features(&dev->vdev, features);
+
+    *vq = qvirtqueue_setup(&dev->vdev, alloc, 0);
+    qvirtio_set_driver_ok(&dev->vdev);
+
+    return dev;
+}
+
+static void virtio_mem_request(QVirtioPCIDevice *dev, QVirtQueue *vq,
+                               QGuestAllocator *alloc, uint16_t type,
+                               uint64_t addr, uint16_t nb_blocks)
+{
+    QTestState *qts = global_qtest;
+    struct virtio_mem_req req = {
+        .type = cpu_to_le16(type),
+    };
+    struct virtio_mem_resp resp;
+    uint64_t req_addr, resp_addr;
+    uint32_t free_head;
+
+    if (type == VIRTIO_MEM_REQ_PLUG) {
+        req.u.plug.addr = cpu_to_le64(addr);
+        req.u.plug.nb_blocks = cpu_to_le16(nb_blocks);
+    } else {
+        g_assert_cmpuint(type, ==, VIRTIO_MEM_REQ_UNPLUG);
+        req.u.unplug.addr = cpu_to_le64(addr);
+        req.u.unplug.nb_blocks = cpu_to_le16(nb_blocks);
+    }
+
+    req_addr = guest_alloc(alloc, sizeof(req));
+    resp_addr = guest_alloc(alloc, sizeof(resp));
+    memwrite(req_addr, &req, sizeof(req));
+
+    free_head = qvirtqueue_add(qts, vq, req_addr, sizeof(req), false, true);
+    qvirtqueue_add(qts, vq, resp_addr, sizeof(resp), true, false);
+    qvirtqueue_kick(qts, &dev->vdev, vq, free_head);
+    qvirtio_wait_used_elem(qts, &dev->vdev, vq, free_head, NULL,
+                           5 * G_TIME_SPAN_SECOND);
+
+    memread(resp_addr, &resp, sizeof(resp));
+    g_assert_cmpuint(le16_to_cpu(resp.type), ==, VIRTIO_MEM_RESP_ACK);
+
+    guest_free(alloc, resp_addr);
+    guest_free(alloc, req_addr);
+}
+
+static bool gpa_covered_by_mem_regs(const VhostUserMemoryRegion *regs,
+                                    unsigned int count,
+                                    uint64_t gpa, uint64_t size)
+{
+    uint64_t covered = gpa;
+    uint64_t end = gpa + size;
+
+    while (covered < end) {
+        uint64_t next = covered;
+        unsigned int i;
+
+        for (i = 0; i < count; i++) {
+            uint64_t reg_start = regs[i].guest_phys_addr;
+            uint64_t reg_end = reg_start + regs[i].memory_size;
+
+            if (reg_start <= covered && reg_end > next) {
+                next = reg_end;
+            }
+        }
+
+        if (next == covered) {
+            return false;
+        }
+        covered = next;
+    }
+
+    return true;
+}
+
+static bool has_mem_reg(const VhostUserMemoryRegion *regs,
+                        unsigned int count, uint64_t gpa, uint64_t size)
+{
+    unsigned int i;
+
+    for (i = 0; i < count; i++) {
+        if (regs[i].guest_phys_addr == gpa &&
+            regs[i].memory_size == size) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static void wait_for_mem_coverage(TestServer *s, unsigned int from,
+                                  uint64_t gpa, uint64_t size)
+{
+    gint64 end_time;
+
+    g_mutex_lock(&s->data_mutex);
+    end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+    while (!gpa_covered_by_mem_regs(&s->add_mem_regs[from],
+                                    s->add_mem_reg_count - from,
+                                    gpa, size)) {
+        if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+            break;
+        }
+    }
+    g_assert_true(gpa_covered_by_mem_regs(&s->add_mem_regs[from],
+                                          s->add_mem_reg_count - from,
+                                          gpa, size));
+    g_mutex_unlock(&s->data_mutex);
+}
+
+static void wait_for_mem_reg(TestServer *s, unsigned int from,
+                             uint64_t gpa, uint64_t size)
+{
+    gint64 end_time;
+
+    g_mutex_lock(&s->data_mutex);
+    end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+    while (!has_mem_reg(&s->add_mem_regs[from],
+                        s->add_mem_reg_count - from, gpa, size)) {
+        if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+            break;
+        }
+    }
+    g_assert_true(has_mem_reg(&s->add_mem_regs[from],
+                              s->add_mem_reg_count - from, gpa, size));
+    g_mutex_unlock(&s->data_mutex);
+}
+
+static QVirtioPCIDevice *recreate_net_with_boundary_vring(QVirtioNet *net,
+                                                           uint64_t boundary)
+{
+    QVirtioPCIDevice *old_pdev = container_of(net->vdev,
+                                              QVirtioPCIDevice, vdev);
+    QPCIBus *bus = old_pdev->pdev->bus;
+    QPCIAddress addr = { .devfn = QPCI_DEVFN(4, 0) };
+    QVirtioPCIDevice *pdev;
+    QVirtioDevice *vdev;
+    QVirtQueue vq = { };
+    uint64_t features;
+
+    qpci_unplug_acpi_device_test(global_qtest, "net0", 4);
+    qtest_qmp_device_add(global_qtest, "virtio-net-pci", "net1",
+                         "{'netdev': 'hs0', 'addr': '04.0'}");
+
+    pdev = virtio_pci_new(bus, &addr);
+    g_assert_nonnull(pdev);
+    vdev = &pdev->vdev;
+
+    qvirtio_pci_device_enable(pdev);
+    qvirtio_start_device(vdev);
+    features = qvirtio_get_features(vdev);
+    features &= ~(QVIRTIO_F_BAD_FEATURE |
+                  (1ULL << VIRTIO_RING_F_INDIRECT_DESC) |
+                  (1ULL << VIRTIO_RING_F_EVENT_IDX));
+    qvirtio_set_features(vdev, features);
+
+    vdev->bus->queue_select(vdev, 0);
+    vq.vdev = vdev;
+    vq.index = 0;
+    vq.size = vdev->bus->get_queue_size(vdev);
+    vq.free_head = 0;
+    vq.num_free = vq.size;
+    vq.align = VIRTIO_PCI_VRING_ALIGN;
+
+    /*
+     * Place the new queue's descriptor table so that the first descriptor is
+     * in the lower memslot and all following descriptors are in the upper
+     * memslot.
+     */
+    qvring_init(global_qtest, NULL, &vq,
+                boundary - sizeof(struct vring_desc));
+    vdev->bus->set_queue_address(vdev, &vq);
+
+    /* qvirtqueue_setup() normally performs this final modern PCI step. */
+    qpci_io_writew(pdev->pdev, pdev->bar,
+                   pdev->common_cfg_offset +
+                   offsetof(struct virtio_pci_common_cfg, queue_enable), 1);
+    qvirtio_set_driver_ok(vdev);
+
+    return pdev;
+}
+
+static void test_mem_slots_boundary(void *obj, void *arg,
+                                    QGuestAllocator *alloc)
+{
+    TestServer *s = arg;
+    QVirtioNet *net = obj;
+    QPCIBus *bus;
+    QVirtioPCIDevice *dev;
+    QVirtioPCIDevice *net_dev;
+    QVirtQueue *vq;
+    gint64 end_time;
+    uint64_t block_size, mem_addr, region_size;
+    unsigned int initial_add_count;
+    unsigned int third_slot_add_from;
+    unsigned int i;
+
+    g_mutex_lock(&s->data_mutex);
+    end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+    while (!s->get_max_mem_slots_count || !s->add_mem_reg_count) {
+        if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+            break;
+        }
+    }
+    g_assert_cmpuint(s->get_max_mem_slots_count, ==, 1);
+    g_assert_cmpuint(s->add_mem_reg_count, >, 0);
+    initial_add_count = s->add_mem_reg_count;
+    g_mutex_unlock(&s->data_mutex);
+
+    bus = qpci_new_pc(global_qtest, alloc);
+    dev = virtio_mem_init(bus, alloc, &vq);
+    block_size = qvirtio_config_readq(&dev->vdev,
+                                      offsetof(struct virtio_mem_config,
+                                               block_size));
+    mem_addr = qvirtio_config_readq(&dev->vdev,
+                                    offsetof(struct virtio_mem_config, addr));
+    region_size = qvirtio_config_readq(&dev->vdev,
+                                       offsetof(struct virtio_mem_config,
+                                                region_size));
+    g_assert_cmpuint(region_size, ==, 3 * GiB);
+
+    g_assert_cmpuint(block_size, <=, GiB);
+    g_assert_true(QEMU_IS_ALIGNED(GiB, block_size));
+
+    /* One request crossing the boundary has to activate slots 0 and 1. */
+    virtio_mem_request(dev, vq, alloc, VIRTIO_MEM_REQ_PLUG,
+                       mem_addr + GiB - block_size, 2);
+    wait_for_mem_coverage(s, initial_add_count, mem_addr, 2 * GiB);
+
+    net_dev = recreate_net_with_boundary_vring(net, mem_addr + GiB);
+
+    g_mutex_lock(&s->data_mutex);
+    third_slot_add_from = s->add_mem_reg_count;
+    g_mutex_unlock(&s->data_mutex);
+
+    /* The third slot must remain separate from the merged first two slots. */
+    virtio_mem_request(dev, vq, alloc, VIRTIO_MEM_REQ_PLUG,
+                       mem_addr + 2 * GiB, 1);
+    wait_for_mem_reg(s, third_slot_add_from, mem_addr + 2 * GiB, GiB);
+
+    g_mutex_lock(&s->data_mutex);
+    for (i = 0; i < 3; i++) {
+        g_assert_true(gpa_covered_by_mem_regs(
+            &s->add_mem_regs[initial_add_count],
+            s->add_mem_reg_count - initial_add_count,
+            mem_addr + i * GiB, GiB));
+    }
+    g_mutex_unlock(&s->data_mutex);
+
+    qvirtqueue_cleanup(dev->vdev.bus, vq, alloc);
+    qos_object_destroy(&dev->obj);
+    qos_object_destroy(&net_dev->obj);
+    qpci_free_pc(bus);
+}
+
 static void test_multiqueue(void *obj, void *arg, QGuestAllocator *alloc)
 {
     TestServer *s = arg;
@@ -1090,6 +1453,10 @@ static void vu_net_get_protocol_features(TestServer *s, 
CharFrontend *chr,
     if (s->queues > 1) {
         msg->payload.u64 |= 1 << VHOST_USER_PROTOCOL_F_MQ;
     }
+    if (s->configure_mem_slots) {
+        msg->payload.u64 |= 1ULL <<
+                            VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS;
+    }
     qemu_chr_fe_write_all(chr, (uint8_t *)msg, VHOST_USER_HDR_SIZE + 
msg->size);
 }
 
@@ -1151,6 +1518,14 @@ static void register_vhost_user_test(void)
     qos_add_test("vhost-user/multiqueue",
                  "virtio-net",
                  test_multiqueue, &opts);
+
+    if (qemu_memfd_check(MFD_ALLOW_SEALING) &&
+        qtest_has_device("virtio-mem-pci")) {
+        opts.before = vhost_user_test_setup_mem_slots;
+        opts.edge.extra_device_opts = "id=net0";
+        qos_add_test("vhost-user/mem-slots/boundary",
+                     "virtio-net", test_mem_slots_boundary, &opts);
+    }
 }
 libqos_init(register_vhost_user_test);
 
-- 
2.52.0


Reply via email to