diff --git a/test/mocks.c b/test/mocks.c index 2ae14b4a..ce3060fd 100644 --- a/test/mocks.c +++ b/test/mocks.c @@ -199,23 +199,6 @@ should_exec_command(vfu_ctx_t *vfu_ctx, uint16_t cmd) return mock(); } -ssize_t -migration_region_access_registers(vfu_ctx_t *vfu_ctx, char *buf, size_t count, - loff_t pos, bool is_write) -{ - if (!is_patched("migration_region_access_registers")) { - return __real_migration_region_access_registers(vfu_ctx, buf, count, - pos, is_write); - } - check_expected(vfu_ctx); - check_expected(buf); - check_expected(count); - check_expected(pos); - check_expected(is_write); - errno = mock(); - return mock(); -} - ssize_t handle_device_state(vfu_ctx_t *vfu_ctx, struct migration *migr, uint32_t device_state, bool notify) { @@ -232,7 +215,8 @@ handle_device_state(vfu_ctx_t *vfu_ctx, struct migration *migr, } void -migr_state_transition(struct migration *migr, enum migr_iter_state state) +migr_state_transition(struct migration *migr, + enum vfio_user_device_mig_state state) { if (!is_patched("migr_state_transition")) { __real_migr_state_transition(migr, state); diff --git a/test/py/libvfio_user.py b/test/py/libvfio_user.py index 8848dbff..e3177d43 100644 --- a/test/py/libvfio_user.py +++ b/test/py/libvfio_user.py @@ -42,7 +42,6 @@ import struct import syslog import copy -import tempfile import sys from resource import getpagesize from math import log2 @@ -118,12 +117,6 @@ VFIO_DMA_UNMAP_FLAG_ALL = (1 << 1) -VFIO_DEVICE_STATE_V1_STOP = (0) -VFIO_DEVICE_STATE_V1_RUNNING = (1 << 0) -VFIO_DEVICE_STATE_V1_SAVING = (1 << 1) -VFIO_DEVICE_STATE_V1_RESUMING = (1 << 2) -VFIO_DEVICE_STATE_MASK = ((1 << 3) - 1) - # libvfio-user defines @@ -170,8 +163,11 @@ def is_32bit(): VFIO_USER_DMA_READ = 11 VFIO_USER_DMA_WRITE = 12 VFIO_USER_DEVICE_RESET = 13 -VFIO_USER_DIRTY_PAGES = 14 -VFIO_USER_MAX = 15 +VFIO_USER_REGION_WRITE_MULTI = 15 +VFIO_USER_DEVICE_FEATURE = 16 +VFIO_USER_MIG_DATA_READ = 17 +VFIO_USER_MIG_DATA_WRITE = 18 +VFIO_USER_MAX = 19 VFIO_USER_F_TYPE_COMMAND = 0 VFIO_USER_F_TYPE_REPLY = 1 @@ -187,8 +183,7 @@ def is_32bit(): VFU_PCI_DEV_ROM_REGION_IDX = 6 VFU_PCI_DEV_CFG_REGION_IDX = 7 VFU_PCI_DEV_VGA_REGION_IDX = 8 -VFU_PCI_DEV_MIGR_REGION_IDX = 9 -VFU_PCI_DEV_NUM_REGIONS = 10 +VFU_PCI_DEV_NUM_REGIONS = 9 VFU_REGION_FLAG_READ = 1 VFU_REGION_FLAG_WRITE = 2 @@ -201,14 +196,42 @@ def is_32bit(): VFIO_DMA_UNMAP_FLAG_GET_DIRTY_BITMAP = (1 << 0) -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) +# enum vfio_user_device_mig_state +VFIO_USER_DEVICE_STATE_ERROR = 0 +VFIO_USER_DEVICE_STATE_STOP = 1 +VFIO_USER_DEVICE_STATE_RUNNING = 2 +VFIO_USER_DEVICE_STATE_STOP_COPY = 3 +VFIO_USER_DEVICE_STATE_RESUMING = 4 +VFIO_USER_DEVICE_STATE_RUNNING_P2P = 5 +VFIO_USER_DEVICE_STATE_PRE_COPY = 6 +VFIO_USER_DEVICE_STATE_PRE_COPY_P2P = 7 + +VFIO_DEVICE_FEATURE_MASK = 0xffff +VFIO_DEVICE_FEATURE_GET = (1 << 16) +VFIO_DEVICE_FEATURE_SET = (1 << 17) +VFIO_DEVICE_FEATURE_PROBE = (1 << 18) + +VFIO_DEVICE_FEATURE_MIGRATION = 1 +VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE = 2 +VFIO_DEVICE_FEATURE_DMA_LOGGING_START = 6 +VFIO_DEVICE_FEATURE_DMA_LOGGING_STOP = 7 +VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT = 8 + +VFIO_MIGRATION_STOP_COPY = (1 << 0) +VFIO_MIGRATION_P2P = (1 << 1) +VFIO_MIGRATION_PRE_COPY = (1 << 2) VFIO_USER_IO_FD_TYPE_IOEVENTFD = 0 VFIO_USER_IO_FD_TYPE_IOREGIONFD = 1 VFIO_USER_IO_FD_TYPE_IOEVENTFD_SHADOW = 2 +# enum vfu_migr_state_t +VFU_MIGR_STATE_STOP = 0 +VFU_MIGR_STATE_RUNNING = 1 +VFU_MIGR_STATE_STOP_AND_COPY = 2 +VFU_MIGR_STATE_PRE_COPY = 3 +VFU_MIGR_STATE_RESUME = 4 + # enum vfu_dev_irq_type VFU_DEV_INTX_IRQ = 0 @@ -233,7 +256,7 @@ def is_32bit(): VFU_CAP_FLAG_CALLBACK = (1 << 1) VFU_CAP_FLAG_READONLY = (1 << 2) -VFU_MIGR_CALLBACKS_VERS = 1 +VFU_MIGR_CALLBACKS_VERS = 2 SOCK_PATH = b"/tmp/vfio-user.sock.%d" % os.getpid() @@ -508,14 +531,6 @@ def __copy__(self): return result -class vfio_user_dirty_pages(Structure): - _pack_ = 1 - _fields_ = [ - ("argsz", c.c_uint32), - ("flags", c.c_uint32) - ] - - class vfio_user_bitmap(Structure): _pack_ = 1 _fields_ = [ @@ -534,24 +549,73 @@ class vfio_user_bitmap_range(Structure): transition_cb_t = c.CFUNCTYPE(c.c_int, c.c_void_p, c.c_int, use_errno=True) -get_pending_bytes_cb_t = c.CFUNCTYPE(c.c_uint64, c.c_void_p) -prepare_data_cb_t = c.CFUNCTYPE(c.c_void_p, c.POINTER(c.c_uint64), - c.POINTER(c.c_uint64)) -read_data_cb_t = c.CFUNCTYPE(c.c_ssize_t, c.c_void_p, c.c_void_p, - c.c_uint64, c.c_uint64) -write_data_cb_t = c.CFUNCTYPE(c.c_ssize_t, c.c_void_p, c.c_uint64) -data_written_cb_t = c.CFUNCTYPE(c.c_int, c.c_void_p, c.c_uint64) +read_data_cb_t = c.CFUNCTYPE(c.c_ssize_t, c.c_void_p, c.c_void_p, c.c_uint64) +write_data_cb_t = c.CFUNCTYPE(c.c_ssize_t, c.c_void_p, c.c_void_p, c.c_uint64) class vfu_migration_callbacks_t(Structure): _fields_ = [ ("version", c.c_int), ("transition", transition_cb_t), - ("get_pending_bytes", get_pending_bytes_cb_t), - ("prepare_data", prepare_data_cb_t), ("read_data", read_data_cb_t), ("write_data", write_data_cb_t), - ("data_written", data_written_cb_t), + ] + + +class vfio_user_device_feature(Structure): + _pack_ = 1 + _fields_ = [ + ("argsz", c.c_uint32), + ("flags", c.c_uint32) + ] + + +class vfio_user_device_feature_migration(Structure): + _pack_ = 1 + _fields_ = [ + ("flags", c.c_uint64) + ] + + +class vfio_user_device_feature_mig_state(Structure): + _pack_ = 1 + _fields_ = [ + ("device_state", c.c_uint32), + ("data_fd", c.c_uint32), + ] + + +class vfio_user_device_feature_dma_logging_control(Structure): + _pack_ = 1 + _fields_ = [ + ("page_size", c.c_uint64), + ("num_ranges", c.c_uint32), + ("reserved", c.c_uint32), + ] + + +class vfio_user_device_feature_dma_logging_range(Structure): + _pack_ = 1 + _fields_ = [ + ("iova", c.c_uint64), + ("length", c.c_uint64), + ] + + +class vfio_user_device_feature_dma_logging_report(Structure): + _pack_ = 1 + _fields_ = [ + ("iova", c.c_uint64), + ("length", c.c_uint64), + ("page_size", c.c_uint64) + ] + + +class vfio_user_mig_data(Structure): + _pack_ = 1 + _fields_ = [ + ("argsz", c.c_uint32), + ("size", c.c_uint32) ] @@ -570,17 +634,6 @@ def __str__(self): hex(self.offset), self.writeable) -class vfio_user_migration_info(Structure): - _pack_ = 1 - _fields_ = [ - ("device_state", c.c_uint32), - ("reserved", c.c_uint32), - ("pending_bytes", c.c_uint64), - ("data_offset", c.c_uint64), - ("data_size", c.c_uint64), - ] - - # # Util functions # @@ -624,7 +677,7 @@ class vfio_user_migration_info(Structure): lib.vfu_setup_device_dma.argtypes = (c.c_void_p, vfu_dma_register_cb_t, vfu_dma_unregister_cb_t) lib.vfu_setup_device_migration_callbacks.argtypes = (c.c_void_p, - c.POINTER(vfu_migration_callbacks_t), c.c_uint64) + c.POINTER(vfu_migration_callbacks_t)) lib.dma_sg_size.restype = (c.c_size_t) lib.vfu_addr_to_sgl.argtypes = (c.c_void_p, c.c_void_p, c.c_size_t, c.POINTER(dma_sg_t), c.c_size_t, c.c_int) @@ -678,13 +731,23 @@ def connect_sock(): return sock -def connect_client(ctx): +def connect_client(ctx, max_data_xfer_size=None): sock = connect_sock() - json = b'{ "capabilities": { "max_msg_fds": 8 } }' + caps = { + "capabilities": { + "max_msg_fds": 8 + } + } + + if max_data_xfer_size is not None: + caps["capabilities"]["max_data_xfer_size"] = max_data_xfer_size + + caps_json = json.dumps(caps).encode("utf-8") + # struct vfio_user_version - payload = struct.pack("HH%dsc" % len(json), LIBVFIO_USER_MAJOR, - LIBVFIO_USER_MINOR, json, b'\0') + payload = struct.pack("HH%dsc" % len(caps_json), LIBVFIO_USER_MAJOR, + LIBVFIO_USER_MINOR, caps_json, b'\0') hdr = vfio_user_header(VFIO_USER_VERSION, size=len(payload)) sock.send(hdr + payload) vfu_attach_ctx(ctx, expect=0) @@ -929,18 +992,6 @@ def prepare_ctx_for_dma(dma_register=__dma_register, ret = vfu_setup_device_reset_cb(ctx, reset) assert ret == 0 - f = tempfile.TemporaryFile() - migr_region_size = 2 << PAGE_SHIFT - f.truncate(migr_region_size) - - mmap_areas = [(PAGE_SIZE, PAGE_SIZE)] - - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, - size=migr_region_size, - flags=VFU_REGION_FLAG_RW, mmap_areas=mmap_areas, - fd=f.fileno()) - assert ret == 0 - if migration_callbacks: ret = vfu_setup_device_migration_callbacks(ctx) assert ret == 0 @@ -950,6 +1001,18 @@ def prepare_ctx_for_dma(dma_register=__dma_register, return ctx + +def transition_to_state(ctx, sock, state, expect=0, rsp=True, busy=False): + feature = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_mig_state()), + flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE + ) + payload = vfio_user_device_feature_mig_state(device_state=state) + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload), + expect=expect, rsp=rsp, busy=busy) + + # # Library wrappers # @@ -1136,24 +1199,6 @@ def __migr_trans_cb(ctx, state): return migr_trans_cb(ctx, state) -def migr_get_pending_bytes_cb(ctx): - pass - - -@get_pending_bytes_cb_t -def __migr_get_pending_bytes_cb(ctx): - return migr_get_pending_bytes_cb(ctx) - - -def migr_prepare_data_cb(ctx, offset, size): - pass - - -@prepare_data_cb_t -def __migr_prepare_data_cb(ctx, offset, size): - return migr_prepare_data_cb(ctx, offset, size) - - def migr_read_data_cb(ctx, buf, count, offset): pass @@ -1172,29 +1217,17 @@ def __migr_write_data_cb(ctx, buf, count, offset): return migr_write_data_cb(ctx, buf, count, offset) -def migr_data_written_cb(ctx, count): - pass - - -@data_written_cb_t -def __migr_data_written_cb(ctx, count): - return migr_data_written_cb(ctx, count) - - -def vfu_setup_device_migration_callbacks(ctx, cbs=None, offset=PAGE_SIZE): +def vfu_setup_device_migration_callbacks(ctx, cbs=None): assert ctx is not None if not cbs: cbs = vfu_migration_callbacks_t() cbs.version = VFU_MIGR_CALLBACKS_VERS cbs.transition = __migr_trans_cb - cbs.get_pending_bytes = __migr_get_pending_bytes_cb - cbs.prepare_data = __migr_prepare_data_cb cbs.read_data = __migr_read_data_cb cbs.write_data = __migr_write_data_cb - cbs.data_written = __migr_data_written_cb - return lib.vfu_setup_device_migration_callbacks(ctx, cbs, offset) + return lib.vfu_setup_device_migration_callbacks(ctx, cbs) def dma_sg_size(): @@ -1244,4 +1277,30 @@ def fds_are_same(fd1: int, fd2: int) -> bool: return s1.st_dev == s2.st_dev and s1.st_ino == s2.st_ino +def get_bitmap_size(size: int, pgsize: int) -> int: + """ + Returns the size, in bytes, of the bitmap that represents the given range + with the given page size. + """ + + nr_pages = (size // pgsize) + (1 if size % pgsize != 0 else 0) + return ((nr_pages + 63) & ~63) // 8 + + +get_errno_loc = libc.__errno_location +get_errno_loc.restype = c.POINTER(c.c_int) + + +def set_real_errno(errno: int): + """ + ctypes's errno is an internal value that only updates the real value when + the foreign function call returns. In callbacks, however, this doesn't + happen, so `c.set_errno` doesn't propagate in time. In this case we need to + manually set the real errno. + """ + + c.set_errno(errno) # set internal errno so `c.get_errno` gives right value + get_errno_loc()[0] = errno # set real errno + + # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: # diff --git a/test/py/test_device_get_region_info.py b/test/py/test_device_get_region_info.py index 62d67406..9e2ffbbf 100644 --- a/test/py/test_device_get_region_info.py +++ b/test/py/test_device_get_region_info.py @@ -78,14 +78,6 @@ def test_device_get_region_info_setup(): mmap_areas=mmap_areas, fd=f.fileno(), offset=0x0) assert ret == 0 - f = tempfile.TemporaryFile() - f.truncate(migr_region_size) - - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, - size=migr_region_size, flags=VFU_REGION_FLAG_RW, - mmap_areas=migr_mmap_areas, fd=f.fileno()) - assert ret == 0 - ret = vfu_realize_ctx(ctx) assert ret == 0 @@ -206,44 +198,6 @@ def test_device_get_region_info_caps(): disconnect_client(ctx, sock) -def test_device_get_region_info_migr(): - global sock - - sock = connect_client(ctx) - - payload = vfio_region_info(argsz=80, flags=0, - index=VFU_PCI_DEV_MIGR_REGION_IDX, cap_offset=0, - size=0, offset=0) - payload = bytes(payload) + b'\0' * (80 - 32) - - result = msg(ctx, sock, VFIO_USER_DEVICE_GET_REGION_INFO, payload) - - info, result = vfio_region_info.pop_from_buffer(result) - mcap, result = vfio_region_info_cap_type.pop_from_buffer(result) - cap, result = vfio_region_info_cap_sparse_mmap.pop_from_buffer(result) - area, result = vfio_region_sparse_mmap_area.pop_from_buffer(result) - - assert info.argsz == 80 - assert info.cap_offset == 32 - - assert mcap.id == VFIO_REGION_INFO_CAP_TYPE - assert mcap.version == 1 - assert mcap.next == 48 - assert mcap.type == VFIO_REGION_TYPE_MIGRATION - assert mcap.subtype == VFIO_REGION_SUBTYPE_MIGRATION - - assert cap.id == VFIO_REGION_INFO_CAP_SPARSE_MMAP - assert cap.version == 1 - assert cap.next == 0 - assert cap.nr_areas == len(migr_mmap_areas) == 1 - - assert area.offset == migr_mmap_areas[0][0] - assert area.size == migr_mmap_areas[0][1] - - # skip reading the SCM_RIGHTS - disconnect_client(ctx, sock) - - def test_device_get_region_info_cleanup(): vfu_destroy_ctx(ctx) diff --git a/test/py/test_device_get_region_info_zero_size.py b/test/py/test_device_get_region_info_zero_size.py index 42a6ae0b..f7927564 100644 --- a/test/py/test_device_get_region_info_zero_size.py +++ b/test/py/test_device_get_region_info_zero_size.py @@ -52,27 +52,26 @@ def test_device_get_region_info_zero_sized_region(): global sock - for index in [VFU_PCI_DEV_BAR1_REGION_IDX, VFU_PCI_DEV_MIGR_REGION_IDX]: - payload = vfio_region_info(argsz=argsz, flags=0, - index=index, cap_offset=0, - size=0, offset=0) - - hdr = vfio_user_header(VFIO_USER_DEVICE_GET_REGION_INFO, - size=len(payload)) - sock.send(hdr + payload) - vfu_run_ctx(ctx) - result = get_reply(sock) - - assert len(result) == argsz - - info, _ = vfio_region_info.pop_from_buffer(result) - - assert info.argsz == argsz - assert info.flags == 0 - assert info.index == index - assert info.cap_offset == 0 - assert info.size == 0 - assert info.offset == 0 + payload = vfio_region_info(argsz=argsz, flags=0, + index=VFU_PCI_DEV_BAR1_REGION_IDX, cap_offset=0, + size=0, offset=0) + + hdr = vfio_user_header(VFIO_USER_DEVICE_GET_REGION_INFO, + size=len(payload)) + sock.send(hdr + payload) + vfu_run_ctx(ctx) + result = get_reply(sock) + + assert len(result) == argsz + + info, _ = vfio_region_info.pop_from_buffer(result) + + assert info.argsz == argsz + assert info.flags == 0 + assert info.index == VFU_PCI_DEV_BAR1_REGION_IDX + assert info.cap_offset == 0 + assert info.size == 0 + assert info.offset == 0 vfu_destroy_ctx(ctx) diff --git a/test/py/test_dirty_pages.py b/test/py/test_dirty_pages.py index b3d4e342..c85bcce0 100644 --- a/test/py/test_dirty_pages.py +++ b/test/py/test_dirty_pages.py @@ -69,16 +69,6 @@ def test_dirty_pages_setup(): ret = vfu_setup_device_dma(ctx, dma_register, dma_unregister) assert ret == 0 - f = tempfile.TemporaryFile() - f.truncate(2 << PAGE_SHIFT) - - mmap_areas = [(PAGE_SIZE, PAGE_SIZE)] - - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, - size=2 << PAGE_SHIFT, flags=VFU_REGION_FLAG_RW, - mmap_areas=mmap_areas, fd=f.fileno()) - assert ret == 0 - ret = vfu_realize_ctx(ctx) assert ret == 0 @@ -100,59 +90,51 @@ def test_dirty_pages_setup(): msg(ctx, sock, VFIO_USER_DMA_MAP, payload) -def test_dirty_pages_short_write(): - payload = struct.pack("I", 8) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) - - -def test_dirty_pages_bad_argsz(): - payload = vfio_user_dirty_pages(argsz=4, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_START) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) - - -def test_dirty_pages_start_no_migration(): - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_START) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.ENOTSUP) +def test_setup_migration(): + ret = vfu_setup_device_migration_callbacks(ctx) + assert ret == 0 -def test_setup_migr_region(): - ret = vfu_setup_device_migration_callbacks(ctx, offset=PAGE_SIZE) - assert ret == 0 +def start_logging(addr=None, length=None, page_size=PAGE_SIZE, expect=0): + """ + Start logging dirty writes. + If a region and page size are specified, they will be sent to the server to + start logging. Otherwise, all regions will be logged and the default page + size will be used. -def test_dirty_pages_start_bad_flags(): - # - # This is a little cheeky, after vfu_realize_ctx(), but it works at the - # moment. - # - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=(VFIO_IOMMU_DIRTY_PAGES_FLAG_START | - VFIO_IOMMU_DIRTY_PAGES_FLAG_STOP)) + Note: in the current implementation, all regions are logged whether or not + you specify a region, as the additional constraint of only logging a + certain region is considered an optimisation and is not yet implemented. + """ - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) + if addr is not None: + ranges = vfio_user_device_feature_dma_logging_range( + iova=addr, + length=length + ) + num_ranges = 1 + else: + ranges = bytearray() + num_ranges = 0 - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=(VFIO_IOMMU_DIRTY_PAGES_FLAG_START | - VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP)) + feature = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_dma_logging_control()) + + len(ranges), + flags=VFIO_DEVICE_FEATURE_DMA_LOGGING_START | VFIO_DEVICE_FEATURE_SET) - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) + payload = vfio_user_device_feature_dma_logging_control( + page_size=page_size, + num_ranges=num_ranges, + reserved=0) + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, + bytes(feature) + bytes(payload) + bytes(ranges), expect=expect) -def start_logging(): - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_START) - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) +def test_dirty_pages_start_zero_pgsize(): + start_logging(page_size=0, expect=errno.EINVAL) def test_dirty_pages_start(): @@ -161,157 +143,64 @@ def test_dirty_pages_start(): start_logging() -def test_dirty_pages_get_short_read(): - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) - - -# -# This should in fact work; update when it does. -# -def test_dirty_pages_get_sub_range(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - br = vfio_user_bitmap_range(iova=0x11 << PAGE_SHIFT, size=PAGE_SIZE, - bitmap=bitmap) - - payload = bytes(dirty_pages) + bytes(br) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.ENOTSUP) +def test_dirty_pages_start_different_pgsize(): + """ + Once we've started logging with page size PAGE_SIZE, any request to start + logging at a different page size should be rejected. + """ + start_logging(page_size=PAGE_SIZE >> 1, expect=errno.EINVAL) + start_logging(page_size=PAGE_SIZE << 1, expect=errno.EINVAL) -def test_dirty_pages_get_bad_page_size(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=2 << PAGE_SHIFT, size=8) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) - payload = bytes(dirty_pages) + bytes(br) +def get_dirty_page_bitmap(addr=0x10 << PAGE_SHIFT, length=0x10 << PAGE_SHIFT, + page_size=PAGE_SIZE, expect=0): + """ + Get the dirty page bitmap from the server for the given region and page + size as a 64-bit integer. This function only works for bitmaps that fit + within a 64-bit integer because that's what it returns. + """ - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) + bitmap_size = get_bitmap_size(length, page_size) + assert bitmap_size == 8 -def test_dirty_pages_get_bad_bitmap_size(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=1) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) + argsz = len(vfio_user_device_feature()) + \ + len(vfio_user_device_feature_dma_logging_report()) + \ + bitmap_size - payload = bytes(dirty_pages) + bytes(br) + feature = vfio_user_device_feature( + argsz=argsz, + flags=VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT | VFIO_DEVICE_FEATURE_GET + ) - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) + report = vfio_user_device_feature_dma_logging_report( + iova=addr, + length=length, + page_size=page_size + ) + payload = bytes(feature) + bytes(report) -def test_dirty_pages_get_bad_argsz(): - dirty_pages = vfio_user_dirty_pages(argsz=SERVER_MAX_DATA_XFER_SIZE + 8, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, - size=SERVER_MAX_DATA_XFER_SIZE + 8) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) + result = msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=expect) - payload = bytes(dirty_pages) + bytes(br) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) - - -def test_dirty_pages_get_short_reply(): - dirty_pages = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) - - payload = bytes(dirty_pages) + bytes(br) - - result = msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) - - assert len(result) == len(vfio_user_dirty_pages()) - - dirty_pages, _ = vfio_user_dirty_pages.pop_from_buffer(result) - - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - - assert dirty_pages.argsz == argsz - assert dirty_pages.flags == VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP - - -def test_get_dirty_page_bitmap_unmapped(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - br = vfio_user_bitmap_range(iova=0x40 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) - - payload = bytes(dirty_pages) + bytes(br) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, - expect=errno.EINVAL) - - -def test_dirty_pages_get_unmodified(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) - - payload = bytes(dirty_pages) + bytes(br) - - result = msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) + if expect != 0: + return assert len(result) == argsz - dirty_pages, result = vfio_user_dirty_pages.pop_from_buffer(result) - - assert dirty_pages.argsz == argsz - assert dirty_pages.flags == VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP - - br, result = vfio_user_bitmap_range.pop_from_buffer(result) - - assert br.iova == 0x10 << PAGE_SHIFT - assert br.size == 0x10 << PAGE_SHIFT + _, result = vfio_user_device_feature.pop_from_buffer(result) + _, result = \ + vfio_user_device_feature_dma_logging_report.pop_from_buffer(result) - assert br.bitmap.pgsize == PAGE_SIZE - assert br.bitmap.size == 8 + assert len(result) == bitmap_size + return struct.unpack("Q", result)[0] -def get_dirty_page_bitmap(): - argsz = len(vfio_user_dirty_pages()) + len(vfio_user_bitmap_range()) + 8 - - dirty_pages = vfio_user_dirty_pages(argsz=argsz, - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - br = vfio_user_bitmap_range(iova=0x10 << PAGE_SHIFT, - size=0x10 << PAGE_SHIFT, bitmap=bitmap) - - payload = bytes(dirty_pages) + bytes(br) - - result = msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) - - _, result = vfio_user_dirty_pages.pop_from_buffer(result) - _, result = vfio_user_bitmap_range.pop_from_buffer(result) - - assert len(result) == 8 - return struct.unpack("Q", result)[0] +def test_dirty_pages_get_unmodified(): + bitmap = get_dirty_page_bitmap() + assert bitmap == 0 sg3 = None @@ -374,6 +263,27 @@ def test_dirty_pages_get_modified(): bitmap = get_dirty_page_bitmap() assert bitmap == 0b0000001111000001 + # check dirty bitmap is correctly extended when we give a smaller page size + vfu_sgl_put(ctx, sg1, iovec1) + vfu_sgl_put(ctx, sg4, iovec4) + bitmap = get_dirty_page_bitmap(page_size=PAGE_SIZE >> 1) + assert bitmap == 0b00000000000011111111000000000011 + + # check dirty bitmap is correctly shortened when we give a larger page size + vfu_sgl_put(ctx, sg1, iovec1) + vfu_sgl_put(ctx, sg4, iovec4) + bitmap = get_dirty_page_bitmap(page_size=PAGE_SIZE << 1) + assert bitmap == 0b00011001 + + # check dirty bitmap is correctly shortened when we give a page size that + # is so large that one bit corresponds to multiple bytes in the raw bitmap + vfu_sgl_put(ctx, sg1, iovec1) + vfu_sgl_put(ctx, sg4, iovec4) + bitmap = get_dirty_page_bitmap(page_size=PAGE_SIZE << 4) + assert bitmap == 0b1 + bitmap = get_dirty_page_bitmap(page_size=PAGE_SIZE << 4) + assert bitmap == 0b0 + # after another two puts, should just be one dirty page vfu_sgl_put(ctx, sg2, iovec2) vfu_sgl_put(ctx, sg3, iovec3) @@ -427,72 +337,76 @@ def test_dirty_pages_get_modified(): assert bitmap == 0b010000000000000000001100 -def test_dirty_pages_stop(): - # FIXME we have a memory leak as we don't free dirty bitmaps when - # destroying the context. - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_STOP) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) +def test_dirty_pages_invalid_arguments(): + # Failed to translate + get_dirty_page_bitmap(addr=0xdeadbeef, expect=errno.ENOENT) + # Does not exactly match a region (libvfio-user limitation) + get_dirty_page_bitmap(addr=(0x10 << PAGE_SHIFT) + 1, + length=(0x20 << PAGE_SHIFT) - 1, + expect=errno.ENOTSUP) -def test_dirty_pages_start_with_quiesce(): - global quiesce_errno + # Invalid requested bitmap size + get_dirty_page_bitmap(page_size=1 << 24, expect=errno.EINVAL) - quiesce_errno = errno.EBUSY + # Region not mapped + get_dirty_page_bitmap(addr=0x40 << PAGE_SHIFT, expect=errno.EINVAL) - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_START) - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, rsp=False, busy=True) +def stop_logging(addr=None, length=None): + if addr is not None: + ranges = vfio_user_device_feature_dma_logging_range( + iova=addr, + length=length + ) + else: + ranges = [] - ret = vfu_device_quiesced(ctx, 0) - assert ret == 0 + feature = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_dma_logging_control()) + + len(ranges), + flags=VFIO_DEVICE_FEATURE_DMA_LOGGING_STOP | VFIO_DEVICE_FEATURE_SET) - # now should be able to get the reply - get_reply(sock, expect=0) + payload = vfio_user_device_feature_dma_logging_control( + page_size=PAGE_SIZE, + num_ranges=(1 if addr is not None else 0), + reserved=0) - quiesce_errno = 0 + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, + bytes(feature) + bytes(payload) + bytes(ranges)) -def test_dirty_pages_bitmap_with_quiesce(): - global quiesce_errno - - quiesce_errno = errno.EBUSY +def test_dirty_pages_stop(): + stop_logging() - ret, sg1 = vfu_addr_to_sgl(ctx, dma_addr=0x10 << PAGE_SHIFT, - length=PAGE_SIZE) - assert ret == 1 - iovec1 = iovec_t() - ret = vfu_sgl_get(ctx, sg1, iovec1) - assert ret == 0 - vfu_sgl_put(ctx, sg1, iovec1) - bitmap = get_dirty_page_bitmap() - assert bitmap == 0b0000000000000001 +def test_dirty_pages_cleanup(): + disconnect_client(ctx, sock) + vfu_destroy_ctx(ctx) -def test_dirty_pages_stop_with_quiesce(): - global quiesce_errno +def test_dirty_pages_uninitialised_dma(): + global ctx, sock - quiesce_errno = errno.EBUSY + ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) + assert ctx is not None - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_STOP) + ret = vfu_pci_init(ctx) + assert ret == 0 - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload, rsp=False, busy=True) + vfu_setup_device_quiesce_cb(ctx, quiesce_cb=quiesce_cb) - ret = vfu_device_quiesced(ctx, 0) + ret = vfu_realize_ctx(ctx) assert ret == 0 - # now should be able to get the reply - get_reply(sock, expect=0) - - quiesce_errno = 0 + sock = connect_client(ctx) + start_logging(expect=errno.EINVAL) + get_dirty_page_bitmap(expect=errno.EINVAL) -def test_dirty_pages_cleanup(): disconnect_client(ctx, sock) + vfu_destroy_ctx(ctx) # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: diff --git a/test/py/test_dma_unmap.py b/test/py/test_dma_unmap.py index 063dedcf..f9529fb7 100644 --- a/test/py/test_dma_unmap.py +++ b/test/py/test_dma_unmap.py @@ -113,26 +113,6 @@ def test_dma_unmap_dirty_not_tracking(): expect=errno.EINVAL) -def test_dma_unmap_dirty_not_mapped(): - - setup_dma_regions([(PAGE_SIZE, PAGE_SIZE)]) - vfu_setup_device_migration_callbacks(ctx, offset=PAGE_SIZE) - payload = vfio_user_dirty_pages(argsz=len(vfio_user_dirty_pages()), - flags=VFIO_IOMMU_DIRTY_PAGES_FLAG_START) - - msg(ctx, sock, VFIO_USER_DIRTY_PAGES, payload) - - argsz = len(vfio_user_dma_unmap()) + len(vfio_user_bitmap()) + 8 - unmap = vfio_user_dma_unmap(argsz=argsz, - flags=VFIO_DMA_UNMAP_FLAG_GET_DIRTY_BITMAP, addr=PAGE_SIZE, - size=PAGE_SIZE) - bitmap = vfio_user_bitmap(pgsize=PAGE_SIZE, size=8) - payload = bytes(unmap) + bytes(bitmap) + bytes(8) - - msg(ctx, sock, VFIO_USER_DMA_UNMAP, payload, - expect=errno.EINVAL) - - def test_dma_unmap_invalid_flags(): setup_dma_regions() diff --git a/test/py/test_migration.py b/test/py/test_migration.py index 614a6156..b320e768 100644 --- a/test/py/test_migration.py +++ b/test/py/test_migration.py @@ -1,7 +1,8 @@ # -# Copyright (c) 2021 Nutanix Inc. All rights reserved. +# Copyright (c) 2023 Nutanix Inc. All rights reserved. # # Authors: Thanos Makatos +# William Henderson # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -28,25 +29,143 @@ # from libvfio_user import * -import ctypes as c +from collections import deque +import ctypes import errno -from unittest.mock import patch ctx = None -sock = 0 +sock = None +current_state = None # the current migration state on the server +path = [] # the server transition path (each transition appends the new state) -def setup_function(function): +read_data = None +write_data = None +callbacks_errno = 0 + + +STATES = { + VFIO_USER_DEVICE_STATE_STOP, + VFIO_USER_DEVICE_STATE_RUNNING, + VFIO_USER_DEVICE_STATE_STOP_COPY, + VFIO_USER_DEVICE_STATE_RESUMING, + VFIO_USER_DEVICE_STATE_PRE_COPY +} + + +UNREACHABLE_STATES = { + VFIO_USER_DEVICE_STATE_ERROR, + VFIO_USER_DEVICE_STATE_PRE_COPY_P2P, + VFIO_USER_DEVICE_STATE_RUNNING_P2P +} + + +VFU_TO_VFIO_MIGR_STATE = { + VFU_MIGR_STATE_STOP: VFIO_USER_DEVICE_STATE_STOP, + VFU_MIGR_STATE_RUNNING: VFIO_USER_DEVICE_STATE_RUNNING, + VFU_MIGR_STATE_STOP_AND_COPY: VFIO_USER_DEVICE_STATE_STOP_COPY, + VFU_MIGR_STATE_RESUME: VFIO_USER_DEVICE_STATE_RESUMING, + VFU_MIGR_STATE_PRE_COPY: VFIO_USER_DEVICE_STATE_PRE_COPY +} + + +# Set a very small maximum transfer size for later tests. +MAX_DATA_XFER_SIZE = 4 + + +@transition_cb_t +def migr_trans_cb(_ctx, state): + global current_state, path + + if callbacks_errno != 0: + set_real_errno(callbacks_errno) + return -1 + + if state in VFU_TO_VFIO_MIGR_STATE: + state = VFU_TO_VFIO_MIGR_STATE[state] + else: + assert False + + current_state = state + + path.append(state) + + return 0 + + +@read_data_cb_t +def migr_read_data_cb(_ctx, buf, count): + global read_data + + if callbacks_errno != 0: + set_real_errno(callbacks_errno) + return -1 + + length = min(count, len(read_data)) + ctypes.memmove(buf, read_data, length) + read_data = None + + return length + + +@write_data_cb_t +def migr_write_data_cb(_ctx, buf, count): + global write_data + + if callbacks_errno != 0: + set_real_errno(callbacks_errno) + return -1 + + write_data = bytes(count) + ctypes.memmove(write_data, buf, count) + + return count + + +def setup_fail_callbacks(errno): + global callbacks_errno + callbacks_errno = errno + + +def teardown_fail_callbacks(): + global callbacks_errno + callbacks_errno = 0 + c.set_errno(0) + + +def teardown_function(function): + teardown_fail_callbacks() + + +def transition_to_migr_state(state, expect=0, rsp=True, busy=False): + return transition_to_state(ctx, sock, state, expect, rsp, busy) + + +def mig_data_payload(data): + argsz = len(vfio_user_mig_data()) + len(data) + return vfio_user_mig_data( + argsz=argsz, + size=len(data) + ) + + +def test_migration_setup(): global ctx, sock ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) assert ctx is not None - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, - size=2 << PAGE_SHIFT, flags=VFU_REGION_FLAG_RW) - assert ret == 0 + cbs = vfu_migration_callbacks_t() + cbs.version = 1 # old callbacks version + cbs.transition = migr_trans_cb + cbs.read_data = migr_read_data_cb + cbs.write_data = migr_write_data_cb - ret = vfu_setup_device_migration_callbacks(ctx) + ret = vfu_setup_device_migration_callbacks(ctx, cbs) + assert ret < 0, "do not allow old callbacks version" + + cbs.version = VFU_MIGR_CALLBACKS_VERS # new callbacks version + ret = vfu_setup_device_migration_callbacks(ctx, cbs) assert ret == 0 vfu_setup_device_quiesce_cb(ctx) @@ -54,113 +173,397 @@ def setup_function(function): ret = vfu_realize_ctx(ctx) assert ret == 0 - sock = connect_client(ctx) + sock = connect_client(ctx, MAX_DATA_XFER_SIZE) -def teardown_function(function): - global ctx - vfu_destroy_ctx(ctx) +def server_transition_track_path(a, b, expectA=0, expectB=0): + """ + Carry out the state transition from a to b on the server, keeping track of + and returning the transition path taken. + """ + global path -@patch('libvfio_user.quiesce_cb') -@patch('libvfio_user.migr_trans_cb') -def test_migration_bad_access(mock_trans, mock_quiesce): - """ - Tests that attempting to access the migration state register in an - non-aligned manner fails. + if current_state == VFIO_USER_DEVICE_STATE_STOP_COPY and \ + a == VFIO_USER_DEVICE_STATE_PRE_COPY: + # The transition STOP_COPY -> PRE_COPY is explicitly blocked so we + # advance one state to get around this in order to set up the test. + transition_to_migr_state(VFIO_USER_DEVICE_STATE_STOP) - This test is important because we tell whether we need to quiesce by - checking for a register-sized access, otherwise we'll change migration - state without having quiesced. - """ - global ctx, sock + transition_to_migr_state(a, expect=expectA) + + if expectA != 0: + return None + + path = [] - data = VFIO_DEVICE_STATE_V1_SAVING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data)-1, data=data, expect=errno.EINVAL) + transition_to_migr_state(b, expect=expectB) - mock_trans.assert_not_called() + return path.copy() -@patch('libvfio_user.quiesce_cb') -@patch('libvfio_user.migr_trans_cb', return_value=0) -def test_migration_trans_sync(mock_trans, mock_quiesce): +def test_migration_shortest_state_transition_paths(): """ - Tests transitioning to the saving state. + The spec dictates that complex state transitions are to be implemented as + combinations of the defined direct transitions, with the path selected + according to the following rules: + + - Select the shortest path. + - The path cannot have saving group states as interior arcs, only start/end + states. + + This test implements a breadth-first search to ensure that the paths taken + by the implementation correctly follow these rules. """ - global ctx, sock + # allowed direct transitions (edges) + E = { + VFIO_USER_DEVICE_STATE_ERROR: set(), + VFIO_USER_DEVICE_STATE_STOP: { + VFIO_USER_DEVICE_STATE_RUNNING, + VFIO_USER_DEVICE_STATE_STOP_COPY, + VFIO_USER_DEVICE_STATE_RESUMING + }, + VFIO_USER_DEVICE_STATE_RUNNING: { + VFIO_USER_DEVICE_STATE_STOP, + VFIO_USER_DEVICE_STATE_PRE_COPY + }, + VFIO_USER_DEVICE_STATE_STOP_COPY: {VFIO_USER_DEVICE_STATE_STOP}, + VFIO_USER_DEVICE_STATE_RESUMING: {VFIO_USER_DEVICE_STATE_STOP}, + VFIO_USER_DEVICE_STATE_RUNNING_P2P: set(), + VFIO_USER_DEVICE_STATE_PRE_COPY: { + VFIO_USER_DEVICE_STATE_RUNNING, + VFIO_USER_DEVICE_STATE_STOP_COPY + }, + VFIO_USER_DEVICE_STATE_PRE_COPY_P2P: set() + } + + # states (vertices) + V = E.keys() + + # "saving states" which cannot be internal arcs + saving_states = {VFIO_USER_DEVICE_STATE_PRE_COPY, + VFIO_USER_DEVICE_STATE_STOP_COPY} + + # Consider each vertex in turn to be the start state, that is, the state + # we are transitioning from. + for source in V: + # The previous node in the shortest path for each node, e.g. for + # shortest path `source -> node -> target`, `back[node] == source`. + back = {v: None for v in V} + queue = deque([(source, None)]) + + # Use BFS to calculate the shortest path from the start state to every + # other state, following the rule that no intermediate states can be + # saving states. + while len(queue) > 0: + (curr, prev) = queue.popleft() + back[curr] = prev + + # Intermediate states cannot be saving states, so if our current + # node is not the start state and it is a saving state, it is only + # allowed to be an end state so we don't explore its neighbours. + if curr != source and curr in saving_states: + continue + + for nxt in E[curr]: + if back[nxt] is None: + queue.append((nxt, curr)) + + # Iterate over the states + for target in V: + if source == VFIO_USER_DEVICE_STATE_STOP_COPY \ + and target == VFIO_USER_DEVICE_STATE_PRE_COPY: + # test for this transition being blocked in a separate test + continue + + # If BFS found a path to that state, follow the backpointers to + # calculate the path, and check that it's equal to the path taken + # by the server. + if back[target] is not None: + seq = deque([]) + curr = target + while curr != source: + seq.appendleft(curr) + curr = back[curr] + + server_seq = server_transition_track_path(source, target) + + assert len(seq) == len(server_seq) + assert all(seq[i] == server_seq[i] for i in range(len(seq))) + + # If BFS couldn't find a path to that state, check that the server + # doesn't allow that transition either. + else: + # If the start state is an unreachable state, we won't be able + # to transition into it in order to try and calculate a path on + # the server, so we expect that transition to fail. + expectA = errno.EINVAL if source in UNREACHABLE_STATES else 0 + + # No matter what, we expect transitioning to the target state + # to fail. + server_transition_track_path(source, target, expectA=expectA, + expectB=errno.EINVAL) + + +def test_migration_stop_copy_to_pre_copy_rejected(): + transition_to_migr_state(VFIO_USER_DEVICE_STATE_STOP_COPY) + transition_to_migr_state(VFIO_USER_DEVICE_STATE_PRE_COPY, + expect=errno.EINVAL) + + +def test_migration_nonexistent_state(): + transition_to_migr_state(0xabcd, expect=errno.EINVAL) + + +def test_migration_failed_callback(): + setup_fail_callbacks(0xbeef) + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING, expect=0xbeef) + assert c.get_errno() == 0xbeef + teardown_fail_callbacks() + + +def test_migration_get_state(): + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING) + + feature = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_mig_state()), + flags=VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE + ) + + result = msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, feature) + _, result = vfio_user_device_feature.pop_from_buffer(result) + state, _ = vfio_user_device_feature_mig_state.pop_from_buffer(result) + assert state.device_state == VFIO_USER_DEVICE_STATE_RUNNING - data = VFIO_DEVICE_STATE_V1_SAVING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data) - ret = vfu_run_ctx(ctx) - assert ret == 0 +def test_handle_mig_data_read(): + global read_data + + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING) + + data = bytes([0, 1, 2, 3]) + payload = mig_data_payload(data) + + VALID_STATES = {VFIO_USER_DEVICE_STATE_PRE_COPY, + VFIO_USER_DEVICE_STATE_STOP_COPY} + + for state in STATES: + transition_to_migr_state(state) + read_data = data + expect = 0 if state in VALID_STATES else errno.EINVAL + result = msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, + expect=expect) + + if state in VALID_STATES: + assert len(result) == len(payload) + len(data) + assert result[len(vfio_user_mig_data()):] == data -@patch('libvfio_user.migr_trans_cb', side_effect=fail_with_errno(errno.EPERM)) -def test_migration_trans_sync_err(mock_trans): +def test_handle_mig_data_read_too_long(): """ - Tests the device returning an error when the migration state is written to. + When we set up the tests at the top of this file we specify that the max + data transfer size is 4 bytes. Here we test to check that a transfer of too + many bytes fails. """ - global ctx, sock + global read_data - data = VFIO_DEVICE_STATE_V1_SAVING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data, expect=errno.EPERM) + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING) + transition_to_migr_state(VFIO_USER_DEVICE_STATE_PRE_COPY) - ret = vfu_run_ctx(ctx) - assert ret == 0 + # Create a payload reading with length 1 byte longer than the max. + read_data = bytes([i for i in range(MAX_DATA_XFER_SIZE + 1)]) + payload = mig_data_payload(read_data) + msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, expect=errno.EINVAL) -@patch('libvfio_user.quiesce_cb', side_effect=fail_with_errno(errno.EBUSY)) -@patch('libvfio_user.migr_trans_cb', return_value=0) -def test_migration_trans_async(mock_trans, mock_quiesce): - """ - Tests transitioning to the saving state where the device is initially busy - quiescing. - """ - global ctx, sock - mock_quiesce +def test_handle_mig_data_read_failed_callback(): + transition_to_migr_state(VFIO_USER_DEVICE_STATE_PRE_COPY) - data = VFIO_DEVICE_STATE_V1_SAVING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data, rsp=False, - busy=True) + read_data = bytes([1, 2, 3, 4]) + payload = mig_data_payload(read_data) - ret = vfu_device_quiesced(ctx, 0) - assert ret == 0 + setup_fail_callbacks(0xbeef) - get_reply(sock) + msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, expect=0xbeef) + assert c.get_errno() == 0xbeef - ret = vfu_run_ctx(ctx) - assert ret == 0 +def test_handle_mig_data_read_short_write(): + data = bytes([1, 2, 3, 4]) + payload = bytes(mig_data_payload(data)) + + # don't send the last byte + msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload[:-1], + expect=errno.EINVAL) + + +def test_handle_mig_data_write(): + data = bytes([1, 2, 3, 4]) + payload = mig_data_payload(data) + + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RESUMING) + msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data) + assert write_data == data + + +def test_handle_mig_data_write_invalid_state(): + data = bytes([1, 2, 3, 4]) + payload = mig_data_payload(data) + + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING) + msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data, + expect=errno.EINVAL) -@patch('libvfio_user.quiesce_cb', side_effect=fail_with_errno(errno.EBUSY)) -@patch('libvfio_user.migr_trans_cb', side_effect=fail_with_errno(errno.ENOTTY)) -def test_migration_trans_async_err(mock_trans, mock_quiesce): + +def test_handle_mig_data_write_too_long(): """ - Tests writing to the migration state register, the device not being able to - immediately quiesce, and then finally the device failing to transition to - the new migration state. + When we set up the tests at the top of this file we specify that the max + data transfer size is 4 bytes. Here we test to check that a transfer of too + many bytes fails. """ - global ctx, sock + # Create a payload writing with length 1 byte longer than the max. + data = bytes([i for i in range(MAX_DATA_XFER_SIZE + 1)]) + payload = mig_data_payload(data) - data = VFIO_DEVICE_STATE_V1_RUNNING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data, rsp=False, - busy=True) + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RESUMING) + msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data, + expect=errno.EINVAL) - ret = vfu_device_quiesced(ctx, 0) - assert ret == 0 - print("waiting for reply") - get_reply(sock, errno.ENOTTY) - print("received reply") +def test_handle_mig_data_write_failed_callback(): + transition_to_migr_state(VFIO_USER_DEVICE_STATE_RESUMING) + + data = bytes([1, 2, 3, 4]) + payload = mig_data_payload(data) + + setup_fail_callbacks(0xbeef) + + msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data, + expect=0xbeef) + assert c.get_errno() == 0xbeef + + +def test_handle_mig_data_write_short_write(): + data = bytes([1, 2, 3, 4]) + payload = mig_data_payload(data) + + msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, payload, expect=errno.EINVAL) + + +def test_device_feature_migration_get(): + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_migration()), + flags=VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIGRATION + ) + + result = msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload) + _, result = vfio_user_device_feature.pop_from_buffer(result) + flags, _ = vfio_user_device_feature_migration.pop_from_buffer(result) + flags = flags.flags + + assert flags == VFIO_MIGRATION_STOP_COPY | VFIO_MIGRATION_PRE_COPY + + +def test_device_feature_short_write(): + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_migration()), + flags=VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIGRATION + ) + + payload = bytes(payload) + + # don't send the last byte + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload[:-1], + expect=errno.EINVAL) + + +def test_device_feature_unsupported_operation(): + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()) + + len(vfio_user_device_feature_migration()), + flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIGRATION + ) + + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=errno.EINVAL) + + +def test_device_feature_bad_argsz_probe(): + payload = vfio_user_device_feature( + argsz=2, + flags=VFIO_DEVICE_FEATURE_PROBE | VFIO_DEVICE_FEATURE_MIGRATION + ) + + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=errno.EINVAL) + + +def test_device_feature_bad_argsz_get_migration(): + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()), + flags=VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIGRATION + ) + + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=errno.EINVAL) + + +def test_device_feature_bad_argsz_get_dma(): + argsz = len(vfio_user_device_feature()) + \ + len(vfio_user_device_feature_dma_logging_report()) + \ + get_bitmap_size(0x20 << PAGE_SHIFT, PAGE_SIZE) + + feature = vfio_user_device_feature( + argsz=argsz - 1, # not big enough + flags=VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT | VFIO_DEVICE_FEATURE_GET + ) + + report = vfio_user_device_feature_dma_logging_report( + iova=0x10 << PAGE_SHIFT, + length=0x20 << PAGE_SHIFT, + page_size=PAGE_SIZE + ) + + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(report), + expect=errno.EINVAL) + + +def test_device_feature_bad_argsz_set(): + feature = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()), # no space for state data + flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE + ) + payload = vfio_user_device_feature_mig_state( + device_state=VFIO_USER_DEVICE_STATE_RUNNING + ) + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload), + expect=errno.EINVAL) + + +def test_device_feature_probe(): + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()), + flags=VFIO_DEVICE_FEATURE_PROBE | VFIO_DEVICE_FEATURE_MIGRATION + ) + + result = msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload) + assert bytes(payload) == result + + payload = vfio_user_device_feature( + argsz=len(vfio_user_device_feature()), + flags=VFIO_DEVICE_FEATURE_PROBE | VFIO_DEVICE_FEATURE_SET | + VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIGRATION + ) + + msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=errno.EINVAL) + + +def test_migration_cleanup(): + disconnect_client(ctx, sock) + vfu_destroy_ctx(ctx) # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: # diff --git a/test/py/test_quiesce.py b/test/py/test_quiesce.py index b1fb2fdc..50c537e5 100644 --- a/test/py/test_quiesce.py +++ b/test/py/test_quiesce.py @@ -31,7 +31,7 @@ import errno from unittest import mock from unittest.mock import patch - +import tempfile ctx = None @@ -197,32 +197,27 @@ def test_allowed_funcs_in_quiesced_dma_unregister_busy(mock_quiesce, @patch('libvfio_user.migr_trans_cb', side_effect=_side_effect) @patch('libvfio_user.quiesce_cb') -def test_allowed_funcs_in_quiesed_migration(mock_quiesce, +def test_allowed_funcs_in_quiesced_migration(mock_quiesce, mock_trans): global ctx, sock _map_dma_region(ctx, sock) - data = VFIO_DEVICE_STATE_V1_SAVING.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data) - mock_trans.assert_called_once_with(ctx, VFIO_DEVICE_STATE_V1_SAVING) + transition_to_state(ctx, sock, VFIO_USER_DEVICE_STATE_STOP) + mock_trans.assert_called_once_with(ctx, VFU_MIGR_STATE_STOP) @patch('libvfio_user.migr_trans_cb', side_effect=_side_effect) @patch('libvfio_user.quiesce_cb') -def test_allowed_funcs_in_quiesed_migration_busy(mock_quiesce, +def test_allowed_funcs_in_quiesced_migration_busy(mock_quiesce, mock_trans): global ctx, sock _map_dma_region(ctx, sock) mock_quiesce.side_effect = fail_with_errno(errno.EBUSY) - data = VFIO_DEVICE_STATE_V1_STOP.to_bytes(c.sizeof(c.c_int), 'little') - write_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, offset=0, - count=len(data), data=data, rsp=False, - busy=True) + transition_to_state(ctx, sock, VFIO_USER_DEVICE_STATE_STOP, busy=True) ret = vfu_device_quiesced(ctx, 0) assert ret == 0 - mock_trans.assert_called_once_with(ctx, VFIO_DEVICE_STATE_V1_STOP) + mock_trans.assert_called_once_with(ctx, VFU_MIGR_STATE_STOP) @patch('libvfio_user.reset_cb', side_effect=_side_effect) diff --git a/test/py/test_request_errors.py b/test/py/test_request_errors.py index 79af0f27..ee7b4df8 100644 --- a/test/py/test_request_errors.py +++ b/test/py/test_request_errors.py @@ -54,10 +54,6 @@ def setup_function(function): ret = vfu_setup_device_reset_cb(ctx) assert ret == 0 - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, - size=2 << PAGE_SHIFT, flags=VFU_REGION_FLAG_RW) - assert ret == 0 - ret = vfu_setup_device_migration_callbacks(ctx) assert ret == 0 @@ -189,24 +185,21 @@ def test_disconnected_socket_quiesce_busy(mock_quiesce): @patch('libvfio_user.reset_cb') @patch('libvfio_user.quiesce_cb', side_effect=fail_with_errno(errno.EBUSY)) -@patch('libvfio_user.migr_get_pending_bytes_cb') -def test_reply_fail_quiesce_busy(mock_get_pending_bytes, mock_quiesce, +@patch('libvfio_user.migr_trans_cb') +def test_reply_fail_quiesce_busy(mock_migr_trans_cb, mock_quiesce, mock_reset): """Tests failing to reply and the quiesce callback returning EBUSY.""" global ctx, sock - def get_pending_bytes_side_effect(ctx): + def migr_trans_cb_side_effect(ctx, state): sock.close() return 0 - mock_get_pending_bytes.side_effect = get_pending_bytes_side_effect - - # read the get_pending_bytes register, it should close the socket causing - # the reply to fail - read_region(ctx, sock, VFU_PCI_DEV_MIGR_REGION_IDX, - vfio_user_migration_info.pending_bytes.offset, - vfio_user_migration_info.pending_bytes.size, rsp=False, - busy=True) + mock_migr_trans_cb.side_effect = migr_trans_cb_side_effect + + # change the state, it should close the socket causing the reply to fail + transition_to_state(ctx, sock, VFIO_USER_DEVICE_STATE_STOP_COPY, rsp=False, + busy=True) # vfu_run_ctx will try to reset the context and to do that it needs to # quiesce the device first diff --git a/test/py/test_setup_region.py b/test/py/test_setup_region.py index d00de689..750958bc 100644 --- a/test/py/test_setup_region.py +++ b/test/py/test_setup_region.py @@ -111,30 +111,6 @@ def test_setup_region_bad_pci(): assert c.get_errno() == errno.EINVAL -def test_setup_region_bad_migr(): - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, size=512, - flags=(VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM)) - assert ret == -1 - assert c.get_errno() == errno.EINVAL - - f = tempfile.TemporaryFile() - f.truncate(0x2000) - - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, size=0x2000, - flags=(VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM), - fd=f.fileno()) - assert ret == -1 - assert c.get_errno() == errno.EINVAL - - mmap_areas = [(0x0, 0x1000), (0x1000, 0x1000)] - - ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_MIGR_REGION_IDX, size=0x2000, - flags=(VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM), - mmap_areas=mmap_areas, fd=f.fileno()) - assert ret == -1 - assert c.get_errno() == errno.EINVAL - - def test_setup_region_cfg_always_cb_nocb(): ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_CFG_REGION_IDX, size=PCI_CFG_SPACE_EXP_SIZE, cb=None, diff --git a/test/unit-tests.c b/test/unit-tests.c index 0fd4fe77..d0c08f08 100644 --- a/test/unit-tests.c +++ b/test/unit-tests.c @@ -398,182 +398,6 @@ typedef struct { int conn_fd; } tran_sock_t; -static void -test_migration_state_transitions(void **state UNUSED) -{ - bool (*f)(uint32_t, uint32_t) = vfio_migr_state_transition_is_valid; - uint32_t i, j; - - /* from stopped (000b): all transitions are invalid except to running */ - assert_true(f(0, 0)); - assert_true(f(0, 1)); - for (i = 2; i < 8; i++) { - assert_false(f(0, i)); - } - - /* from running (001b) */ - assert_true(f(1, 0)); - assert_true(f(1, 1)); - assert_true(f(1, 2)); - assert_true(f(1, 3)); - assert_true(f(1, 4)); - assert_false(f(1, 5)); - assert_true(f(1, 6)); - assert_false(f(1, 5)); - - /* from stop-and-copy (010b) */ - assert_true(f(2, 0)); - assert_true(f(2, 1)); - assert_true(f(2, 2)); - assert_false(f(2, 3)); - assert_false(f(2, 4)); - assert_false(f(2, 5)); - assert_true(f(2, 6)); - assert_false(f(2, 7)); - - /* from pre-copy (011b) */ - assert_true(f(3, 0)); - assert_true(f(3, 1)); - assert_true(f(3, 2)); - assert_false(f(3, 3)); - assert_false(f(3, 4)); - assert_false(f(3, 5)); - assert_true(f(3, 6)); - assert_false(f(3, 7)); - - /* from resuming (100b) */ - assert_false(f(4, 0)); - assert_true(f(4, 1)); - assert_false(f(4, 2)); - assert_false(f(4, 3)); - assert_true(f(4, 4)); - assert_false(f(4, 5)); - assert_true(f(4, 6)); - assert_false(f(4, 7)); - - /* - * Transitioning to any other state from the remaining 3 states - * (101b - invalid, 110b - error, 111b - invalid) is invalid. - * Transitioning from the error state to the stopped state is possible but - * that requires a device reset, so we don't consider it a valid state - * transition. - */ - for (i = 5; i < 8; i++) { - for (j = 0; j < 8; j++) { - assert_false(f(i, j)); - } - } -} - -static struct test_setup_migr_reg_dat { - vfu_ctx_t *v; - size_t rs; /* migration registers size */ - size_t ds; /* migration data size */ - size_t s; /* migration region size*/ - const vfu_migration_callbacks_t c; -} migr_reg_data = { - .c = { - .version = VFU_MIGR_CALLBACKS_VERS, - .transition = (void *)0x1, - .get_pending_bytes = (void *)0x2, - .prepare_data = (void *)0x3, - .read_data = (void *)0x4, - .write_data = (void *)0x5, - .data_written = (void *)0x6 - } -}; - -static int -setup_test_setup_migration_region(void **state) -{ - struct test_setup_migr_reg_dat *p = &migr_reg_data; - p->v = vfu_create_ctx(VFU_TRANS_SOCK, "test", 0, NULL, - VFU_DEV_TYPE_PCI); - if (p->v == NULL) { - return -1; - } - p->rs = ROUND_UP(sizeof(struct vfio_user_migration_info), - sysconf(_SC_PAGE_SIZE)); - p->ds = sysconf(_SC_PAGE_SIZE); - p->s = p->rs + p->ds; - *state = p; - return setup(state); -} - -static vfu_ctx_t * -get_vfu_ctx(void **state) -{ - return (*((struct test_setup_migr_reg_dat **)(state)))->v; -} - -static int -teardown_test_setup_migration_region(void **state) -{ - struct test_setup_migr_reg_dat *p = *state; - vfu_destroy_ctx(p->v); - return 0; -} - -static void -test_setup_migration_region_size_ok(void **state) -{ - vfu_ctx_t *v = get_vfu_ctx(state); - int r = vfu_setup_region(v, VFU_PCI_DEV_MIGR_REGION_IDX, - vfu_get_migr_register_area_size(), NULL, - VFU_REGION_FLAG_READ | VFU_REGION_FLAG_WRITE, NULL, 0, -1, 0); - assert_int_equal(0, r); -} - -static void -test_setup_migration_region_sparsely_mappable_valid(void **state) -{ - struct test_setup_migr_reg_dat *p = *state; - struct iovec mmap_areas[] = { - [0] = { - .iov_base = (void *)p->rs, - .iov_len = p->ds - } - }; - int r = vfu_setup_region(p->v, VFU_PCI_DEV_MIGR_REGION_IDX, p->s, NULL, - VFU_REGION_FLAG_READ | VFU_REGION_FLAG_WRITE, mmap_areas, 1, - 0xdeadbeef, 0); - assert_int_equal(0, r); -} - -static void -test_setup_migration_callbacks_without_migration_region(void **state) -{ - struct test_setup_migr_reg_dat *p = *state; - assert_int_equal(-1, vfu_setup_device_migration_callbacks(p->v, &p->c, 0)); - assert_int_equal(EINVAL, errno); -} - -static void -test_setup_migration_callbacks_bad_data_offset(void **state) -{ - struct test_setup_migr_reg_dat *p = *state; - int r = vfu_setup_region(p->v, VFU_PCI_DEV_MIGR_REGION_IDX, p->s, NULL, - VFU_REGION_FLAG_READ | VFU_REGION_FLAG_WRITE, NULL, 0, -1, 0); - assert_int_equal(0, r); - r = vfu_setup_device_migration_callbacks(p->v, &p->c, - vfu_get_migr_register_area_size() - 1); - assert_int_equal(-1, r); -} - -static void -test_setup_migration_callbacks(void **state) -{ - struct test_setup_migr_reg_dat *p = *state; - int r = vfu_setup_region(p->v, VFU_PCI_DEV_MIGR_REGION_IDX, p->s, NULL, - VFU_REGION_FLAG_READ | VFU_REGION_FLAG_WRITE, NULL, 0, -1, 0); - assert_int_equal(0, r); - r = vfu_setup_device_migration_callbacks(p->v, &p->c, - vfu_get_migr_register_area_size()); - assert_int_equal(0, r); - assert_non_null(p->v->migration); - /* FIXME can't validate p->v->migration because it's a private strcut, need to move it out of lib/migration.c */ -} - static void test_device_is_stopped_and_copying(UNUSED void **state) { @@ -583,19 +407,16 @@ test_device_is_stopped_and_copying(UNUSED void **state) size_t i; struct migration migration; vfu_ctx.migration = &migration; - for (i = 0; i < ARRAY_SIZE(migr_states); i++) { - if (migr_states[i].name == NULL) { - continue; - } - migration.info.device_state = i; + for (i = 0; i < VFIO_USER_DEVICE_NUM_STATES; i++) { + migration.state = i; bool r = device_is_stopped_and_copying(vfu_ctx.migration); - if (i == VFIO_DEVICE_STATE_V1_SAVING) { + if (i == VFIO_USER_DEVICE_STATE_STOP_COPY) { assert_true(r); } else { assert_false(r); } r = device_is_stopped(vfu_ctx.migration); - if (i == VFIO_DEVICE_STATE_V1_STOP) { + if (i == VFIO_USER_DEVICE_STATE_STOP) { assert_true(r); } else { assert_false(r); @@ -611,8 +432,10 @@ test_cmd_allowed_when_stopped_and_copying(UNUSED void **state) for (i = 0; i < VFIO_USER_MAX; i++) { bool r = cmd_allowed_when_stopped_and_copying(i); - if (i == VFIO_USER_REGION_READ || i == VFIO_USER_REGION_WRITE || - i == VFIO_USER_DIRTY_PAGES) { + if (i == VFIO_USER_REGION_READ || + i == VFIO_USER_REGION_WRITE || + i == VFIO_USER_DEVICE_FEATURE || + i == VFIO_USER_MIG_DATA_READ) { assert_true(r); } else { assert_false(r); @@ -623,7 +446,7 @@ test_cmd_allowed_when_stopped_and_copying(UNUSED void **state) static void test_should_exec_command(UNUSED void **state) { - struct migration migration = { { 0 } }; + struct migration migration = { 0 }; vfu_ctx.migration = &migration; @@ -675,22 +498,6 @@ main(void) cmocka_unit_test_setup(test_dma_controller_remove_region_unmapped, setup), cmocka_unit_test_setup(test_dma_addr_to_sgl, setup), cmocka_unit_test_setup(test_vfu_setup_device_dma, setup), - cmocka_unit_test_setup(test_migration_state_transitions, setup), - cmocka_unit_test_setup_teardown(test_setup_migration_region_size_ok, - setup_test_setup_migration_region, - teardown_test_setup_migration_region), - cmocka_unit_test_setup_teardown(test_setup_migration_region_sparsely_mappable_valid, - setup_test_setup_migration_region, - teardown_test_setup_migration_region), - cmocka_unit_test_setup_teardown(test_setup_migration_callbacks_without_migration_region, - setup_test_setup_migration_region, - teardown_test_setup_migration_region), - cmocka_unit_test_setup_teardown(test_setup_migration_callbacks_bad_data_offset, - setup_test_setup_migration_region, - teardown_test_setup_migration_region), - cmocka_unit_test_setup_teardown(test_setup_migration_callbacks, - setup_test_setup_migration_region, - teardown_test_setup_migration_region), cmocka_unit_test_setup(test_device_is_stopped_and_copying, setup), cmocka_unit_test_setup(test_cmd_allowed_when_stopped_and_copying, setup), cmocka_unit_test_setup(test_should_exec_command, setup),