Virtuozzo QEMU development (svt-core@virtuozzo.com)
 help / color / mirror / Atom feed
From: no-reply@sashiko.virtuozzo.com
To: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
	 svt-core@virtuozzo.com
Cc: andrey.drobyshev@virtuozzo.com
Subject: Re: [PATCH QEMU HCI-8.1 v2 23/24] VZ: vfio/region: preserve BAR mappings across cpr-exec #VSTOR-103860
Date: Tue, 13 Oct 2026 16:00:53 +0000	[thread overview]
Message-ID: <010701a135fecd05-db919099-27ad-4baa-9ef0-265e0a85916d-000000@eu-central-1.amazonses.com> (raw)
In-Reply-To: <20261013-vfio_v1-v2-23-433455c0a294@virtuozzo.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] vfio: vfio_region_cpr_find() exits QEMU for lazily mmapped display region after incoming mode reset
- [Medium] vfio: vfio_region_mmap() adds cpr-transfer blocker with error_abort
- [Medium] vfio: cpr-exec new QEMU exit(1)s when a BAR was not mmapped by old QEMU
- [Medium] vfio: display region CPR entry leaked, vfio_region_exit() runs after unparent
- [Low] vfio: cpr-transfer blocker added before mmap and kept after failure
- [Low] vfio: cpr-transfer is blocked for every VFIO device with mmapped regions
--

commit 715bce8fddbc1bc88d530d34be1f40b7da065453
Author: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

VZ: vfio/region: preserve BAR mappings across cpr-exec #VSTOR-103860

This patch saves each VFIO BAR mmap in the CPR ramblock list, so that the
new QEMU can reuse the mapping after cpr-exec instead of calling mmap()
again. It also adds a cpr-transfer blocker for devices with mmapped
regions.

> diff --git a/hw/vfio/device.c b/hw/vfio/device.c
> index 5a14a7d5c0a2e..88b9e8752cd93 100644
> --- a/hw/vfio/device.c
> +++ b/hw/vfio/device.c
> @@ -514,6 +514,7 @@ void vfio_device_unprepare(VFIODevice *vbasedev)
>  
>      g_clear_pointer(&vbasedev->reginfo, g_free);
>      g_clear_pointer(&vbasedev->region_fds, g_free);
> +    migrate_del_blocker(&vbasedev->cpr.mmap_blocker);
>  
>      QLIST_REMOVE(vbasedev, container_next);
>      QLIST_REMOVE(vbasedev, global_next);

[ ... ]

> diff --git a/hw/vfio/region.c b/hw/vfio/region.c
> index 1deaef12fd0b8..fce09a0d17b1a 100644
> --- a/hw/vfio/region.c
> +++ b/hw/vfio/region.c

[ ... ]

> +static char *vfio_region_cpr_name(VFIORegion *region, int index)
> +{
> +    g_autofree char *path = qdev_get_dev_path(region->vbasedev->dev);
> +    return g_strdup_printf("%s%s%s mmaps[%d]", path ?: "", path ? "/" : "",
> +                           memory_region_name(region->mem), index);
> +}

[Severity: Medium]
The name built here depends on qdev_get_dev_path(), which returns NULL once
device_unparent() has cleared dev->parent_bus. Can the save-time and
delete-time names diverge for the display region?

The display region is mmapped lazily:

vfio_display_region_update()
  vfio_region_setup()
  vfio_region_mmap()
    vfio_region_cpr_save()   /* saved as "<path>/display mmaps[0]" */

On hot-unplug, vfio_exitfn() runs vfio_pci_bars_exit(), which covers only
the BAR regions. The display region is torn down later, from
vfio_pci_finalize():

vfio_pci_finalize()
  vfio_pci_put_device()
    vfio_display_finalize()
      vfio_display_region_exit()
        vfio_region_exit()

By then device_unparent() has run, so the name becomes "display mmaps[0]"
without the path prefix. Then cpr_delete_ramblock() in vfio_region_exit()
finds nothing, and the saved entry stays while vfio_region_finalize()
munmap()s the region.

Could this leave a stale entry that makes a later hot-replug of a device
with the same path hit exit(1) in vfio_region_cpr_find(), or makes a later
cpr-exec try MADV_DOEXEC on an unmapped range?

> +/* Same checks as for RAM blocks in ram_block_add() */
> +static void *vfio_region_cpr_find(VFIORegion *region, int index)
> +{
> +    g_autofree char *name = vfio_region_cpr_name(region, index);
> +    cpr_ramblock_t ram = cpr_find_ramblock(name);
> +
> +    if (cpr_exec_is_incoming()) {
> +        if (!ram.addr || !ram.length) {
> +            error_report("CPR mode=exec is requested, but mapping %s can't "
> +                         "be reused: addr=%p length=%zu. Aborting",
> +                         name, (void *)ram.addr, ram.length);
> +            exit(1);
> +        }

[Severity: Medium]
Can this exit(1) be reached for a region that old QEMU legitimately could
not mmap?

In old QEMU, vfio_bar_register() treats a vfio_region_mmap() failure as
non-fatal:

    if (vfio_region_mmap(&bar->region)) {
        error_report("Failed to mmap %s BAR %d. Performance may be slow",
                     vdev->vbasedev.name, nr);
    }

The no_mmap path in vfio_region_mmap() calls vfio_subregion_unmap(), which
calls cpr_delete_ramblock(). A failed region therefore has no CPR entry.

The only blocker added by this patch is mmap_blocker, which applies to
cpr-transfer only. Nothing blocks cpr-exec in this state, so the new QEMU
runs vfio_region_mmap() again. Then vfio_region_cpr_find() sees
cpr_exec_is_incoming() true and no entry, and exits.

The comment says this mirrors ram_block_add(), but RAM blocks are always
allocated while BAR mmaps are optional. Should this fall back to mmap() or
the slow path when no entry exists, or should cpr-exec be blocked when a
region could not be mapped?

> +        assert(ram.length == region->mmaps[index].size);
> +    } else if (ram.addr || ram.length) {
> +        error_report("CPR mode=exec wasn't requested, but mapping %s was "
> +                     "saved: addr=%p length=%zu. Aborting",
> +                     name, (void *)ram.addr, ram.length);
> +        exit(1);
> +    }

[Severity: High]
Can this second branch kill QEMU after a cpr-exec that had otherwise
succeeded, for the lazily mapped display region?

The sequence would be:

1. Old QEMU maps the display buffer from vfio_display_region_update(), and
   vfio_region_cpr_save() adds "<path>/display mmaps[0]" to the ramblock
   list. cpr_exec_cb() preserves it.

2. New QEMU loads the entry in cpr_state_load(), which also sets
   incoming_mode to MIG_MODE_CPR_EXEC. Nothing maps the display region at
   realize time, since vfio_display_region_init() only registers the
   gfx_update callback.

3. migration_incoming_state_destroy() calls
   cpr_set_incoming_mode(MIG_MODE_NONE).

4. The first gfx_update with an active plane after that calls
   vfio_region_setup() and vfio_region_mmap(), and then
   vfio_region_cpr_find().

The saved entry is still present, because only vfio_subregion_unmap() and
vfio_region_exit() delete it. cpr_exec_is_incoming() is now false, so the
"wasn't requested, but mapping %s was saved" branch calls exit(1).

The check was copied from ram_block_add(), which runs at realize time
while the incoming mode is still set. Is it valid for a mapping that can be
created after the mode is reset?

Even without the exit, the preserved mapping would be neither reused nor
released.

[ ... ]

> @@ -307,6 +352,19 @@ int vfio_region_mmap(VFIORegion *region)
>          return 0;
>      }
>  
> +    /*
> +     * Only cpr-exec keeps the address space, a cpr-transfer target would
> +     * have to map the regions anew, which some drivers (NVIDIA vGPU) refuse
> +     * while the old mappings exist.  Not supported.
> +     */
> +    if (!region->vbasedev->cpr.mmap_blocker) {
> +        error_setg(&region->vbasedev->cpr.mmap_blocker,
> +                   "cpr-transfer is not supported with mmapped VFIO regions "
> +                   "(%s); use the cpr-exec mode", region->vbasedev->name);
> +        migrate_add_blocker_modes(&region->vbasedev->cpr.mmap_blocker,
> +                                  &error_abort, MIG_MODE_CPR_TRANSFER, -1);
> +    }

[Severity: Medium]
Can passing &error_abort here abort QEMU at runtime?

migrate_add_blocker_modes() calls is_busy(), which fails with -EBUSY and
fills errp when the following holds:

    if (runstate_check(RUN_STATE_SAVE_VM) || migration_is_running()) {
        error_propagate_prepend(errp, *reasonp, ...

vfio_region_mmap() is not only a realize-time call. The gfx_update
callback reaches it lazily:

vfio_display_region_update()
  vfio_region_mmap(&dpy->region.buffer)

For a device whose BARs have no mmap, or whose BAR mmaps failed, the first
blocker registration happens at runtime. If a migration or snapshot is
running at that moment, error_abort turns the -EBUSY into abort().

Since is_busy() does not depend on the mode, would this also hit ordinary
non-CPR migrations? Could the -EBUSY be handled instead of aborting?

[Severity: Low]
Is the blocker installed too early here? It is added before the mmap loop
and is not removed on the no_mmap error path, nor by vfio_subregion_unmap()
or vfio_region_exit().

The only removal is migrate_del_blocker() in vfio_device_unprepare(). The
commit message says the block applies "for devices with mmapped regions",
but a device whose first mmap() fails, or whose mappings were all undone,
still blocks cpr-transfer until vfio_device_unprepare().

[Severity: Low]
Is the blocker intended to apply to every device with mmapped regions?

It is registered unconditionally for any region with nr_mmaps > 0, so
nearly every vfio-pci device blocks cpr-transfer. Before this patch, VFIO
devices reached cpr-transfer through no such check, since
vfio_devices_present() was only consulted through
migration_is_blocked()->migrate_cpr_is_blocked() when migrate_mode() ==
MIG_MODE_CPR_EXEC.

The commit message gives the NVIDIA vGPU case as the reason, but the code
does not narrow the block to drivers that refuse to remap. Could the
blocker be limited to the affected drivers or devices?

[ ... ]

> @@ -314,6 +372,11 @@ int vfio_region_mmap(VFIORegion *region)
>          size_t align = MIN(1ULL << ctz64(region->mmaps[i].size), 1 * GiB);
>          void *map_base, *map_align;
>  
> +        region->mmaps[i].mmap = vfio_region_cpr_find(region, i);
> +        if (region->mmaps[i].mmap) {
> +            goto mapped;
> +        }
> +

[ ... ]

> @@ -410,6 +475,13 @@ void vfio_region_exit(VFIORegion *region)
>  
>      for (i = 0; i < region->nr_mmaps; i++) {
>          if (region->mmaps[i].mmap) {
> +            /*
> +             * The mapping is unmapped by vfio_region_finalize(), but by
> +             * then the device is unparented and its path, part of the CPR
> +             * name, is gone.  So drop the CPR entry here.
> +             */
> +            g_autofree char *cpr_name = vfio_region_cpr_name(region, i);
> +            cpr_delete_ramblock(cpr_name);
>              memory_region_del_subregion(region->mem, &region->mmaps[i].mem);
>          }
>      }

This comment is correct for the BAR path through vfio_exitfn(), but is the
same ordering guaranteed when vfio_region_exit() is reached from
vfio_display_finalize()? As described above, the device is already
unparented there.

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261013-vfio_v1-v2-0-433455c0a294@virtuozzo.com?part=23

  reply	other threads:[~2026-10-13 16:00 UTC|newest]

Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13 15:28 [PATCH QEMU HCI-8.1 v2 00/24] vfio: support qemu-update with passed through GPU's and vGPU's #VSTOR-103860 Andrey Drobyshev
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 01/24] migration: Plug memory leaks after migrate_set_error() Andrey Drobyshev
2026-10-13 15:30   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 02/24] hw/vfio: Fix liveness check in vfio_connect_kvm_msi_virq() Andrey Drobyshev
2026-10-13 15:30   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 03/24] vfio/pci: Reject invalid MSI-X Table and PBA BIR values Andrey Drobyshev
2026-10-13 15:31   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 04/24] vfio/pci: Fix information leak in vfio_rom_read() Andrey Drobyshev
2026-10-13 15:31   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 05/24] vfio/pci: Initialize rom_read_failed in vfio_pci_load_rom() Andrey Drobyshev
2026-10-13 15:33   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 06/24] vfio/pci: Grow buffer in vfio_pci_host_match() Andrey Drobyshev
2026-10-13 15:32   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 07/24] q35: Fix migration of SMRAM state Andrey Drobyshev
2026-10-13 15:35   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 08/24] migration: ensure APIC is loaded prior to VFIO PCI devices Andrey Drobyshev
2026-10-13 15:33   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 09/24] migration: Fix regression of passing error_fatal into vmstate_load_state() Andrey Drobyshev
2026-10-13 15:36   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 10/24] migration: vmstate_save_state_v: fix double error_setg Andrey Drobyshev
2026-10-13 15:38   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 11/24] migration: yank: Move register instance earlier Andrey Drobyshev
2026-10-13 15:41   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 12/24] migration: Free cpr-transfer MigrationAddress along with gsource Andrey Drobyshev
2026-10-13 15:39   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 13/24] migration: Move CPR HUP watch to cpr-transfer.c Andrey Drobyshev
2026-10-13 15:40   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 14/24] migration: Fix crash on second migration when cancel early Andrey Drobyshev
2026-10-13 15:45   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 15/24] kvm: tolerate ioctls after kvm_close() Andrey Drobyshev
2026-10-13 15:44   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 16/24] kvm: fix coalesced MMIO flush racing with kvm_close() Andrey Drobyshev
2026-10-13 15:47   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 17/24] migration/cpr: add cpr_is_in_progress() Andrey Drobyshev
2026-10-13 15:47   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 18/24] vfio/migration: don't migrate device state during CPR Andrey Drobyshev
2026-10-13 15:53   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 19/24] vfio/listener: skip dirty page tracking " Andrey Drobyshev
2026-10-13 15:50   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 20/24] VZ: vfio/cpr: don't close KVM on cpr-exec Andrey Drobyshev
2026-10-13 15:53   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 21/24] VZ: migration/cpr-exec: abort cpr-exec if memory can't be preserved Andrey Drobyshev
2026-10-13 15:55   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 22/24] VZ: migration/cpr-exec: take the vhost-net devices back if exec fails Andrey Drobyshev
2026-10-13 15:55   ` no-reply
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 23/24] VZ: vfio/region: preserve BAR mappings across cpr-exec #VSTOR-103860 Andrey Drobyshev
2026-10-13 16:00   ` no-reply [this message]
2026-10-13 15:28 ` [PATCH QEMU HCI-8.1 v2 24/24] VZ: migration/cpr-exec: allow cpr-exec with VFIO devices #VSTOR-103860 Andrey Drobyshev
2026-10-13 15:57   ` no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=010701a135fecd05-db919099-27ad-4baa-9ef0-265e0a85916d-000000@eu-central-1.amazonses.com \
    --to=no-reply@sashiko.virtuozzo.com \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=svt-core@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox