qemu-storage-daemon is launched with the vhost-user listen socket path.
The path is first unlinked before opening the listen socket. This
prevents stale UNIX domain socket files from stopping socket
initialization.
This behavior is undesirable in vhost-user-blk-test and the cause of a
bug:
There is a race condition in vhost-user-blk-test when QEMU launches
before QSD. It connects to the old socket that QSD unlinks and the
vhost-user connection is never serviced, resulting in a hang.
Pass the listen socket fd to QSD to maintain listen socket continuity
and prevent the lost connection.
Fixes: 806952026d
("test: new qTest case to test the vhost-user-blk-server")
Cc: Raphael Norwitz <raphael.norwitz@nutanix.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Cc: Thomas Huth <thuth@redhat.com>
Cc: Coiby Xu <coiby.xu@gmail.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211019135655.83067-1-stefanha@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
998 lines
31 KiB
C
998 lines
31 KiB
C
/*
|
|
* QTest testcase for Vhost-user Block Device
|
|
*
|
|
* Based on tests/qtest//virtio-blk-test.c
|
|
|
|
* Copyright (c) 2014 SUSE LINUX Products GmbH
|
|
* Copyright (c) 2014 Marc Marí
|
|
* Copyright (c) 2020 Coiby Xu
|
|
*
|
|
* 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/bswap.h"
|
|
#include "qemu/module.h"
|
|
#include "standard-headers/linux/virtio_blk.h"
|
|
#include "standard-headers/linux/virtio_pci.h"
|
|
#include "libqos/qgraph.h"
|
|
#include "libqos/vhost-user-blk.h"
|
|
#include "libqos/libqos-pc.h"
|
|
|
|
#define TEST_IMAGE_SIZE (64 * 1024 * 1024)
|
|
#define QVIRTIO_BLK_TIMEOUT_US (30 * 1000 * 1000)
|
|
#define PCI_SLOT_HP 0x06
|
|
|
|
typedef struct {
|
|
pid_t pid;
|
|
} QemuStorageDaemonState;
|
|
|
|
typedef struct QVirtioBlkReq {
|
|
uint32_t type;
|
|
uint32_t ioprio;
|
|
uint64_t sector;
|
|
char *data;
|
|
uint8_t status;
|
|
} QVirtioBlkReq;
|
|
|
|
#ifdef HOST_WORDS_BIGENDIAN
|
|
static const bool host_is_big_endian = true;
|
|
#else
|
|
static const bool host_is_big_endian; /* false */
|
|
#endif
|
|
|
|
static inline void virtio_blk_fix_request(QVirtioDevice *d, QVirtioBlkReq *req)
|
|
{
|
|
if (qvirtio_is_big_endian(d) != host_is_big_endian) {
|
|
req->type = bswap32(req->type);
|
|
req->ioprio = bswap32(req->ioprio);
|
|
req->sector = bswap64(req->sector);
|
|
}
|
|
}
|
|
|
|
static inline void virtio_blk_fix_dwz_hdr(QVirtioDevice *d,
|
|
struct virtio_blk_discard_write_zeroes *dwz_hdr)
|
|
{
|
|
if (qvirtio_is_big_endian(d) != host_is_big_endian) {
|
|
dwz_hdr->sector = bswap64(dwz_hdr->sector);
|
|
dwz_hdr->num_sectors = bswap32(dwz_hdr->num_sectors);
|
|
dwz_hdr->flags = bswap32(dwz_hdr->flags);
|
|
}
|
|
}
|
|
|
|
static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioDevice *d,
|
|
QVirtioBlkReq *req, uint64_t data_size)
|
|
{
|
|
uint64_t addr;
|
|
uint8_t status = 0xFF;
|
|
QTestState *qts = global_qtest;
|
|
|
|
switch (req->type) {
|
|
case VIRTIO_BLK_T_IN:
|
|
case VIRTIO_BLK_T_OUT:
|
|
g_assert_cmpuint(data_size % 512, ==, 0);
|
|
break;
|
|
case VIRTIO_BLK_T_DISCARD:
|
|
case VIRTIO_BLK_T_WRITE_ZEROES:
|
|
g_assert_cmpuint(data_size %
|
|
sizeof(struct virtio_blk_discard_write_zeroes), ==, 0);
|
|
break;
|
|
default:
|
|
g_assert_cmpuint(data_size, ==, 0);
|
|
}
|
|
|
|
addr = guest_alloc(alloc, sizeof(*req) + data_size);
|
|
|
|
virtio_blk_fix_request(d, req);
|
|
|
|
qtest_memwrite(qts, addr, req, 16);
|
|
qtest_memwrite(qts, addr + 16, req->data, data_size);
|
|
qtest_memwrite(qts, addr + 16 + data_size, &status, sizeof(status));
|
|
|
|
return addr;
|
|
}
|
|
|
|
static void test_invalid_discard_write_zeroes(QVirtioDevice *dev,
|
|
QGuestAllocator *alloc,
|
|
QTestState *qts,
|
|
QVirtQueue *vq,
|
|
uint32_t type)
|
|
{
|
|
QVirtioBlkReq req;
|
|
struct virtio_blk_discard_write_zeroes dwz_hdr;
|
|
struct virtio_blk_discard_write_zeroes dwz_hdr2[2];
|
|
uint64_t req_addr;
|
|
uint32_t free_head;
|
|
uint8_t status;
|
|
|
|
/* More than one dwz is not supported */
|
|
req.type = type;
|
|
req.data = (char *) dwz_hdr2;
|
|
dwz_hdr2[0].sector = 0;
|
|
dwz_hdr2[0].num_sectors = 1;
|
|
dwz_hdr2[0].flags = 0;
|
|
dwz_hdr2[1].sector = 1;
|
|
dwz_hdr2[1].num_sectors = 1;
|
|
dwz_hdr2[1].flags = 0;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr2[0]);
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr2[1]);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr2));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr2), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr2), 1, true,
|
|
false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr2));
|
|
g_assert_cmpint(status, ==, VIRTIO_BLK_S_UNSUPP);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* num_sectors must be less than config->max_write_zeroes_sectors */
|
|
req.type = type;
|
|
req.data = (char *) &dwz_hdr;
|
|
dwz_hdr.sector = 0;
|
|
dwz_hdr.num_sectors = 0xffffffff;
|
|
dwz_hdr.flags = 0;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
|
|
false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr));
|
|
g_assert_cmpint(status, ==, VIRTIO_BLK_S_IOERR);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* sector must be less than the device capacity */
|
|
req.type = type;
|
|
req.data = (char *) &dwz_hdr;
|
|
dwz_hdr.sector = TEST_IMAGE_SIZE / 512 + 1;
|
|
dwz_hdr.num_sectors = 1;
|
|
dwz_hdr.flags = 0;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
|
|
false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr));
|
|
g_assert_cmpint(status, ==, VIRTIO_BLK_S_IOERR);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* reserved flag bits must be zero */
|
|
req.type = type;
|
|
req.data = (char *) &dwz_hdr;
|
|
dwz_hdr.sector = 0;
|
|
dwz_hdr.num_sectors = 1;
|
|
dwz_hdr.flags = ~VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
|
|
false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr));
|
|
g_assert_cmpint(status, ==, VIRTIO_BLK_S_UNSUPP);
|
|
|
|
guest_free(alloc, req_addr);
|
|
}
|
|
|
|
/* Returns the request virtqueue so the caller can perform further tests */
|
|
static QVirtQueue *test_basic(QVirtioDevice *dev, QGuestAllocator *alloc)
|
|
{
|
|
QVirtioBlkReq req;
|
|
uint64_t req_addr;
|
|
uint64_t capacity;
|
|
uint64_t features;
|
|
uint32_t free_head;
|
|
uint8_t status;
|
|
char *data;
|
|
QTestState *qts = global_qtest;
|
|
QVirtQueue *vq;
|
|
|
|
features = qvirtio_get_features(dev);
|
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
|
(1u << VIRTIO_RING_F_INDIRECT_DESC) |
|
|
(1u << VIRTIO_RING_F_EVENT_IDX) |
|
|
(1u << VIRTIO_BLK_F_SCSI));
|
|
qvirtio_set_features(dev, features);
|
|
|
|
capacity = qvirtio_config_readq(dev, 0);
|
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
|
|
|
vq = qvirtqueue_setup(dev, alloc, 0);
|
|
|
|
qvirtio_set_driver_ok(dev);
|
|
|
|
/* Write and read with 3 descriptor layout */
|
|
/* Write request */
|
|
req.type = VIRTIO_BLK_T_OUT;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* Read request */
|
|
req.type = VIRTIO_BLK_T_IN;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
data = g_malloc0(512);
|
|
qtest_memread(qts, req_addr + 16, data, 512);
|
|
g_assert_cmpstr(data, ==, "TEST");
|
|
g_free(data);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
if (features & (1u << VIRTIO_BLK_F_WRITE_ZEROES)) {
|
|
struct virtio_blk_discard_write_zeroes dwz_hdr;
|
|
void *expected;
|
|
|
|
/*
|
|
* WRITE_ZEROES request on the same sector of previous test where
|
|
* we wrote "TEST".
|
|
*/
|
|
req.type = VIRTIO_BLK_T_WRITE_ZEROES;
|
|
req.data = (char *) &dwz_hdr;
|
|
dwz_hdr.sector = 0;
|
|
dwz_hdr.num_sectors = 1;
|
|
dwz_hdr.flags = 0;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
|
|
false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr));
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* Read request to check if the sector contains all zeroes */
|
|
req.type = VIRTIO_BLK_T_IN;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
data = g_malloc(512);
|
|
expected = g_malloc0(512);
|
|
qtest_memread(qts, req_addr + 16, data, 512);
|
|
g_assert_cmpmem(data, 512, expected, 512);
|
|
g_free(expected);
|
|
g_free(data);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
test_invalid_discard_write_zeroes(dev, alloc, qts, vq,
|
|
VIRTIO_BLK_T_WRITE_ZEROES);
|
|
}
|
|
|
|
if (features & (1u << VIRTIO_BLK_F_DISCARD)) {
|
|
struct virtio_blk_discard_write_zeroes dwz_hdr;
|
|
|
|
req.type = VIRTIO_BLK_T_DISCARD;
|
|
req.data = (char *) &dwz_hdr;
|
|
dwz_hdr.sector = 0;
|
|
dwz_hdr.num_sectors = 1;
|
|
dwz_hdr.flags = 0;
|
|
|
|
virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr),
|
|
1, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 16 + sizeof(dwz_hdr));
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
test_invalid_discard_write_zeroes(dev, alloc, qts, vq,
|
|
VIRTIO_BLK_T_DISCARD);
|
|
}
|
|
|
|
if (features & (1u << VIRTIO_F_ANY_LAYOUT)) {
|
|
/* Write and read with 2 descriptor layout */
|
|
/* Write request */
|
|
req.type = VIRTIO_BLK_T_OUT;
|
|
req.ioprio = 1;
|
|
req.sector = 1;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 528, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
guest_free(alloc, req_addr);
|
|
|
|
/* Read request */
|
|
req.type = VIRTIO_BLK_T_IN;
|
|
req.ioprio = 1;
|
|
req.sector = 1;
|
|
req.data = g_malloc0(512);
|
|
|
|
req_addr = virtio_blk_request(alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 513, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
data = g_malloc0(512);
|
|
qtest_memread(qts, req_addr + 16, data, 512);
|
|
g_assert_cmpstr(data, ==, "TEST");
|
|
g_free(data);
|
|
|
|
guest_free(alloc, req_addr);
|
|
}
|
|
|
|
return vq;
|
|
}
|
|
|
|
static void basic(void *obj, void *data, QGuestAllocator *t_alloc)
|
|
{
|
|
QVhostUserBlk *blk_if = obj;
|
|
QVirtQueue *vq;
|
|
|
|
vq = test_basic(blk_if->vdev, t_alloc);
|
|
qvirtqueue_cleanup(blk_if->vdev->bus, vq, t_alloc);
|
|
|
|
}
|
|
|
|
static void indirect(void *obj, void *u_data, QGuestAllocator *t_alloc)
|
|
{
|
|
QVirtQueue *vq;
|
|
QVhostUserBlk *blk_if = obj;
|
|
QVirtioDevice *dev = blk_if->vdev;
|
|
QVirtioBlkReq req;
|
|
QVRingIndirectDesc *indirect;
|
|
uint64_t req_addr;
|
|
uint64_t capacity;
|
|
uint64_t features;
|
|
uint32_t free_head;
|
|
uint8_t status;
|
|
char *data;
|
|
QTestState *qts = global_qtest;
|
|
|
|
features = qvirtio_get_features(dev);
|
|
g_assert_cmphex(features & (1u << VIRTIO_RING_F_INDIRECT_DESC), !=, 0);
|
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
|
(1u << VIRTIO_RING_F_EVENT_IDX) |
|
|
(1u << VIRTIO_BLK_F_SCSI));
|
|
qvirtio_set_features(dev, features);
|
|
|
|
capacity = qvirtio_config_readq(dev, 0);
|
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
|
|
|
vq = qvirtqueue_setup(dev, t_alloc, 0);
|
|
qvirtio_set_driver_ok(dev);
|
|
|
|
/* Write request */
|
|
req.type = VIRTIO_BLK_T_OUT;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
|
|
qvring_indirect_desc_add(dev, qts, indirect, req_addr, 528, false);
|
|
qvring_indirect_desc_add(dev, qts, indirect, req_addr + 528, 1, true);
|
|
free_head = qvirtqueue_add_indirect(qts, vq, indirect);
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
g_free(indirect);
|
|
guest_free(t_alloc, req_addr);
|
|
|
|
/* Read request */
|
|
req.type = VIRTIO_BLK_T_IN;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
|
|
qvring_indirect_desc_add(dev, qts, indirect, req_addr, 16, false);
|
|
qvring_indirect_desc_add(dev, qts, indirect, req_addr + 16, 513, true);
|
|
free_head = qvirtqueue_add_indirect(qts, vq, indirect);
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
data = g_malloc0(512);
|
|
qtest_memread(qts, req_addr + 16, data, 512);
|
|
g_assert_cmpstr(data, ==, "TEST");
|
|
g_free(data);
|
|
|
|
g_free(indirect);
|
|
guest_free(t_alloc, req_addr);
|
|
qvirtqueue_cleanup(dev->bus, vq, t_alloc);
|
|
}
|
|
|
|
static void idx(void *obj, void *u_data, QGuestAllocator *t_alloc)
|
|
{
|
|
QVirtQueue *vq;
|
|
QVhostUserBlkPCI *blk = obj;
|
|
QVirtioPCIDevice *pdev = &blk->pci_vdev;
|
|
QVirtioDevice *dev = &pdev->vdev;
|
|
QVirtioBlkReq req;
|
|
uint64_t req_addr;
|
|
uint64_t capacity;
|
|
uint64_t features;
|
|
uint32_t free_head;
|
|
uint32_t write_head;
|
|
uint32_t desc_idx;
|
|
uint8_t status;
|
|
char *data;
|
|
QOSGraphObject *blk_object = obj;
|
|
QPCIDevice *pci_dev = blk_object->get_driver(blk_object, "pci-device");
|
|
QTestState *qts = global_qtest;
|
|
|
|
if (qpci_check_buggy_msi(pci_dev)) {
|
|
return;
|
|
}
|
|
|
|
qpci_msix_enable(pdev->pdev);
|
|
qvirtio_pci_set_msix_configuration_vector(pdev, t_alloc, 0);
|
|
|
|
features = qvirtio_get_features(dev);
|
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
|
(1u << VIRTIO_RING_F_INDIRECT_DESC) |
|
|
(1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
|
|
(1u << VIRTIO_BLK_F_SCSI));
|
|
qvirtio_set_features(dev, features);
|
|
|
|
capacity = qvirtio_config_readq(dev, 0);
|
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
|
|
|
vq = qvirtqueue_setup(dev, t_alloc, 0);
|
|
qvirtqueue_pci_msix_setup(pdev, (QVirtQueuePCI *)vq, t_alloc, 1);
|
|
|
|
qvirtio_set_driver_ok(dev);
|
|
|
|
/*
|
|
* libvhost-user signals the call fd in VHOST_USER_SET_VRING_CALL, make
|
|
* sure to wait for the isr here so we don't race and confuse it later on.
|
|
*/
|
|
qvirtio_wait_queue_isr(qts, dev, vq, QVIRTIO_BLK_TIMEOUT_US);
|
|
|
|
/* Write request */
|
|
req.type = VIRTIO_BLK_T_OUT;
|
|
req.ioprio = 1;
|
|
req.sector = 0;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
|
|
/* Write request */
|
|
req.type = VIRTIO_BLK_T_OUT;
|
|
req.ioprio = 1;
|
|
req.sector = 1;
|
|
req.data = g_malloc0(512);
|
|
strcpy(req.data, "TEST");
|
|
|
|
req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
/* Notify after processing the third request */
|
|
qvirtqueue_set_used_event(qts, vq, 2);
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
write_head = free_head;
|
|
|
|
/* No notification expected */
|
|
status = qvirtio_wait_status_byte_no_isr(qts, dev,
|
|
vq, req_addr + 528,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
guest_free(t_alloc, req_addr);
|
|
|
|
/* Read request */
|
|
req.type = VIRTIO_BLK_T_IN;
|
|
req.ioprio = 1;
|
|
req.sector = 1;
|
|
req.data = g_malloc0(512);
|
|
|
|
req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
|
|
|
|
g_free(req.data);
|
|
|
|
free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
|
|
qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
|
|
|
|
qvirtqueue_kick(qts, dev, vq, free_head);
|
|
|
|
/* We get just one notification for both requests */
|
|
qvirtio_wait_used_elem(qts, dev, vq, write_head, NULL,
|
|
QVIRTIO_BLK_TIMEOUT_US);
|
|
g_assert(qvirtqueue_get_buf(qts, vq, &desc_idx, NULL));
|
|
g_assert_cmpint(desc_idx, ==, free_head);
|
|
|
|
status = readb(req_addr + 528);
|
|
g_assert_cmpint(status, ==, 0);
|
|
|
|
data = g_malloc0(512);
|
|
qtest_memread(qts, req_addr + 16, data, 512);
|
|
g_assert_cmpstr(data, ==, "TEST");
|
|
g_free(data);
|
|
|
|
guest_free(t_alloc, req_addr);
|
|
|
|
/* End test */
|
|
qpci_msix_disable(pdev->pdev);
|
|
|
|
qvirtqueue_cleanup(dev->bus, vq, t_alloc);
|
|
}
|
|
|
|
static void pci_hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
|
|
{
|
|
QVirtioPCIDevice *dev1 = obj;
|
|
QVirtioPCIDevice *dev;
|
|
QTestState *qts = dev1->pdev->bus->qts;
|
|
|
|
/* plug secondary disk */
|
|
qtest_qmp_device_add(qts, "vhost-user-blk-pci", "drv1",
|
|
"{'addr': %s, 'chardev': 'char2'}",
|
|
stringify(PCI_SLOT_HP) ".0");
|
|
|
|
dev = virtio_pci_new(dev1->pdev->bus,
|
|
&(QPCIAddress) { .devfn = QPCI_DEVFN(PCI_SLOT_HP, 0)
|
|
});
|
|
g_assert_nonnull(dev);
|
|
g_assert_cmpint(dev->vdev.device_type, ==, VIRTIO_ID_BLOCK);
|
|
qvirtio_pci_device_disable(dev);
|
|
qos_object_destroy((QOSGraphObject *)dev);
|
|
|
|
/* unplug secondary disk */
|
|
qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
|
|
}
|
|
|
|
static void multiqueue(void *obj, void *data, QGuestAllocator *t_alloc)
|
|
{
|
|
QVirtioPCIDevice *pdev1 = obj;
|
|
QVirtioDevice *dev1 = &pdev1->vdev;
|
|
QVirtioPCIDevice *pdev8;
|
|
QVirtioDevice *dev8;
|
|
QTestState *qts = pdev1->pdev->bus->qts;
|
|
uint64_t features;
|
|
uint16_t num_queues;
|
|
|
|
/*
|
|
* The primary device has 1 queue and VIRTIO_BLK_F_MQ is not enabled. The
|
|
* VIRTIO specification allows VIRTIO_BLK_F_MQ to be enabled when there is
|
|
* only 1 virtqueue, but --device vhost-user-blk-pci doesn't do this (which
|
|
* is also spec-compliant).
|
|
*/
|
|
features = qvirtio_get_features(dev1);
|
|
g_assert_cmpint(features & (1u << VIRTIO_BLK_F_MQ), ==, 0);
|
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
|
(1u << VIRTIO_RING_F_INDIRECT_DESC) |
|
|
(1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
|
|
(1u << VIRTIO_BLK_F_SCSI));
|
|
qvirtio_set_features(dev1, features);
|
|
|
|
/* Hotplug a secondary device with 8 queues */
|
|
qtest_qmp_device_add(qts, "vhost-user-blk-pci", "drv1",
|
|
"{'addr': %s, 'chardev': 'char2', 'num-queues': 8}",
|
|
stringify(PCI_SLOT_HP) ".0");
|
|
|
|
pdev8 = virtio_pci_new(pdev1->pdev->bus,
|
|
&(QPCIAddress) {
|
|
.devfn = QPCI_DEVFN(PCI_SLOT_HP, 0)
|
|
});
|
|
g_assert_nonnull(pdev8);
|
|
g_assert_cmpint(pdev8->vdev.device_type, ==, VIRTIO_ID_BLOCK);
|
|
|
|
qos_object_start_hw(&pdev8->obj);
|
|
|
|
dev8 = &pdev8->vdev;
|
|
features = qvirtio_get_features(dev8);
|
|
g_assert_cmpint(features & (1u << VIRTIO_BLK_F_MQ),
|
|
==,
|
|
(1u << VIRTIO_BLK_F_MQ));
|
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
|
(1u << VIRTIO_RING_F_INDIRECT_DESC) |
|
|
(1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
|
|
(1u << VIRTIO_BLK_F_SCSI) |
|
|
(1u << VIRTIO_BLK_F_MQ));
|
|
qvirtio_set_features(dev8, features);
|
|
|
|
num_queues = qvirtio_config_readw(dev8,
|
|
offsetof(struct virtio_blk_config, num_queues));
|
|
g_assert_cmpint(num_queues, ==, 8);
|
|
|
|
qvirtio_pci_device_disable(pdev8);
|
|
qos_object_destroy(&pdev8->obj);
|
|
|
|
/* unplug secondary disk */
|
|
qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
|
|
}
|
|
|
|
/*
|
|
* Check that setting the vring addr on a non-existent virtqueue does
|
|
* not crash.
|
|
*/
|
|
static void test_nonexistent_virtqueue(void *obj, void *data,
|
|
QGuestAllocator *t_alloc)
|
|
{
|
|
QVhostUserBlkPCI *blk = obj;
|
|
QVirtioPCIDevice *pdev = &blk->pci_vdev;
|
|
QPCIBar bar0;
|
|
QPCIDevice *dev;
|
|
|
|
dev = qpci_device_find(pdev->pdev->bus, QPCI_DEVFN(4, 0));
|
|
g_assert(dev != NULL);
|
|
qpci_device_enable(dev);
|
|
|
|
bar0 = qpci_iomap(dev, 0, NULL);
|
|
|
|
qpci_io_writeb(dev, bar0, VIRTIO_PCI_QUEUE_SEL, 2);
|
|
qpci_io_writel(dev, bar0, VIRTIO_PCI_QUEUE_PFN, 1);
|
|
|
|
g_free(dev);
|
|
}
|
|
|
|
static const char *qtest_qemu_storage_daemon_binary(void)
|
|
{
|
|
const char *qemu_storage_daemon_bin;
|
|
|
|
qemu_storage_daemon_bin = getenv("QTEST_QEMU_STORAGE_DAEMON_BINARY");
|
|
if (!qemu_storage_daemon_bin) {
|
|
fprintf(stderr, "Environment variable "
|
|
"QTEST_QEMU_STORAGE_DAEMON_BINARY required\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* If we've got a path to the binary, check whether we can access it */
|
|
if (strchr(qemu_storage_daemon_bin, '/') &&
|
|
access(qemu_storage_daemon_bin, X_OK) != 0) {
|
|
fprintf(stderr, "ERROR: '%s' is not accessible\n",
|
|
qemu_storage_daemon_bin);
|
|
exit(1);
|
|
}
|
|
|
|
return qemu_storage_daemon_bin;
|
|
}
|
|
|
|
/* g_test_queue_destroy() cleanup function for files */
|
|
static void destroy_file(void *path)
|
|
{
|
|
unlink(path);
|
|
g_free(path);
|
|
qos_invalidate_command_line();
|
|
}
|
|
|
|
static char *drive_create(void)
|
|
{
|
|
int fd, ret;
|
|
/** vhost-user-blk won't recognize drive located in /tmp */
|
|
char *t_path = g_strdup("qtest.XXXXXX");
|
|
|
|
/** Create a temporary raw image */
|
|
fd = mkstemp(t_path);
|
|
g_assert_cmpint(fd, >=, 0);
|
|
ret = ftruncate(fd, TEST_IMAGE_SIZE);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
close(fd);
|
|
|
|
g_test_queue_destroy(destroy_file, t_path);
|
|
return t_path;
|
|
}
|
|
|
|
static char *create_listen_socket(int *fd)
|
|
{
|
|
int tmp_fd;
|
|
char *path;
|
|
|
|
/* No race because our pid makes the path unique */
|
|
path = g_strdup_printf("/tmp/qtest-%d-sock.XXXXXX", getpid());
|
|
tmp_fd = mkstemp(path);
|
|
g_assert_cmpint(tmp_fd, >=, 0);
|
|
close(tmp_fd);
|
|
unlink(path);
|
|
|
|
*fd = qtest_socket_server(path);
|
|
g_test_queue_destroy(destroy_file, path);
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* g_test_queue_destroy() and qtest_add_abrt_handler() cleanup function for
|
|
* qemu-storage-daemon.
|
|
*/
|
|
static void quit_storage_daemon(void *data)
|
|
{
|
|
QemuStorageDaemonState *qsd = data;
|
|
int wstatus;
|
|
pid_t pid;
|
|
|
|
/*
|
|
* If we were invoked as a g_test_queue_destroy() cleanup function we need
|
|
* to remove the abrt handler to avoid being called again if the code below
|
|
* aborts. Also, we must not leave the abrt handler installed after
|
|
* cleanup.
|
|
*/
|
|
qtest_remove_abrt_handler(data);
|
|
|
|
/* Before quitting storage-daemon, quit qemu to avoid dubious messages */
|
|
qtest_kill_qemu(global_qtest);
|
|
|
|
kill(qsd->pid, SIGTERM);
|
|
pid = waitpid(qsd->pid, &wstatus, 0);
|
|
g_assert_cmpint(pid, ==, qsd->pid);
|
|
if (!WIFEXITED(wstatus)) {
|
|
fprintf(stderr, "%s: expected qemu-storage-daemon to exit\n",
|
|
__func__);
|
|
abort();
|
|
}
|
|
if (WEXITSTATUS(wstatus) != 0) {
|
|
fprintf(stderr, "%s: expected qemu-storage-daemon to exit "
|
|
"successfully, got %d\n",
|
|
__func__, WEXITSTATUS(wstatus));
|
|
abort();
|
|
}
|
|
|
|
g_free(data);
|
|
}
|
|
|
|
static void start_vhost_user_blk(GString *cmd_line, int vus_instances,
|
|
int num_queues)
|
|
{
|
|
const char *vhost_user_blk_bin = qtest_qemu_storage_daemon_binary();
|
|
int i;
|
|
gchar *img_path;
|
|
GString *storage_daemon_command = g_string_new(NULL);
|
|
QemuStorageDaemonState *qsd;
|
|
|
|
g_string_append_printf(storage_daemon_command,
|
|
"exec %s ",
|
|
vhost_user_blk_bin);
|
|
|
|
g_string_append_printf(cmd_line,
|
|
" -object memory-backend-memfd,id=mem,size=256M,share=on "
|
|
" -M memory-backend=mem -m 256M ");
|
|
|
|
for (i = 0; i < vus_instances; i++) {
|
|
int fd;
|
|
char *sock_path = create_listen_socket(&fd);
|
|
|
|
/* create image file */
|
|
img_path = drive_create();
|
|
g_string_append_printf(storage_daemon_command,
|
|
"--blockdev driver=file,node-name=disk%d,filename=%s "
|
|
"--export type=vhost-user-blk,id=disk%d,addr.type=fd,addr.str=%d,"
|
|
"node-name=disk%i,writable=on,num-queues=%d ",
|
|
i, img_path, i, fd, i, num_queues);
|
|
|
|
g_string_append_printf(cmd_line, "-chardev socket,id=char%d,path=%s ",
|
|
i + 1, sock_path);
|
|
}
|
|
|
|
g_test_message("starting vhost-user backend: %s",
|
|
storage_daemon_command->str);
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
/*
|
|
* Close standard file descriptors so tap-driver.pl pipe detects when
|
|
* our parent terminates.
|
|
*/
|
|
close(0);
|
|
close(1);
|
|
open("/dev/null", O_RDONLY);
|
|
open("/dev/null", O_WRONLY);
|
|
|
|
execlp("/bin/sh", "sh", "-c", storage_daemon_command->str, NULL);
|
|
exit(1);
|
|
}
|
|
g_string_free(storage_daemon_command, true);
|
|
|
|
qsd = g_new(QemuStorageDaemonState, 1);
|
|
qsd->pid = pid;
|
|
|
|
/* Make sure qemu-storage-daemon is stopped */
|
|
qtest_add_abrt_handler(quit_storage_daemon, qsd);
|
|
g_test_queue_destroy(quit_storage_daemon, qsd);
|
|
}
|
|
|
|
static void *vhost_user_blk_test_setup(GString *cmd_line, void *arg)
|
|
{
|
|
start_vhost_user_blk(cmd_line, 1, 1);
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* Setup for hotplug.
|
|
*
|
|
* Since vhost-user server only serves one vhost-user client one time,
|
|
* another exprot
|
|
*
|
|
*/
|
|
static void *vhost_user_blk_hotplug_test_setup(GString *cmd_line, void *arg)
|
|
{
|
|
/* "-chardev socket,id=char2" is used for pci_hotplug*/
|
|
start_vhost_user_blk(cmd_line, 2, 1);
|
|
return arg;
|
|
}
|
|
|
|
static void *vhost_user_blk_multiqueue_test_setup(GString *cmd_line, void *arg)
|
|
{
|
|
start_vhost_user_blk(cmd_line, 2, 8);
|
|
return arg;
|
|
}
|
|
|
|
static void register_vhost_user_blk_test(void)
|
|
{
|
|
QOSGraphTestOptions opts = {
|
|
.before = vhost_user_blk_test_setup,
|
|
};
|
|
|
|
/*
|
|
* tests for vhost-user-blk and vhost-user-blk-pci
|
|
* The tests are borrowed from tests/virtio-blk-test.c. But some tests
|
|
* regarding block_resize don't work for vhost-user-blk.
|
|
* vhost-user-blk device doesn't have -drive, so tests containing
|
|
* block_resize are also abandoned,
|
|
* - config
|
|
* - resize
|
|
*/
|
|
qos_add_test("basic", "vhost-user-blk", basic, &opts);
|
|
qos_add_test("indirect", "vhost-user-blk", indirect, &opts);
|
|
qos_add_test("idx", "vhost-user-blk-pci", idx, &opts);
|
|
qos_add_test("nxvirtq", "vhost-user-blk-pci",
|
|
test_nonexistent_virtqueue, &opts);
|
|
|
|
opts.before = vhost_user_blk_hotplug_test_setup;
|
|
qos_add_test("hotplug", "vhost-user-blk-pci", pci_hotplug, &opts);
|
|
|
|
opts.before = vhost_user_blk_multiqueue_test_setup;
|
|
qos_add_test("multiqueue", "vhost-user-blk-pci", multiqueue, &opts);
|
|
}
|
|
|
|
libqos_init(register_vhost_user_blk_test);
|