diff --git a/tests/virtio-9p-test.c b/tests/virtio-9p-test.c index 7273160863376f9367aaadd6aaffbe382cbb3a14..b3ee956cbcb24415060900b32bf0cea198575664 100644 --- a/tests/virtio-9p-test.c +++ b/tests/virtio-9p-test.c @@ -16,6 +16,7 @@ #include "libqos/virtio-pci.h" #include "standard-headers/linux/virtio_ids.h" #include "standard-headers/linux/virtio_pci.h" +#include "hw/9pfs/9p.h" static const char mount_tag[] = "qtest"; @@ -98,6 +99,226 @@ static void pci_config(QVirtIO9P *v9p) g_free(tag); } +#define P9_MAX_SIZE 4096 /* Max size of a T-message or R-message */ + +typedef struct { + QVirtIO9P *v9p; + uint16_t tag; + uint64_t t_msg; + uint32_t t_size; + uint64_t r_msg; + /* No r_size, it is hardcoded to P9_MAX_SIZE */ + size_t t_off; + size_t r_off; +} P9Req; + +static void v9fs_memwrite(P9Req *req, const void *addr, size_t len) +{ + memwrite(req->t_msg + req->t_off, addr, len); + req->t_off += len; +} + +static void v9fs_memskip(P9Req *req, size_t len) +{ + req->r_off += len; +} + +static void v9fs_memrewind(P9Req *req, size_t len) +{ + req->r_off -= len; +} + +static void v9fs_memread(P9Req *req, void *addr, size_t len) +{ + memread(req->r_msg + req->r_off, addr, len); + req->r_off += len; +} + +static void v9fs_uint16_write(P9Req *req, uint16_t val) +{ + uint16_t le_val = cpu_to_le16(val); + + v9fs_memwrite(req, &le_val, 2); +} + +static void v9fs_uint16_read(P9Req *req, uint16_t *val) +{ + v9fs_memread(req, val, 2); + le16_to_cpus(val); +} + +static void v9fs_uint32_write(P9Req *req, uint32_t val) +{ + uint32_t le_val = cpu_to_le32(val); + + v9fs_memwrite(req, &le_val, 4); +} + +static void v9fs_uint32_read(P9Req *req, uint32_t *val) +{ + v9fs_memread(req, val, 4); + le32_to_cpus(val); +} + +/* len[2] string[len] */ +static uint16_t v9fs_string_size(const char *string) +{ + size_t len = strlen(string); + + g_assert_cmpint(len, <=, UINT16_MAX); + + return 2 + len; +} + +static void v9fs_string_write(P9Req *req, const char *string) +{ + int len = strlen(string); + + g_assert_cmpint(len, <=, UINT16_MAX); + + v9fs_uint16_write(req, (uint16_t) len); + v9fs_memwrite(req, string, len); +} + +static void v9fs_string_read(P9Req *req, uint16_t *len, char **string) +{ + uint16_t local_len; + + v9fs_uint16_read(req, &local_len); + if (len) { + *len = local_len; + } + if (string) { + *string = g_malloc(local_len); + v9fs_memread(req, *string, local_len); + } else { + v9fs_memskip(req, local_len); + } +} + + typedef struct { + uint32_t size; + uint8_t id; + uint16_t tag; +} QEMU_PACKED P9Hdr; + +static P9Req *v9fs_req_init(QVirtIO9P *v9p, uint32_t size, uint8_t id, + uint16_t tag) +{ + P9Req *req = g_new0(P9Req, 1); + uint32_t t_size = 7 + size; /* 9P header has well-known size of 7 bytes */ + P9Hdr hdr = { + .size = cpu_to_le32(t_size), + .id = id, + .tag = cpu_to_le16(tag) + }; + + g_assert_cmpint(t_size, <=, P9_MAX_SIZE); + + req->v9p = v9p; + req->t_size = t_size; + req->t_msg = guest_alloc(v9p->qs->alloc, req->t_size); + v9fs_memwrite(req, &hdr, 7); + req->tag = tag; + return req; +} + +static void v9fs_req_send(P9Req *req) +{ + QVirtIO9P *v9p = req->v9p; + uint32_t free_head; + + req->r_msg = guest_alloc(v9p->qs->alloc, P9_MAX_SIZE); + free_head = qvirtqueue_add(v9p->vq, req->t_msg, req->t_size, false, true); + qvirtqueue_add(v9p->vq, req->r_msg, P9_MAX_SIZE, true, false); + qvirtqueue_kick(v9p->dev, v9p->vq, free_head); + req->t_off = 0; +} + +static void v9fs_req_recv(P9Req *req, uint8_t id) +{ + QVirtIO9P *v9p = req->v9p; + P9Hdr hdr; + int i; + + for (i = 0; i < 10; i++) { + qvirtio_wait_queue_isr(v9p->dev, v9p->vq, 1000 * 1000); + + v9fs_memread(req, &hdr, 7); + le32_to_cpus(&hdr.size); + le16_to_cpus(&hdr.tag); + if (hdr.size >= 7) { + break; + } + v9fs_memrewind(req, 7); + } + + g_assert_cmpint(hdr.size, >=, 7); + g_assert_cmpint(hdr.size, <=, P9_MAX_SIZE); + g_assert_cmpint(hdr.tag, ==, req->tag); + + if (hdr.id != id && hdr.id == P9_RLERROR) { + uint32_t err; + v9fs_uint32_read(req, &err); + g_printerr("Received Rlerror (%d) instead of Response %d\n", err, id); + g_assert_not_reached(); + } + g_assert_cmpint(hdr.id, ==, id); +} + +static void v9fs_req_free(P9Req *req) +{ + QVirtIO9P *v9p = req->v9p; + + guest_free(v9p->qs->alloc, req->t_msg); + guest_free(v9p->qs->alloc, req->r_msg); + g_free(req); +} + +/* size[4] Tversion tag[2] msize[4] version[s] */ +static P9Req *v9fs_tversion(QVirtIO9P *v9p, uint32_t msize, const char *version) +{ + P9Req *req = v9fs_req_init(v9p, 4 + v9fs_string_size(version), P9_TVERSION, + P9_NOTAG); + + v9fs_uint32_write(req, msize); + v9fs_string_write(req, version); + v9fs_req_send(req); + return req; +} + +/* size[4] Rversion tag[2] msize[4] version[s] */ +static void v9fs_rversion(P9Req *req, uint16_t *len, char **version) +{ + uint32_t msize; + + v9fs_req_recv(req, P9_RVERSION); + v9fs_uint32_read(req, &msize); + + g_assert_cmpint(msize, ==, P9_MAX_SIZE); + + if (len || version) { + v9fs_string_read(req, len, version); + } + + v9fs_req_free(req); +} + +static void fs_version(QVirtIO9P *v9p) +{ + const char *version = "9P2000.L"; + uint16_t server_len; + char *server_version; + P9Req *req; + + req = v9fs_tversion(v9p, P9_MAX_SIZE, version); + v9fs_rversion(req, &server_len, &server_version); + + g_assert_cmpmem(server_version, server_len, version, strlen(version)); + + g_free(server_version); +} + typedef void (*v9fs_test_fn)(QVirtIO9P *v9p); static void v9fs_run_pci_test(gconstpointer data) @@ -121,6 +342,7 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); v9fs_qtest_pci_add("/virtio/9p/pci/nop", NULL); v9fs_qtest_pci_add("/virtio/9p/pci/config", pci_config); + v9fs_qtest_pci_add("/virtio/9p/pci/fs/version/basic", fs_version); return g_test_run(); }