dma: return empty dirty bitmap for unmapped or message-based regions (#877)
To align with kernel VFIO uAPI behavior, the server should not fail the
migration when asked to report dirty pages for unmapped or non-FD DMA
regions.
For regions accessed via messages (REGION_ACCESS_MODE_MSG) or unmapped
firmware/MMIO regions, the client physically executes the memory writes
and natively tracks them in its own dirty bitmap.
Return an empty (zeroed) bitmap instead of -EINVAL for these regions
so the client migration does not crash.
Signed-off-by: Hugo Komatsu <hugo.komatsu@nutanix.com>
diff --git a/lib/dma.c b/lib/dma.c
index c0bea6d..087dbf6 100644
--- a/lib/dma.c
+++ b/lib/dma.c
@@ -730,10 +730,12 @@
*/
ret = dma_addr_to_sgl(dma, addr, len, &sg, 1, PROT_NONE);
if (unlikely(ret != 1)) {
- vfu_log(dma->vfu_ctx, LOG_DEBUG, "failed to translate %#llx-%#llx: %m",
- (unsigned long long)(uintptr_t)addr,
- (unsigned long long)(uintptr_t)addr + len - 1);
- return ret;
+ /*
+ * Unmapped region (e.g. firmware or MMIO). The client tracks any
+ * message-based DMA that might occur here. Return an empty bitmap.
+ */
+ memset(bitmap, 0, size);
+ return 0;
}
if (unlikely(sg.dma_addr != addr || sg.length != len)) {
@@ -780,9 +782,12 @@
region = sg.region;
if (region->access_mode == REGION_ACCESS_MODE_MSG) {
- vfu_log(dma->vfu_ctx, LOG_ERR, "region [%p-%p] isn't accessed directly",
- region->info.iova.iov_base, iov_end(®ion->info.iova));
- return ERROR_INT(EINVAL);
+ /*
+ * The client automatically tracks dirty pages for message-based DMA
+ * operations, so we safely return an empty bitmap here.
+ */
+ memset(bitmap, 0, size);
+ return 0;
}
if (client_pgsize == dma->dirty_pgsize) {
diff --git a/test/py/test_dirty_pages.py b/test/py/test_dirty_pages.py
index b4f2468..aad2c77 100644
--- a/test/py/test_dirty_pages.py
+++ b/test/py/test_dirty_pages.py
@@ -341,9 +341,6 @@
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,
@@ -352,8 +349,19 @@
# Invalid requested bitmap size
get_dirty_page_bitmap(page_size=1 << 24, expect=errno.EINVAL)
- # Region not mapped
- get_dirty_page_bitmap(addr=0x40 << PAGE_SHIFT, expect=errno.EINVAL)
+
+def test_dirty_pages_unmapped_and_msg_regions():
+ """
+ Regions that are unmapped or accessed via message-based DMA should
+ return success and an empty bitmap (all zeroes).
+ """
+ # Unmapped region
+ bitmap = get_dirty_page_bitmap(addr=0xdeadbeef, expect=0)
+ assert bitmap == 0
+
+ # Message-based DMA region
+ bitmap = get_dirty_page_bitmap(addr=0x40 << PAGE_SHIFT, expect=0)
+ assert bitmap == 0
def stop_logging(addr=None, length=None):
@@ -479,10 +487,11 @@
addr=0x10 << PAGE_SHIFT, size=0x10 << PAGE_SHIFT)
msg(ctx, client.sock, VFIO_USER_DMA_UNMAP, payload)
- # Verify first region is actually unmapped (should fail)
- get_dirty_page_bitmap(addr=0x10 << PAGE_SHIFT,
- length=0x10 << PAGE_SHIFT,
- expect=errno.ENOENT)
+ # Verify first region is actually unmapped (returns empty bitmap)
+ bitmap = get_dirty_page_bitmap(addr=0x10 << PAGE_SHIFT,
+ length=0x10 << PAGE_SHIFT,
+ expect=0)
+ assert bitmap == 0
# Verify second region still works and dirty bit is correctly set
bitmap = get_dirty_page_bitmap(addr=0x30 << PAGE_SHIFT,