| # |
| # 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 errno |
| import tempfile |
| import os |
| import struct |
| |
| ctx = None |
| sock = None |
| fds = [] |
| |
| |
| def test_device_get_region_io_fds_setup(): |
| global ctx, sock |
| |
| ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) |
| assert ctx is not 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_setup_region(ctx, index=VFU_PCI_DEV_BAR5_REGION_IDX, size=0x8000, |
| flags=(VFU_REGION_FLAG_RW), 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_fds(): |
| |
| 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) |
| |
| msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, payload, |
| expect=errno.EINVAL) |
| |
| |
| def test_device_get_region_io_fds_region_no_mmap(): |
| |
| payload = vfio_user_region_io_fds_request(argsz=512, flags=0, |
| index=VFU_PCI_DEV_BAR5_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_BAR5_REGION_IDX |
| |
| |
| 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].gpa_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].gpa_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].gpa_offset != ioevents[1].gpa_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 - 0x800, 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) |
| |
| |
| def test_device_get_region_io_fds_invalid_fd(): |
| """Tests that an ioregionfd where fd is -1 is a legitimate ioregionfd.""" |
| ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) |
| assert ctx is not None |
| |
| ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_BAR0_REGION_IDX, size=0x1000, |
| flags=(VFU_REGION_FLAG_RW)) |
| assert ret == 0 |
| |
| ret = vfu_realize_ctx(ctx) |
| assert ret == 0 |
| |
| sock = connect_client(ctx) |
| |
| fds = [] |
| |
| # use valid fd |
| fd = eventfd(0, 0) |
| fds.append(fd) |
| assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, fd, |
| 0 * IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 |
| |
| # use -1 fd |
| assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, -1, |
| 1 * IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 |
| |
| # use another valid fd |
| fd = eventfd(0, 0) |
| fds.append(fd) |
| assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, fd, |
| 2 * IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 |
| |
| # use -1 fd |
| assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, -1, |
| 3 * IOEVENT_SIZE, IOEVENT_SIZE, 0, 0) != -1 |
| |
| # use duplicate valid fd |
| assert vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, fds[1], |
| 4 * 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()) * 5, flags=0, |
| index=VFU_PCI_DEV_BAR0_REGION_IDX, count=0) |
| |
| newfds, ret = msg_fds(ctx, sock, VFIO_USER_DEVICE_GET_REGION_IO_FDS, |
| payload, expect=0) |
| |
| # two unique fds |
| assert len(newfds) == 2 |
| reply, ret = vfio_user_region_io_fds_reply.pop_from_buffer(ret) |
| |
| # five ioregionfds |
| assert reply.count == 5 |
| ioevents = [] |
| for i in range(0, reply.count): |
| ioevent, ret = vfio_user_sub_region_ioeventfd.pop_from_buffer(ret) |
| ioevents.append(ioevent) |
| |
| # TODO this assumes that ioregionfds are returned in the reverse order |
| # they're created. It should be straightforward to compare based on IOVA. |
| assert ioevents[0].fd_index == 0 |
| assert ioevents[0].gpa_offset == 4 * IOEVENT_SIZE |
| assert ioevents[0].size == IOEVENT_SIZE |
| assert fds_are_same(newfds[0], fds[0]) |
| |
| assert ioevents[1].fd_index == UINT32_MAX |
| assert ioevents[1].gpa_offset == 3 * IOEVENT_SIZE |
| assert ioevents[1].size == IOEVENT_SIZE |
| |
| assert ioevents[2].fd_index == 0 |
| assert ioevents[2].gpa_offset == 2 * IOEVENT_SIZE |
| assert ioevents[2].size == IOEVENT_SIZE |
| assert fds_are_same(newfds[0], fds[0]) |
| |
| assert ioevents[3].fd_index == UINT32_MAX |
| assert ioevents[3].gpa_offset == 1 * IOEVENT_SIZE |
| assert ioevents[3].size == IOEVENT_SIZE |
| |
| assert ioevents[4].fd_index == 1 |
| assert ioevents[4].gpa_offset == 0 * IOEVENT_SIZE |
| assert ioevents[4].size == IOEVENT_SIZE |
| assert fds_are_same(newfds[1], fds[1]) |
| |
| # cleanup |
| for fd in fds: |
| os.close(fd) |
| vfu_destroy_ctx(ctx) |
| |
| # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: # |