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.
Is igb VF a real hw passthrough to L1, then L1 pass it to L2?
Seems L2 crash, not L1?
>
>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;
Thanks
Zhenzhong