>-----Original Message-----
>From: Cédric Le Goater <[email protected]>
>Subject: Re: [PULL 28/41] vfio/listener: Add missing dirty tracking in
>region_del
>
>On 6/24/26 10:02, Duan, Zhenzhong wrote:
>> Hi Cédric,
>>
>>> -----Original Message-----
>>> From: Cédric Le Goater <[email protected]>
>>> Subject: Re: [PULL 28/41] vfio/listener: Add missing dirty tracking in
>>> region_del
>>>
>>> Hello Zhenzhong,
>>>
>>> On 1/13/26 10:36, Cédric Le Goater wrote:
>>>> From: Zhenzhong Duan <[email protected]>
>>>>
>>>> If a VFIO device in guest switches from passthrough(PT) domain to block
>>>> domain, the whole memory address space is unmapped, but we passed a
>NULL
>>>> iotlb entry to unmap_bitmap, then bitmap query didn't happen and we lost
>>>> dirty pages.
>>>>
>>>> By constructing an iotlb entry with iova = gpa for unmap_bitmap, it can
>>>> set dirty bits correctly.
>>>>
>>>> For IOMMU address space, we still send NULL iotlb because VFIO don't know
>>>> the actual mappings in guest. It's vIOMMU's responsibility to send actual
>>>> unmapping notifications, e.g., vtd_address_space_unmap_in_dirty_tracking().
>>>>
>>>> Signed-off-by: Zhenzhong Duan <[email protected]>
>>>> Tested-by: Giovannio Cabiddu <[email protected]>
>>>> Reviewed-by: Yi Liu <[email protected]>
>>>> Link: https://lore.kernel.org/qemu-devel/20251218062643.624796-8-
>>> [email protected]
>>>> Signed-off-by: Cédric Le Goater <[email protected]>
>>>> ---
>>>> hw/vfio/listener.c | 22 +++++++++++++++++++++-
>>>> 1 file changed, 21 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c
>>>> index
>>>
>62699cb772d786c1510318dff73973ef4d297177..813621f22f8b5ec284388f9c5f71
>>> 9525ec5f282c 100644
>>>> --- a/hw/vfio/listener.c
>>>> +++ b/hw/vfio/listener.c
>>>> @@ -713,14 +713,34 @@ static void
>vfio_listener_region_del(MemoryListener
>>> *listener,
>>>>
>>>> if (try_unmap) {
>>>> bool unmap_all = false;
>>>> + IOMMUTLBEntry entry = {}, *iotlb = NULL;
>>>>
>>>> if (int128_eq(llsize, int128_2_64())) {
>>>> assert(!iova);
>>>> unmap_all = true;
>>>> llsize = int128_zero();
>>>> }
>>>> +
>>>> + /*
>>>> + * Fake an IOTLB entry for identity mapping which is needed by
>>>> dirty
>>>> + * tracking when switch out of PT domain. In fact, in
>>>> unmap_bitmap,
>>>> + * only translated_addr field is used to set dirty bitmap.
>>>> + *
>>>> + * Note: When switch into PT domain from DMA domain, the whole
>>> IOMMU
>>>> + * MR is deleted without iotlb, before that happen, we depend on
>>>> + * vIOMMU to send unmap notification with accurate iotlb entry to
>>>> + * VFIO. See vtd_address_space_unmap_in_dirty_tracking() for
>>>> example,
>>>> + * it is triggered during switching to block domain because vtd
>>>> does
>>>> + * not support direct switching from DMA to PT domain.
>>>> + */
>>>> + if (global_dirty_tracking && memory_region_is_ram(section->mr)) {
>>>> + entry.iova = iova;
>>>> + entry.translated_addr = iova;
>>>
>>> In an experiment involving a nested setup with L1 + intel_iommu,
>>> an L2 VM with an assigned igb VF can trigger the following QEMU
>>> segv :
>>>
>>> bitmap_set_atomic(map=NULL, start=1, nr=1)
>>> physical_memory_set_dirty_range(start=0x380004040000, length=4096)
>>> physical_memory_set_dirty_lebitmap(start=0x380004040000, pages=3)
>>> vfio_container_query_dirty_bitmap(translated_addr=0x380004040000)
>>> vfio_legacy_dma_unmap_one(iova=0x380004040000, size=12288)
>>> vfio_listener_region_del()
>>
>> Hmm, looks a range in PCI space, I suspect it's a PCI bar but it has 3 pages.
>
>It's the nested VT-d interrupt remapping table.
>
>> Is igb VF a real hw passthrough to L1, then L1 pass it to L2?
>
>Iit's an L1 emulated igb VF assigned to an L2.
>
>> Seems L2 crash, not L1?
>yes. It occured a few times and I can't reproduce anymore. hmm.
It can be reproduced with below steps:
1. Start guest with VFIO device in legacy iommu mode, then start dirty rate
tracking.
(qemu) calc_dirty_rate -b 60
2. disable VFIO device bar region by clear bit1 in COMMAND register in guest.
# setpci -s 01:00.0 COMMAND=0005
3. below trace triggers:
vfio_listener_region_del region_del 0xe0400020000 - 0xe0400021fff
Thread 10 "CPU 6/KVM" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffde5dfb640 (LWP 217374)]
0x000055555613c8a0 in bitmap_set_atomic (map=0x0, start=32, nr=1) at
../util/bitmap.c:213
213 qatomic_or(p, mask_to_set);
(gdb) bt
#0 0x000055555613c8a0 in bitmap_set_atomic (map=0x0, start=32, nr=1) at
../util/bitmap.c:213
#1 0x0000555555d6a0e2 in physical_memory_set_dirty_range
(start=15410342789120, length=4096, mask=5 '\005') at ../system/physmem.c:1042
#2 0x0000555555d6b2c9 in physical_memory_set_dirty_lebitmap
(bitmap=0x7ffdc80ac690, start=15410342789120, pages=2) at
../system/physmem.c:1305
#3 0x0000555555ca97ee in vfio_legacy_dma_unmap_get_dirty_bitmap
(container=0x5555576fc980, iova=15410342789120, size=8192,
iotlb=0x7ffde5df9e20) at ../hw/vfio/container-legacy.c:112
#4 0x0000555555ca9927 in vfio_legacy_dma_unmap_one (container=0x5555576fc980,
iova=15410342789120, size=8192, flags=0, iotlb=0x7ffde5df9e20) at
../hw/vfio/container-legacy.c:145
#5 0x0000555555ca9aba in vfio_legacy_dma_unmap (bcontainer=0x5555576fc980,
iova=15410342789120, size=8192, iotlb=0x7ffde5df9e20, unmap_all=false) at
../hw/vfio/container-legacy.c:196
#6 0x0000555555ca84b5 in vfio_container_dma_unmap (bcontainer=0x5555576fc980,
iova=15410342789120, size=8192, iotlb=0x7ffde5df9e20, unmap_all=false) at
../hw/vfio/container.c:131
#7 0x0000555555ca68cd in vfio_listener_region_del (listener=0x5555576fc9b0,
section=0x7ffde5df9ed0) at ../hw/vfio/listener.c:753
#8 0x0000555555d5d9a9 in address_space_update_topology_pass (as=0x5555574c1880
<address_space_memory>, old_view=0x7ffd7c001580, new_view=0x7ffdc8000c00,
adding=false)
>
>That seemed to fix it
>
>- entry.translated_addr = iova;
>+ entry.translated_addr = memory_region_get_ram_addr(section->mr) +
>+ section->offset_within_region;
I verified this works.
>
>
>Thanks,
>
>C.
>
>
>>>
>>> The VT-d interrupt-remapping table is mapped pretty high and
>>> translated_addr ends up as a very large value. It seems that
>>> the correct value should be :
>>>
>>> translated_addr = memory_region_get_ram_addr(section->mr) +
>>> section->offset_within_region;
>>>
>>> as found in vfio_iommu_map_dirty_notify() and
>>> and vfio_ram_discard_query_dirty_bitmap().
>>>
>>> Is that correct ?
>>
>> Good catch, it looks we lack a conversion from iotlb->translated_addr to
>dirty_block offset just like in vfio_iommu_map_dirty_notify().
>> iotlb->translated_addr is designed to hold translation result from vIOMMU.
>> How
>about below(untested)?
>>
>> --- a/hw/vfio/container-legacy.c
>> +++ b/hw/vfio/container-legacy.c
>> @@ -71,7 +71,7 @@ static int
>vfio_ram_block_discard_disable(VFIOLegacyContainer *container,
>> static int
>> vfio_legacy_dma_unmap_get_dirty_bitmap(const VFIOLegacyContainer
>*container,
>> hwaddr iova, uint64_t size,
>> - IOMMUTLBEntry *iotlb)
>> + hwaddr translated_addr)
>> {
>> const VFIOContainer *bcontainer = VFIO_IOMMU(container);
>> struct vfio_iommu_type1_dma_unmap *unmap;
>> @@ -109,8 +109,8 @@ vfio_legacy_dma_unmap_get_dirty_bitmap(const
>VFIOLegacyContainer *container,
>>
>> ret = ioctl(container->fd, VFIO_IOMMU_UNMAP_DMA, unmap);
>> if (!ret) {
>> - physical_memory_set_dirty_lebitmap(vbmap.bitmap,
>> - iotlb->translated_addr, vbmap.pages);
>> + physical_memory_set_dirty_lebitmap(vbmap.bitmap, translated_addr,
>> + vbmap.pages);
>> } else {
>> error_report("VFIO_UNMAP_DMA with DIRTY_BITMAP : %m");
>> }
>> @@ -134,16 +134,27 @@ static int vfio_legacy_dma_unmap_one(const
>VFIOLegacyContainer *container,
>> .size = size,
>> };
>> bool need_dirty_sync = false;
>> + MemoryRegion *mr;
>> + hwaddr translated_addr, xlat;
>> int ret;
>> Error *local_err = NULL;
>>
>> g_assert(!cpr_is_incoming());
>>
>> + RCU_READ_LOCK_GUARD();
>> +
>> + mr = vfio_translate_iotlb(iotlb, &xlat, &local_err);
>> + if (!mr) {
>> + return -EINVAL;
>> + }
>> +
>> + translated_addr = memory_region_get_ram_addr(mr) + xlat;
>> +
>> if (iotlb && vfio_container_dirty_tracking_is_started(bcontainer)) {
>> if
>> (!vfio_container_devices_dirty_tracking_is_supported(bcontainer) &&
>> bcontainer->dirty_pages_supported) {
>> return vfio_legacy_dma_unmap_get_dirty_bitmap(container, iova,
>> size,
>> - iotlb);
>> + translated_addr);
>> }
>>
>> need_dirty_sync = true;
>> @@ -155,7 +166,7 @@ static int vfio_legacy_dma_unmap_one(const
>VFIOLegacyContainer *container,
>>
>> if (need_dirty_sync) {
>> ret = vfio_container_query_dirty_bitmap(bcontainer, iova, size, 0,
>> - iotlb->translated_addr, &local_err);
>> + translated_addr,
>> &local_err);
>> if (ret) {
>> error_report_err(local_err);
>> return ret;
>> diff --git a/hw/vfio/iommufd.c b/hw/vfio/iommufd.c
>> index 68f2ae6f9f..969629b0f5 100644
>> --- a/hw/vfio/iommufd.c
>> +++ b/hw/vfio/iommufd.c
>> @@ -65,6 +65,8 @@ static int iommufd_cdev_unmap(const VFIOContainer
>*bcontainer,
>> IOMMUFDBackend *be = container->be;
>> uint32_t ioas_id = container->ioas_id;
>> bool need_dirty_sync = false;
>> + hwaddr translated_addr, xlat;
>> + MemoryRegion *mr;
>> Error *local_err = NULL;
>> int ret, unmap_ret;
>>
>> @@ -72,12 +74,21 @@ static int iommufd_cdev_unmap(const VFIOContainer
>*bcontainer,
>> size = UINT64_MAX;
>> }
>>
>> + RCU_READ_LOCK_GUARD();
>> +
>> + mr = vfio_translate_iotlb(iotlb, &xlat, &local_err);
>> + if (!mr) {
>> + return -EINVAL;
>> + }
>> +
>> + translated_addr = memory_region_get_ram_addr(mr) + xlat;
>> +
>> if (iotlb && vfio_container_dirty_tracking_is_started(bcontainer)) {
>> if
>> (!vfio_container_devices_dirty_tracking_is_supported(bcontainer) &&
>> bcontainer->dirty_pages_supported) {
>> ret = vfio_container_query_dirty_bitmap(bcontainer, iova, size,
>>
>> IOMMU_HWPT_GET_DIRTY_BITMAP_NO_CLEAR,
>> - iotlb->translated_addr,
>> + translated_addr,
>> &local_err);
>> if (ret) {
>> error_report_err(local_err);
>> @@ -103,7 +114,7 @@ static int iommufd_cdev_unmap(const VFIOContainer
>*bcontainer,
>>
>> if (need_dirty_sync) {
>> ret = vfio_container_query_dirty_bitmap(bcontainer, iova, size, 0,
>> - iotlb->translated_addr,
>> + translated_addr,
>> &local_err);
>> if (ret) {
>> error_report_err(local_err);
>> diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c
>> index 109b5d61af..e1b078a7b3 100644
>> --- a/hw/vfio/listener.c
>> +++ b/hw/vfio/listener.c
>> @@ -97,8 +97,8 @@ static bool
>vfio_listener_skipped_section(MemoryRegionSection *section,
>> * Called with rcu_read_lock held.
>> * The returned MemoryRegion must not be accessed after calling
>rcu_read_unlock.
>> */
>> -static MemoryRegion *vfio_translate_iotlb(IOMMUTLBEntry *iotlb, hwaddr
>*xlat_p,
>> - Error **errp)
>> +MemoryRegion *vfio_translate_iotlb(IOMMUTLBEntry *iotlb, hwaddr *xlat_p,
>> + Error **errp)
>> {
>> MemoryRegion *mr;
Above change needs below tweak to work:
@@ -134,7 +134,6 @@ static int vfio_legacy_dma_unmap_one(const
VFIOLegacyContainer *container,
.size = size,
};
bool need_dirty_sync = false;
- MemoryRegion *mr;
hwaddr translated_addr, xlat;
int ret;
Error *local_err = NULL;
@@ -143,14 +142,15 @@ static int vfio_legacy_dma_unmap_one(const
VFIOLegacyContainer *container,
RCU_READ_LOCK_GUARD();
- mr = vfio_translate_iotlb(iotlb, &xlat, &local_err);
- if (!mr) {
- return -EINVAL;
- }
+ if (iotlb && vfio_container_dirty_tracking_is_started(bcontainer)) {
+ MemoryRegion *mr = vfio_translate_iotlb(iotlb, &xlat, &local_err);
- translated_addr = memory_region_get_ram_addr(mr) + xlat;
+ if (!mr) {
+ return -EINVAL;
+ }
+
+ translated_addr = memory_region_get_ram_addr(mr) + xlat;
- if (iotlb && vfio_container_dirty_tracking_is_started(bcontainer)) {
if (!vfio_container_devices_dirty_tracking_is_supported(bcontainer) &&
bcontainer->dirty_pages_supported) {
return vfio_legacy_dma_unmap_get_dirty_bitmap(container, iova,
size,
Thanks
Zhenzhong