diff options
-rw-r--r-- | docs/vfio-user.rst | 12 | ||||
-rw-r--r-- | include/libvfio-user.h | 19 | ||||
-rw-r--r-- | include/vfio-user.h | 42 | ||||
-rw-r--r-- | lib/libvfio-user.c | 182 | ||||
-rw-r--r-- | lib/private.h | 12 | ||||
-rw-r--r-- | test/py/libvfio_user.py | 99 | ||||
-rw-r--r-- | test/py/test_device_get_region_io_fds.py | 294 |
7 files changed, 652 insertions, 8 deletions
diff --git a/docs/vfio-user.rst b/docs/vfio-user.rst index e912fb3..9d03f4d 100644 --- a/docs/vfio-user.rst +++ b/docs/vfio-user.rst @@ -322,9 +322,9 @@ usual ``msg_size`` field in the header, not the ``argsz`` field. In a reply, the server sets ``argsz`` field to the size needed for a full payload size. This may be less than the requested maximum size. This may be -larger than the requested maximum size: in that case, the full payload is not -included in the reply, but the ``argsz`` field in the reply indicates the needed -size, allowing a client to allocate a larger buffer for holding the reply before +larger than the requested maximum size: in that case, the payload reply header +is returned, but the ``argsz`` field in the reply indicates the needed size, +allowing a client to allocate a larger buffer for holding the reply before trying again. In addition, during negotiation (see `Version`_), the client and server may @@ -1000,7 +1000,7 @@ Reply * *argsz* is the size of the region IO FD info structure plus the total size of the sub-region array. Thus, each array entry "i" is at offset - i * ((argsz - 32) / count). Note that currently this is 40 bytes for both IO + i * ((argsz - 16) / count). Note that currently this is 40 bytes for both IO FD types, but this is not to be relied on. As elsewhere, this indicates the full reply payload size needed. * *flags* must be zero @@ -1016,8 +1016,8 @@ Note that it is the client's responsibility to verify the requested values (for example, that the requested offset does not exceed the region's bounds). Each sub-region given in the response has one of two possible structures, -depending whether *type* is ``VFIO_USER_IO_FD_TYPE_IOEVENTFD`` or -``VFIO_USER_IO_FD_TYPE_IOREGIONFD``: +depending whether *type* is ``VFIO_USER_IO_FD_TYPE_IOEVENTFD`` (0) or +``VFIO_USER_IO_FD_TYPE_IOREGIONFD`` (1): Sub-Region IO FD info format (ioeventfd) """""""""""""""""""""""""""""""""""""""" diff --git a/include/libvfio-user.h b/include/libvfio-user.h index 148d76e..a4d8105 100644 --- a/include/libvfio-user.h +++ b/include/libvfio-user.h @@ -897,6 +897,25 @@ vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended, bool vfu_sg_is_mappable(vfu_ctx_t *vfu_ctx, dma_sg_t *sg); +/* + * Creates a new ioeventfd at the given setup memory region with @offset, @size, + * @fd, @flags and @datamatch. + * + * Returns 0 on success and -1 on failure with errno set. + * + * @vfu_ctx: the libvfio-user context + * @region_idx: The index of the memory region to set up the ioeventfd + * @fd: the value of the file descriptor + * @offset: The offset into the memory region + * @size: size of the ioeventfd + * @flags: Any flags to set up the ioeventfd + * @datamatch: sets the datamatch value + */ +int +vfu_create_ioeventfd(vfu_ctx_t *vfu_ctx, uint32_t region_idx, int fd, + size_t offset, uint32_t size, uint32_t flags, + uint64_t datamatch); + #ifdef __cplusplus } #endif diff --git a/include/vfio-user.h b/include/vfio-user.h index b5e8962..7439484 100644 --- a/include/vfio-user.h +++ b/include/vfio-user.h @@ -159,6 +159,48 @@ struct vfio_user_irq_info { uint32_t subindex; } __attribute__((packed)); +typedef struct vfio_user_region_io_fds_request { + uint32_t argsz; + uint32_t flags; + uint32_t index; + uint32_t count; +} __attribute__((packed)) vfio_user_region_io_fds_request_t; + +#define VFIO_USER_IO_FD_TYPE_IOEVENTFD 0 +#define VFIO_USER_IO_FD_TYPE_IOREGIONFD 1 + +typedef struct vfio_user_sub_region_ioeventfd { + uint64_t offset; + uint64_t size; + uint32_t fd_index; + uint32_t type; + uint32_t flags; + uint32_t padding; + uint64_t datamatch; +} __attribute__((packed)) vfio_user_sub_region_ioeventfd_t; + +typedef struct vfio_user_sub_region_ioregionfd { + uint64_t offset; + uint64_t size; + uint32_t fd_index; + uint32_t type; + uint32_t flags; + uint32_t padding; + uint64_t user_data; +} __attribute__((packed)) vfio_user_sub_region_ioregionfd_t; + +typedef struct vfio_user_region_io_fds_reply { + uint32_t argsz; + uint32_t flags; + uint32_t index; + uint32_t count; + union sub_region { + struct vfio_user_sub_region_ioeventfd ioeventfd; + struct vfio_user_sub_region_ioregionfd ioregionfd; + } sub_regions[]; +} __attribute__((packed)) vfio_user_region_io_fds_reply_t; + + /* Analogous to vfio_iommu_type1_dirty_bitmap. */ struct vfio_user_dirty_pages { uint32_t argsz; diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c index 3cb30ba..8881dc7 100644 --- a/lib/libvfio-user.c +++ b/lib/libvfio-user.c @@ -48,6 +48,8 @@ #include <sys/socket.h> #include <sys/stat.h> +#include <sys/eventfd.h> + #include "dma.h" #include "irq.h" #include "libvfio-user.h" @@ -460,6 +462,179 @@ handle_device_get_region_info(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) return 0; } +EXPORT int +vfu_create_ioeventfd(vfu_ctx_t *vfu_ctx, uint32_t region_idx, int fd, + size_t offset, uint32_t size, uint32_t flags, + uint64_t datamatch) +{ + vfu_reg_info_t *vfu_reg = &vfu_ctx->reg_info[region_idx]; + + assert(vfu_ctx != NULL); + assert(fd >= 0); + + if (region_idx >= VFU_PCI_DEV_NUM_REGIONS) { + return ERROR_INT(EINVAL); + } + + vfu_reg = &vfu_ctx->reg_info[region_idx]; + + if (offset + size > vfu_reg->size) { + return ERROR_INT(EINVAL); + } + + ioeventfd_t *elem = malloc(sizeof(ioeventfd_t)); + if (elem == NULL) { + return -1; + } + + elem->fd = fd; + elem->offset = offset; + elem->size = size; + elem->flags = flags; + elem->datamatch = datamatch; + LIST_INSERT_HEAD(&vfu_reg->subregions, elem, entry); + + return 0; +} + +static void +free_regions(vfu_ctx_t *vfu_ctx) +{ + size_t index = 0; + + assert(vfu_ctx != NULL); + + for (index = 0; index < VFU_PCI_DEV_NUM_REGIONS; index++) { + vfu_reg_info_t *vfu_reg = &vfu_ctx->reg_info[index]; + + while (!LIST_EMPTY(&vfu_reg->subregions)) { + ioeventfd_t *n = LIST_FIRST(&vfu_reg->subregions); + LIST_REMOVE(n, entry); + free(n); + } + } + free(vfu_ctx->reg_info); +} + +/* + * This function is used to add fd's to the fd return array and gives you back + * the index of the fd that has been added. If the fd is already present it will + * return the index to that duplicate fd to reduce the number of fd's sent. + */ +static int +add_fd_index(int *out_fds, size_t *nr_out_fds, int fd_search) +{ + size_t i = 0; + + assert(out_fds != NULL); + assert(nr_out_fds != NULL); + + for (i = 0; i < *nr_out_fds; i++) { + if (out_fds[i] == fd_search) { + return i; + } + } + + out_fds[*nr_out_fds] = fd_search; + (*nr_out_fds)++; + + return *nr_out_fds - 1; +} + +static int +handle_device_get_region_io_fds(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) +{ + size_t max_sent_sub_regions = 0; + uint subregion_array_size = 0; + vfu_reg_info_t *vfu_reg = NULL; + vfio_user_region_io_fds_reply_t *reply = NULL; + vfio_user_sub_region_ioeventfd_t *ioefd = NULL; + vfio_user_region_io_fds_request_t *req = NULL; + ioeventfd_t *sub_reg = NULL; + size_t nr_sub_reg = 0; + size_t i = 0; + + assert(vfu_ctx != NULL); + assert(msg != NULL); + assert(msg->out_fds == NULL); + + if (msg->in_size != sizeof(vfio_user_region_io_fds_request_t)) { + return ERROR_INT(EINVAL); + } + + req = msg->in_data; + + if (req->flags != 0 || req->count != 0) { + return ERROR_INT(EINVAL); + } + + if (req->index >= vfu_ctx->nr_regions) { + vfu_log(vfu_ctx, LOG_DEBUG, "bad region index %d in get region io fds " + "info", req->index); + return ERROR_INT(EINVAL); + } + + vfu_reg = &vfu_ctx->reg_info[req->index]; + + if (vfu_reg->fd == -1) { + return ERROR_INT(EINVAL); + } + + LIST_FOREACH(sub_reg, &vfu_reg->subregions, entry) { + nr_sub_reg++; + } + + if (req->argsz < sizeof(vfio_user_region_io_fds_reply_t) || + req->argsz > SERVER_MAX_DATA_XFER_SIZE) { + return ERROR_INT(EINVAL); + } + + max_sent_sub_regions = MIN((req->argsz - + sizeof(vfio_user_region_io_fds_reply_t)) / + sizeof(vfio_user_sub_region_ioeventfd_t), + nr_sub_reg); + subregion_array_size = ((max_sent_sub_regions >= nr_sub_reg) ? nr_sub_reg : + 0) * sizeof(vfio_user_sub_region_ioeventfd_t); + msg->out_size = sizeof(vfio_user_region_io_fds_reply_t) + + subregion_array_size; + msg->out_data = calloc(1, msg->out_size); + if (msg->out_data == NULL) { + return -1; + } + reply = msg->out_data; + reply->index = req->index; + reply->count = nr_sub_reg; + reply->flags = 0; + reply->argsz = sizeof(vfio_user_region_io_fds_reply_t) + + nr_sub_reg * + sizeof(vfio_user_sub_region_ioeventfd_t); + + msg->nr_out_fds = 0; + if (req->argsz >= reply->argsz) { + msg->out_fds = calloc(sizeof(int), max_sent_sub_regions); + if (msg->out_fds == NULL) { + return -1; + } + + sub_reg = LIST_FIRST(&vfu_reg->subregions); + for (i = 0; i < max_sent_sub_regions; i++) { + + ioefd = &reply->sub_regions[i].ioeventfd; + ioefd->offset = sub_reg->offset; + ioefd->size = sub_reg->size; + ioefd->fd_index = add_fd_index(msg->out_fds, &msg->nr_out_fds, + sub_reg->fd); + ioefd->type = VFIO_USER_IO_FD_TYPE_IOEVENTFD; + ioefd->flags = sub_reg->flags; + ioefd->datamatch = sub_reg->datamatch; + + sub_reg = LIST_NEXT(sub_reg, entry); + } + } + + return 0; +} + int consume_fd(int *fds, size_t nr_fds, size_t index) { @@ -992,6 +1167,10 @@ exec_command(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) ret = handle_device_get_region_info(vfu_ctx, msg); break; + case VFIO_USER_DEVICE_GET_REGION_IO_FDS: + ret = handle_device_get_region_io_fds(vfu_ctx, msg); + break; + case VFIO_USER_DEVICE_GET_IRQ_INFO: ret = handle_device_get_irq_info(vfu_ctx, msg); break; @@ -1264,7 +1443,7 @@ vfu_destroy_ctx(vfu_ctx_t *vfu_ctx) dma_controller_destroy(vfu_ctx->dma); } free_sparse_mmap_areas(vfu_ctx); - free(vfu_ctx->reg_info); + free_regions(vfu_ctx); free(vfu_ctx->migration); free(vfu_ctx->irqs); free(vfu_ctx); @@ -1328,6 +1507,7 @@ vfu_create_ctx(vfu_trans_t trans, const char *path, int flags, void *pvt, for (i = 0; i < vfu_ctx->nr_regions; i++) { vfu_ctx->reg_info[i].fd = -1; + LIST_INIT(&vfu_ctx->reg_info[i].subregions); } if (vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_ERR_IRQ, 1) == -1) { diff --git a/lib/private.h b/lib/private.h index fb26b6a..93a354b 100644 --- a/lib/private.h +++ b/lib/private.h @@ -34,6 +34,7 @@ #define LIB_VFIO_USER_PRIVATE_H #include <errno.h> +#include <sys/queue.h> #include "common.h" #include "pci_caps.h" @@ -133,6 +134,8 @@ typedef struct { int fd; /* offset of region within fd. */ uint64_t offset; + /* The subregions for ioregionfds and ioeventfds */ + LIST_HEAD(, ioeventfd) subregions; } vfu_reg_info_t; struct pci_dev { @@ -173,6 +176,15 @@ struct vfu_ctx { vfu_dev_type_t dev_type; }; +typedef struct ioeventfd { + uint64_t offset; + uint64_t size; + int32_t fd; + uint32_t flags; + uint64_t datamatch; + LIST_ENTRY(ioeventfd) entry; +} ioeventfd_t; + static inline int ERROR_INT(int err) { diff --git a/test/py/libvfio_user.py b/test/py/libvfio_user.py index bf3e9cd..1ad5db5 100644 --- a/test/py/libvfio_user.py +++ b/test/py/libvfio_user.py @@ -34,6 +34,7 @@ from collections import namedtuple from types import SimpleNamespace import ctypes as c +import array import errno import json import mmap @@ -170,6 +171,10 @@ VFIO_IOMMU_DIRTY_PAGES_FLAG_START = (1 << 0) VFIO_IOMMU_DIRTY_PAGES_FLAG_STOP = (1 << 1) VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP = (1 << 2) +VFIO_USER_IO_FD_TYPE_IOEVENTFD = 0 +VFIO_USER_IO_FD_TYPE_IOREGIONFD = 1 + + # enum vfu_dev_irq_type VFU_DEV_INTX_IRQ = 0 VFU_DEV_MSI_IRQ = 1 @@ -329,6 +334,55 @@ class vfio_region_sparse_mmap_area(Structure): ("size", c.c_uint64), ] +class vfio_user_region_io_fds_request(Structure): + _pack_ = 1 + _fields_ = [ + ("argsz", c.c_uint32), + ("flags", c.c_uint32), + ("index", c.c_uint32), + ("count", c.c_uint32) + ] + +class vfio_user_sub_region_ioeventfd(Structure): + _pack_ = 1 + _fields_ = [ + ("offset", c.c_uint64), + ("size", c.c_uint64), + ("fd_index", c.c_uint32), + ("type", c.c_uint32), + ("flags", c.c_uint32), + ("padding", c.c_uint32), + ("datamatch", c.c_uint64) + ] + +class vfio_user_sub_region_ioregionfd(Structure): + _pack_ = 1 + _fields_ = [ + ("offset", c.c_uint64), + ("size", c.c_uint64), + ("fd_index", c.c_uint32), + ("type", c.c_uint32), + ("flags", c.c_uint32), + ("padding", c.c_uint32), + ("user_data", c.c_uint64) + ] + +class vfio_user_sub_region_io_fd(c.Union): + _pack_ = 1 + _fields_ = [ + ("sub_region_ioeventfd", vfio_user_sub_region_ioeventfd), + ("sub_region_ioregionfd", vfio_user_sub_region_ioregionfd) + ] + +class vfio_user_region_io_fds_reply(Structure): + _pack_ = 1 + _fields_ = [ + ("argsz", c.c_uint32), + ("flags", c.c_uint32), + ("index", c.c_uint32), + ("count", c.c_uint32) + ] + class vfio_user_dma_map(Structure): _pack_ = 1 _fields_ = [ @@ -406,7 +460,7 @@ class dma_sg_t(Structure): ("length", c.c_uint64), ("offset", c.c_uint64), ("writeable", c.c_bool), - ("le_next", c.c_void_p), # FIXME add struct for LIST_ENTRY + ("le_next", c.c_void_p), # FIXME add struct for LIST_ENTRY ("le_prev", c.c_void_p), ] @@ -455,6 +509,11 @@ lib.vfu_map_sg.argtypes = (c.c_void_p, c.POINTER(dma_sg_t), c.POINTER(iovec_t), lib.vfu_unmap_sg.argtypes = (c.c_void_p, c.POINTER(dma_sg_t), c.POINTER(iovec_t), c.c_int) +lib.vfu_create_ioeventfd.argtypes = (c.c_void_p, c.c_uint32, c.c_int, + c.c_size_t, c.c_uint32, c.c_uint32, + c.c_uint64) + + def to_byte(val): """Cast an int to a byte value.""" return val.to_bytes(1, 'little') @@ -519,6 +578,40 @@ def msg(ctx, sock, cmd, payload, expect=0, fds=None): return get_reply(sock, expect=expect) +def get_reply_fds(sock, expect=0): + """Receives a message from a socket and pulls the returned file descriptors + out of the message.""" + fds = array.array("i") + data, ancillary, flags, addr = sock.recvmsg(4096, + socket.CMSG_LEN(64 * fds.itemsize)) + (msg_id, cmd, msg_size, msg_flags, errno) = struct.unpack("HHIII", + data[0:16]) + assert errno == expect + + cmsg_level, cmsg_type, packed_fd = ancillary[0] if len(ancillary) != 0 else \ + (0, 0, []) + unpacked_fds = [] + for i in range(0, len(packed_fd), 4): + [unpacked_fd] = struct.unpack_from("i", packed_fd, offset = i) + unpacked_fds.append(unpacked_fd) + assert len(packed_fd)/4 == len(unpacked_fds) + assert (msg_flags & VFIO_USER_F_TYPE_REPLY) != 0 + return (unpacked_fds, data[16:]) + +def msg_fds(ctx, sock, cmd, payload, expect=0, fds=None): + """Round trip a request and reply to the server. With the server returning + new fds""" + hdr = vfio_user_header(cmd, size=len(payload)) + + if fds: + sock.sendmsg([hdr + payload], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, + struct.pack("I" * len(fds), *fds))]) + else: + sock.send(hdr + payload) + + vfu_run_ctx(ctx) + return get_reply_fds(sock, expect=expect) + def get_pci_header(ctx): ptr = lib.vfu_pci_get_config_space(ctx) return c.cast(ptr, c.POINTER(vfu_pci_hdr_t)).contents @@ -761,4 +854,8 @@ def vfu_map_sg(ctx, sg, iovec, cnt=1, flags=0): def vfu_unmap_sg(ctx, sg, iovec, cnt=1): return lib.vfu_unmap_sg(ctx, sg, iovec, cnt) +def vfu_create_ioeventfd(ctx, region_idx, fd, offset, size, flags, datamatch): + assert ctx != None + + return lib.vfu_create_ioeventfd(ctx, region_idx, fd, offset, size, flags, datamatch) # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: # diff --git a/test/py/test_device_get_region_io_fds.py b/test/py/test_device_get_region_io_fds.py new file mode 100644 index 0000000..ab29dbb --- /dev/null +++ b/test/py/test_device_get_region_io_fds.py @@ -0,0 +1,294 @@ +# +# Copyright (c) 2021 Nutanix Inc. All rights reserved. +# +# Authors: Jack Kelly <jack.kelly@nutanix.com> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Nutanix nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# + +from libvfio_user import * +import ctypes as c +import errno +import tempfile +import os +import struct +import ctypes + +ctx = None +sock = None +fds = [] +IOEVENT_SIZE = 8 + +def test_device_get_region_io_fds_setup(): + global ctx, sock + + ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) + assert ctx != None + + f = tempfile.TemporaryFile() + f.truncate(65536) + + mmap_areas = [ (0x2000, 0x1000), (0x4000, 0x2000) ] + + ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_BAR1_REGION_IDX, size=0x8000, + flags=(VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM), + mmap_areas=mmap_areas, fd=f.fileno(), offset=0x8000) + assert ret == 0 + + f = tempfile.TemporaryFile() + f.truncate(65536) + + mmap_areas = [ (0x2000, 0x1000), (0x4000, 0x2000) ] + + ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_BAR2_REGION_IDX, size=0x8000, + flags=(VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM), + mmap_areas=mmap_areas, fd=f.fileno(), offset=0x8000) + assert ret == 0 + + ret = vfu_realize_ctx(ctx) + assert ret == 0 + + sock = connect_client(ctx) + for i in range(0,6): + tmp = eventfd(0,0) + fds.append(tmp) + assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR2_REGION_IDX, tmp, + i * IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 + +def test_device_get_region_io_fds_bad_flags(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) + + len(vfio_user_sub_region_ioeventfd()) * 5, flags = 1, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 0) + + msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, + expect=errno.EINVAL) + +def test_device_get_region_io_fds_bad_count(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) + + len(vfio_user_sub_region_ioeventfd()) * 5, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 1) + + msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, + expect=errno.EINVAL) + +def test_device_get_region_io_fds_buffer_too_small(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) - 1, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 1) + + msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, + expect=errno.EINVAL) + +def test_device_get_region_io_fds_buffer_too_large(): + + payload = vfio_user_region_io_fds_request(argsz = SERVER_MAX_DATA_XFER_SIZE + + 1, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, + count = 1) + + msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, expect = + errno.EINVAL) + +def test_device_get_region_io_fds_no_regions(): + + payload = vfio_user_region_io_fds_request(argsz = 512, flags = 0, + index = VFU_PCI_DEV_BAR1_REGION_IDX, count = 0) + + ret = msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, expect=0) + + reply, ret = vfio_user_region_io_fds_reply.pop_from_buffer(ret) + + assert reply.argsz == len(vfio_user_region_io_fds_reply()) + assert reply.count == 0 + assert reply.flags == 0 + assert reply.index == VFU_PCI_DEV_BAR1_REGION_IDX + + +def test_device_get_region_io_fds_no_regions_setup(): + + payload = vfio_user_region_io_fds_request(argsz = 512, flags = 0, + index = VFU_PCI_DEV_BAR3_REGION_IDX, count = 0) + + ret = msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, + expect=errno.EINVAL) + +def test_device_get_region_io_fds_region_out_of_range(): + + payload = vfio_user_region_io_fds_request(argsz = 512, flags = 0, + index = 512, count = 0) + + msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, expect = + errno.EINVAL) + +def test_device_get_region_io_fds_fds_read_write(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) + + len(vfio_user_sub_region_ioeventfd()) * 10, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 0) + + newfds, ret = msg_fds(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, + payload, expect=0) + + assert len(newfds) == 6 + _, ret = vfio_user_region_io_fds_reply.pop_from_buffer(ret) + _, ret = vfio_user_sub_region_ioeventfd.pop_from_buffer(ret) + + # Simulating a VM triggering an ioeventfd and the server waking up + + # Client + for i in range(0, len(newfds)): + os.write(newfds[i], c.c_ulonglong(10)) + + # Server + for i in range(0, len(newfds)): + out = os.read(newfds[i], IOEVENT_SIZE) + [out] = struct.unpack("@Q",out) + assert out == 10 + + for i in newfds: + os.close(i) + +def test_device_get_region_io_fds_full(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) + + len(vfio_user_sub_region_ioeventfd()) * 6, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 0) + + newfds, ret = msg_fds(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, + payload, expect=0) + + reply, ret = vfio_user_region_io_fds_reply.pop_from_buffer(ret) + assert len(newfds) == reply.count + ioevents = [] + for i in range(0, reply.count): + ioevent, ret = vfio_user_sub_region_ioeventfd.pop_from_buffer(ret) + ioevents.append(ioevent) + os.write(newfds[ioevent.fd_index], c.c_ulonglong(1)) + + for i in range(0, reply.count): + out = os.read(newfds[ioevents[i].fd_index], ioevent.size) + [out] = struct.unpack("@Q",out) + assert out == 1 + assert ioevents[i].size == IOEVENT_SIZE + assert ioevents[i].offset == 40 - (IOEVENT_SIZE * i) + assert ioevents[i].type == VFIO_USER_IO_FD_TYPE_IOEVENTFD + + for i in newfds: + os.close(i) + +def test_device_get_region_io_fds_fds_read_write_nothing(): + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()), flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 0) + + newfds, ret = msg_fds(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, + payload, expect=0) + + assert len(newfds) == 0 + reply, _ = vfio_user_region_io_fds_request.pop_from_buffer(ret) + assert reply.argsz == len(vfio_user_region_io_fds_reply()) + \ + len(vfio_user_sub_region_ioeventfd()) * 6 + +def test_device_get_region_io_fds_fds_read_write_dupe_fd(): + """ Test here to show that we can return mutliple sub regions with the same + fd_index. fd_index points to the list of fds returned from the socket as + returned by msg_fds. """ + + t = eventfd(0,0) + assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR2_REGION_IDX, t, 6 * + IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 + assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR2_REGION_IDX, t, 7 * + IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 + + payload = vfio_user_region_io_fds_request( + argsz = len(vfio_user_region_io_fds_reply()) + + len(vfio_user_sub_region_ioeventfd()) * 8, flags = 0, + index = VFU_PCI_DEV_BAR2_REGION_IDX, count = 0) + + newfds, ret = msg_fds(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, + payload, expect=0) + reply, ret = vfio_user_region_io_fds_reply.pop_from_buffer(ret) + assert len(newfds) == 7 + assert reply.count == 8 + assert reply.argsz == len(vfio_user_region_io_fds_reply()) + \ + len(vfio_user_sub_region_ioeventfd()) * 8 + + ioevents = [] + for i in range(0, reply.count): + ioevent, ret = vfio_user_sub_region_ioeventfd.pop_from_buffer(ret) + ioevents.append(ioevent) + + for i in range(2, 8): + os.write(newfds[ioevents[i].fd_index], c.c_ulonglong(1)) + + for i in range(2, 8): + out = os.read(newfds[ioevents[i].fd_index], ioevent.size) + [out] = struct.unpack("@Q",out) + assert out == 1 + assert ioevents[i].size == IOEVENT_SIZE + assert ioevents[i].offset == 56 - (IOEVENT_SIZE * i) + assert ioevents[i].type == VFIO_USER_IO_FD_TYPE_IOEVENTFD + + assert ioevents[0].fd_index == ioevents[1].fd_index + assert ioevents[0].offset != ioevents[1].offset + + os.write(newfds[ioevents[0].fd_index], c.c_ulonglong(1)) + + out = os.read(newfds[ioevents[1].fd_index], ioevent.size) + [out] = struct.unpack("@Q",out) + assert out == 1 + + os.write(newfds[ioevents[1].fd_index], c.c_ulonglong(1)) + + out = os.read(newfds[ioevents[0].fd_index], ioevent.size) + [out] = struct.unpack("@Q",out) + assert out == 1 + + os.write(newfds[ioevents[0].fd_index], c.c_ulonglong(1)) + out = os.read(newfds[ioevents[1].fd_index], ioevent.size) + [out] = struct.unpack("@Q",out) + assert out == 1 + + for i in newfds: + os.close(i) + +def test_device_get_region_io_fds_ioeventfd_invalid_size(): + + t = eventfd(0,0) + assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR2_REGION_IDX, t, 0x8000 + -2048, 4096, 0, 0) == -1 + os.close(t) + +def test_device_get_region_info_cleanup(): + for i in fds: + os.close(i) + vfu_destroy_ctx(ctx) |