Merge tag 'pull-vfio-20260707' of https://github.com/legoater/qemu into staging

vfio queue:

* Fixes ROM read issues in vfio/pci: information leak, error
  propagation, and uninitialized state
* Validates VERSION replies in vfio-user and updates the spec
  for DMA access mode bits
* Merges .dma_map_file() into .dma_map() in the iommufd backend
* Reworks switchover-ack to be re-usable and implements the
  VFIO_PRECOPY_INFO_REINIT feature for additional pre-copy
  iterations before switchover
* Adds ATS support for passthrough devices via iommufd
* Fixes translated_addr for non-identity-mapped RAM sections in
  the VFIO listener
* Reject invalid MSI-X Table and PBA BIR values

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEEoPZlSPBIlev+awtgUaNDx8/77KEFAmpMkQ8ACgkQUaNDx8/7
# 7KHM0Q/9H4sFijvDZbLkwSK+lkDBpiuIfQ/2WS6P5jZFIH47q3drqA0GIChNbqMv
# W0ikOGEw09lVYYM1z8+90PRNMgps9TcuCo2PB+bWrlibMbwNZCROjhxzaRvX2u7l
# +VhMpQ7BdpddZt4xuEWzvN3NRUc881iSMjlP60qCRbCfx1enK1XFQapi3pnqBL8C
# h9XPBdbEKiW6q8BBvzGUmHsdfUb/uVMw6/imTaLoFNd1QE0lkNP/JmwZe+SCBmU6
# B44GSQ9XrUaoEZ+jxCtKwOxAr0Z/qwbTVtSI7lzlFB6oi/vZqMgc9l9TE7IlYnaz
# v+7SzTAxzgdsZiaGggsYPg+Nu7AYTa2A7zEDwfcqG6Xtc8Vtx3vGyNXNVrX4Voen
# a6PqdpO8LoVRGt/1V+SXKtyB0IuzHVI76XsZvJTDHHq9hnu3RLTIbTnqz0qJ6lm9
# ii0VqGpsXLX9ru4fVsmLJzvllowYT3aAAJWz5fVCsxLBw1qVXRMWK0Vks0d2m1SW
# HH/4qn/m4u+RrF9f/Ns2AWhuFEOZJvmQXIcgl3ciCuaJaPzhbQO25OKjCl/wBrUe
# jtT6Hqgt6n6+k6iL4qfkA3q6b6ns8Mi4+GsjMeCbNR7UrNlPXwOWq6PrWMAc1V4/
# aW2gK3o9dtL5oguBLCTkNpxgmbKmxB5n5oOp06M7dPM59HeSWpg=
# =flhm
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 07 Jul 2026 07:39:27 CEST
# gpg:                using RSA key A0F66548F04895EBFE6B0B6051A343C7CFFBECA1
# gpg: Good signature from "Cédric Le Goater <clg@redhat.com>" [full]
# gpg:                 aka "Cédric Le Goater <clg@kaod.org>" [full]
# Primary key fingerprint: A0F6 6548 F048 95EB FE6B  0B60 51A3 43C7 CFFB ECA1

* tag 'pull-vfio-20260707' of https://github.com/legoater/qemu: (27 commits)
  vfio/pci: Reject invalid MSI-X Table and PBA BIR values
  backends/iommufd: Fix dev_id and type order in viommu trace
  vfio/listener: Fix translated_addr for non-identity-mapped RAM sections
  vfio/pci: Propagate errors in vfio_pci_load_rom() using Error API
  vfio/pci: Add ats property
  iommufd: Introduce handler for device ATS support
  migration: Fix "switchover" used as a verb in comments and docs
  migration: Refactor migration_completion_precopy() to return bool
  migration: Enable new switchover-ack
  vfio/migration: Check VFIO_PRECOPY_INFO_REINIT during switchover
  vfio/migration: Implement VFIO_PRECOPY_INFO_REINIT feature
  vfio/migration: Add new switchover-ack mechanism
  vfio/migration: Add Error ** parameter to vfio_migration_init()
  vfio/migration: Extract VFIO_MIG_FLAG_DEV_INIT_DATA_SENT sending to helper
  migration: Fail migration if switchover-ack is requested after switchover decision
  migration: Make switchover-ack re-usable
  migration: Rename switchover-ack code to legacy
  migration: Replace switchover_ack_needed SaveVMHandler
  migration: Log the approver in qemu_loadvm_approve_switchover()
  migration: Run final save_query_pending at switchover
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2026-07-07 19:16:58 +02:00
30 changed files with 757 additions and 348 deletions

View File

@@ -638,6 +638,13 @@ static int hiod_iommufd_get_cap(HostIOMMUDevice *hiod, int cap, Error **errp)
}
}
static bool hiod_iommufd_support_ats(HostIOMMUDevice *hiod)
{
HostIOMMUDeviceCaps *caps = &hiod->caps;
return !(caps->hw_caps & IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED);
}
static bool hiod_iommufd_get_pasid_info(HostIOMMUDevice *hiod,
PasidInfo *pasid_info)
{
@@ -660,6 +667,7 @@ static void hiod_iommufd_class_init(ObjectClass *oc, const void *data)
hiodc->get_cap = hiod_iommufd_get_cap;
hiodc->get_pasid_info = hiod_iommufd_get_pasid_info;
hiodc->support_ats = hiod_iommufd_support_ats;
};
static const TypeInfo types[] = {

View File

@@ -21,7 +21,7 @@ iommufd_backend_free_id(int iommufd, uint32_t id, int ret) " iommufd=%d id=%d (%
iommufd_backend_set_dirty(int iommufd, uint32_t hwpt_id, bool start, int ret) " iommufd=%d hwpt=%u enable=%d (%d)"
iommufd_backend_get_dirty_bitmap(int iommufd, uint32_t hwpt_id, uint64_t iova, uint64_t size, uint64_t flags, uint64_t page_size, int ret) " iommufd=%d hwpt=%u iova=0x%"PRIx64" size=0x%"PRIx64" flags=0x%"PRIx64" page_size=0x%"PRIx64" (%d)"
iommufd_backend_invalidate_cache(int iommufd, uint32_t id, uint32_t data_type, uint32_t entry_len, uint32_t entry_num, uint32_t done_num, uint64_t data_ptr, int ret) " iommufd=%d id=%u data_type=%u entry_len=%u entry_num=%u done_num=%u data_ptr=0x%"PRIx64" (%d)"
iommufd_backend_alloc_viommu(int iommufd, uint32_t dev_id, uint32_t type, uint32_t hwpt_id, uint64_t data_ptr, uint32_t data_len, uint32_t viommu_id, int ret) " iommufd=%d type=%u dev_id=%u hwpt_id=%u data_ptr=0x%"PRIx64" data_len=0x%x viommu_id=%u (%d)"
iommufd_backend_alloc_viommu(int iommufd, uint32_t dev_id, uint32_t type, uint32_t hwpt_id, uint64_t data_ptr, uint32_t data_len, uint32_t viommu_id, int ret) " iommufd=%d dev_id=%u type=%u hwpt_id=%u data_ptr=0x%"PRIx64" data_len=0x%x viommu_id=%u (%d)"
iommufd_backend_alloc_vdev(int iommufd, uint32_t dev_id, uint32_t viommu_id, uint64_t virt_id, uint32_t vdev_id, int ret) " iommufd=%d dev_id=%u viommu_id=%u virt_id=0x%"PRIx64" vdev_id=%u (%d)"
iommufd_viommu_alloc_eventq(int iommufd, uint32_t viommu_id, uint32_t type, uint32_t veventq_id, uint32_t veventq_fd, int ret) " iommufd=%d viommu_id=%u type=%u veventq_id=%u veventq_fd=%u (%d)"
iommufd_backend_alloc_hw_queue(int iommufd, uint32_t viommu_id, uint32_t queue_type, uint32_t index, uint64_t addr, uint64_t size, uint32_t queue_id, int ret) " iommufd=%d viommu_id=%u queue_type=%u index=%u addr=0x%"PRIx64" size=0x%"PRIx64" queue_id=%u (%d)"

View File

@@ -23,6 +23,20 @@ and recommends that the initial bytes are sent and loaded in the destination
before stopping the source VM. Enabling this migration capability will
guarantee that and thus, can potentially reduce downtime even further.
For example, in mlx5 devices, the initial bytes hold metadata used for time
consuming pre-allocations of resources on the destination. Although init bytes
may be small in size and sending them may take little time, loading them in the
destination can take a significant amount of time. Switchover-ack guarantees
that this pre-allocation doesn't happen during downtime.
Initial bytes was originally defined to be monotonically decreasing, however
there are cases where a new chunk of initial bytes should be transferred during
precopy, e.g., due to a device reconfiguration, etc. The
VFIO_PRECOPY_INFO_REINIT feature addresses this and when supported, allows to
report a new initial bytes value regardless of any previously reported values.
In this case, a new switchover ACK will be requested to make sure the new
initial bytes are loaded in the destination before switching over.
To support migration of multiple devices that might do P2P transactions between
themselves, VFIO migration uAPI defines an intermediate P2P quiescent state.
While in the P2P quiescent state, P2P DMA transactions cannot be initiated by
@@ -59,9 +73,6 @@ VFIO implements the device hooks for the iterative approach as follows:
* A ``save_live_iterate`` function that reads the VFIO device's data from the
vendor driver during iterative pre-copy phase.
* A ``switchover_ack_needed`` function that checks if the VFIO device uses
"switchover-ack" migration capability when this capability is enabled.
* A ``switchover_start`` function that in the multifd mode starts a thread that
reassembles the multifd received data and loads it in-order into the device.
In the non-multifd mode this function is a NOP.

View File

@@ -604,27 +604,31 @@ Request
The request payload for this message is a structure of the following format:
+-------------+--------+-------------+
| Name | Offset | Size |
+=============+========+=============+
| argsz | 0 | 4 |
+-------------+--------+-------------+
| flags | 4 | 4 |
+-------------+--------+-------------+
| | +-----+------------+ |
| | | Bit | Definition | |
| | +=====+============+ |
| | | 0 | readable | |
| | +-----+------------+ |
| | | 1 | writeable | |
| | +-----+------------+ |
+-------------+--------+-------------+
| offset | 8 | 8 |
+-------------+--------+-------------+
| address | 16 | 8 |
+-------------+--------+-------------+
| size | 24 | 8 |
+-------------+--------+-------------+
+-------------+--------+------------------------+
| Name | Offset | Size |
+=============+========+========================+
| argsz | 0 | 4 |
+-------------+--------+------------------------+
| flags | 4 | 4 |
+-------------+--------+------------------------+
| | +-----+-----------------------+ |
| | | Bit | Definition | |
| | +=====+=======================+ |
| | | 0 | readable | |
| | +-----+-----------------------+ |
| | | 1 | writeable | |
| | +-----+-----------------------+ |
| | | 2 | access mode: mmap | |
| | +-----+-----------------------+ |
| | | 3 | access mode: file I/O | |
| | +-----+-----------------------+ |
+-------------+--------+------------------------+
| offset | 8 | 8 |
+-------------+--------+------------------------+
| address | 16 | 8 |
+-------------+--------+------------------------+
| size | 24 | 8 |
+-------------+--------+------------------------+
* *argsz* is the size of the above structure. Note there is no reply payload,
so this field differs from other message types.
@@ -634,6 +638,8 @@ The request payload for this message is a structure of the following format:
* *writeable* indicates that the region can be written to.
* *access mode* bits indicate how the region is to be accessed by the server.
* *offset* is the file offset of the region with respect to the associated file
descriptor, or zero if the region is not mappable
* *address* is the base DMA address of the region.
@@ -641,16 +647,30 @@ The request payload for this message is a structure of the following format:
This structure is 32 bytes in size, so the message size is 16 + 32 bytes.
If the DMA region being added can be directly mapped by the server, a file
descriptor must be sent as part of the message meta-data. The region can be
mapped via the mmap() system call. On ``AF_UNIX`` sockets, the file descriptor
must be passed as ``SCM_RIGHTS`` type ancillary data. Otherwise, if the DMA
region cannot be directly mapped by the server, no file descriptor must be sent
as part of the message meta-data and the DMA region can be accessed by the
server using ``VFIO_USER_DMA_READ`` and ``VFIO_USER_DMA_WRITE`` messages,
explained in `Read and Write Operations`_. A command to map over an existing
region must be failed by the server with ``EEXIST`` set in error field in the
reply.
There are several alternative access modes for the server to use when accessing
the region:
* ``VFIO_USER_DMA_READ`` and ``VFIO_USER_DMA_WRITE`` messages, explained in
`Read and Write Operations`_.
* ``mmap()`` a client-provided file descriptor, then perform direct accesses to
the underlying memory.
* File I/O system calls (such as ``pread()`` / ``pwrite()``) against a
client-provided file descriptor.
The access mode bits in the flags field indicate which access mode to use. If
an access mode requiring a file descriptor is specified, but the client does
not provide a file descriptor, the server must fail the request with
``EINVAL``. If no access mode flag bit is set, the server should use ``mmap()``
based access if the client provided a file descriptor and message-based access
otherwise.
On ``AF_UNIX`` sockets, the file descriptor must be passed as ``SCM_RIGHTS``
type ancillary data.
A command to map over an existing region must be failed by the server with
``EEXIST`` set in the error field in the reply.
Reply
^^^^^

View File

@@ -48,6 +48,7 @@ GlobalProperty hw_compat_11_0[] = {
{ TYPE_ARM_SMMUV3, "ril", "on" },
{ TYPE_ARM_SMMUV3, "ssidsize", "0" },
{ TYPE_ARM_SMMUV3, "oas", "44" },
{ "migration", "switchover-ack-legacy", "on" },
};
const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);

View File

@@ -190,7 +190,7 @@ static int cmma_save_setup(QEMUFile *f, void *opaque, Error **errp)
}
static void cmma_state_pending(void *opaque, MigPendingData *pending,
bool exact)
bool exact, bool final)
{
S390StAttribState *sas = S390_STATTRIB(opaque);
S390StAttribClass *sac = S390_STATTRIB_GET_CLASS(sas);

View File

@@ -1292,7 +1292,7 @@ bool vfio_user_validate_version(VFIOUserProxy *proxy, Error **errp)
{
g_autofree VFIOUserVersion *msgp = NULL;
GString *caps;
char *reply;
const char *reply = "";
int size, caplen;
caps = caps_json();
@@ -1322,17 +1322,24 @@ bool vfio_user_validate_version(VFIOUserProxy *proxy, Error **errp)
return false;
}
reply = msgp->capabilities;
if (reply[msgp->hdr.size - sizeof(*msgp) - 1] != '\0') {
error_setg(errp, "corrupt version reply");
if (msgp->hdr.size < sizeof(*msgp)) {
error_setg(errp, "short version reply");
return false;
}
if (!caps_check(proxy, msgp->minor, reply, errp)) {
return false;
if (msgp->hdr.size > sizeof(*msgp)) {
reply = msgp->capabilities;
if (reply[msgp->hdr.size - sizeof(*msgp) - 1] != '\0') {
error_setg(errp, "corrupt version reply");
return false;
}
if (!caps_check(proxy, msgp->minor, reply, errp)) {
return false;
}
}
trace_vfio_user_version(msgp->major, msgp->minor, msgp->capabilities);
trace_vfio_user_version(msgp->major, msgp->minor, reply);
return true;
}

View File

@@ -15,7 +15,6 @@
#include <linux/vfio.h>
#include "system/tcg.h"
#include "system/ramblock.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "hw/vfio/vfio-container.h"
@@ -74,49 +73,12 @@ void vfio_address_space_insert(VFIOAddressSpace *space,
bcontainer->space = space;
}
static bool vfio_container_can_dma_map_file(VFIOContainer *bcontainer,
MemoryRegion *mr, int *fd)
{
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
RAMBlock *rb = mr->ram_block;
if (!vioc->dma_map_file || !rb) {
return false;
}
*fd = qemu_ram_get_fd(rb);
if (*fd < 0) {
return false;
}
/*
* We can use IOMMU DMA mapping (IOMMU_IOAS_MAP_FILE) for :
*
* 1) Guest RAM blocks explicitly configured as shared (MAP_SHARED)
* 2) RAM device sub-regions (MMIO BARs)
*
* Private RAM mappings (MAP_PRIVATE) are strictly excluded. Because
* they are subject to copy-on-write (COW) anomalies, their underlying
* PFNs can permanently diverge from the backing file
*/
return qemu_ram_is_shared(rb) || memory_region_is_ram_device(mr);
}
int vfio_container_dma_map(VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
void *vaddr, bool readonly, MemoryRegion *mr)
{
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
int mfd;
if (vfio_container_can_dma_map_file(bcontainer, mr, &mfd)) {
RAMBlock *rb = mr->ram_block;
unsigned long start = vaddr - qemu_ram_get_host_addr(rb);
unsigned long offset = qemu_ram_get_fd_offset(rb);
return vioc->dma_map_file(bcontainer, iova, size, mfd, start + offset,
readonly);
}
g_assert(vioc->dma_map);
return vioc->dma_map(bcontainer, iova, size, vaddr, readonly, mr);
}

View File

@@ -20,6 +20,7 @@
#include "trace.h"
#include "qapi/error.h"
#include "system/iommufd.h"
#include "system/ramblock.h"
#include "hw/core/iommu.h"
#include "hw/core/qdev.h"
#include "hw/vfio/vfio-cpr.h"
@@ -35,28 +36,52 @@
#define TYPE_HOST_IOMMU_DEVICE_IOMMUFD_VFIO \
TYPE_HOST_IOMMU_DEVICE_IOMMUFD "-vfio"
static bool iommufd_cdev_can_map_file_dma(MemoryRegion *mr, int *fd)
{
RAMBlock *rb = mr ? mr->ram_block : NULL;
if (!rb) {
return false;
}
*fd = qemu_ram_get_fd(rb);
if (*fd < 0) {
return false;
}
/*
* Use iommufd_backend_map_file_dma() (IOMMU_IOAS_MAP_FILE) for:
* 1) Guest RAM blocks explicitly configured as shared (MAP_SHARED)
* 2) RAM device sub-regions (MMIO BARs)
*
* Private RAM mappings (MAP_PRIVATE) are excluded: copy-on-write
* semantics can cause their underlying PFNs to permanently diverge
* from the backing file.
*/
return qemu_ram_is_shared(rb) || memory_region_is_ram_device(mr);
}
static int iommufd_cdev_map(const VFIOContainer *bcontainer, hwaddr iova,
uint64_t size, void *vaddr, bool readonly,
MemoryRegion *mr)
{
const VFIOIOMMUFDContainer *container = VFIO_IOMMU_IOMMUFD(bcontainer);
int fd;
return iommufd_backend_map_dma(container->be,
container->ioas_id,
if (iommufd_cdev_can_map_file_dma(mr, &fd)) {
RAMBlock *rb = mr->ram_block;
unsigned long start = vaddr - qemu_ram_get_host_addr(rb);
unsigned long offset = qemu_ram_get_fd_offset(rb);
return iommufd_backend_map_file_dma(container->be, container->ioas_id,
iova, size, fd,
start + offset, readonly);
}
return iommufd_backend_map_dma(container->be, container->ioas_id,
iova, size, vaddr, readonly);
}
static int iommufd_cdev_map_file(const VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
int fd, unsigned long start, bool readonly)
{
const VFIOIOMMUFDContainer *container = VFIO_IOMMU_IOMMUFD(bcontainer);
return iommufd_backend_map_file_dma(container->be,
container->ioas_id,
iova, size, fd, start, readonly);
}
static int iommufd_cdev_unmap(const VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
IOMMUTLBEntry *iotlb, bool unmap_all)
@@ -929,7 +954,6 @@ static void vfio_iommu_iommufd_class_init(ObjectClass *klass, const void *data)
VFIOIOMMUClass *vioc = VFIO_IOMMU_CLASS(klass);
vioc->dma_map = iommufd_cdev_map;
vioc->dma_map_file = iommufd_cdev_map_file;
vioc->dma_unmap = iommufd_cdev_unmap;
vioc->attach_device = iommufd_cdev_attach;
vioc->detach_device = iommufd_cdev_detach;

View File

@@ -731,7 +731,7 @@ static void vfio_listener_region_del(MemoryListener *listener,
}
/*
* Fake an IOTLB entry for writable identity mapping which is needed
* Fake an IOTLB entry for writable RAM sections 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.
@@ -746,7 +746,8 @@ static void vfio_listener_region_del(MemoryListener *listener,
if (global_dirty_tracking && memory_region_is_ram(section->mr) &&
!section->readonly) {
entry.iova = iova;
entry.translated_addr = iova;
entry.translated_addr = memory_region_get_ram_addr(section->mr) +
section->offset_within_region;
iotlb = &entry;
}

View File

@@ -373,9 +373,11 @@ static int vfio_query_stop_copy_size(VFIODevice *vbasedev)
static int vfio_query_precopy_size(VFIOMigration *migration)
{
VFIODevice *vbasedev = migration->vbasedev;
struct vfio_precopy_info precopy = {
.argsz = sizeof(precopy),
};
bool reinit = false;
int ret = 0;
if (ioctl(migration->data_fd, VFIO_MIG_GET_PRECOPY_INFO, &precopy)) {
@@ -383,25 +385,43 @@ static int vfio_query_precopy_size(VFIOMigration *migration)
migration->precopy_dirty_size = 0;
ret = -errno;
warn_report_once("VFIO device %s ioctl(VFIO_MIG_GET_PRECOPY_INFO) "
"failed (%d)", migration->vbasedev->name, ret);
"failed (%d)", vbasedev->name, ret);
} else {
bool overflow;
migration->precopy_init_size = precopy.initial_bytes;
migration->precopy_dirty_size = precopy.dirty_bytes;
/*
* struct vfio_precopy_info.flags is valid only if
* VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 is used.
*/
if (migration->precopy_info_v2_used) {
reinit = precopy.flags & VFIO_PRECOPY_INFO_REINIT;
}
overflow = vfio_migration_check_overflow(migration->vbasedev,
overflow = vfio_migration_check_overflow(vbasedev,
migration->precopy_init_size, "precopy init size");
overflow |= vfio_migration_check_overflow(migration->vbasedev,
overflow |= vfio_migration_check_overflow(vbasedev,
migration->precopy_dirty_size, "precopy dirty size");
if (overflow) {
ret = -ERANGE;
}
}
trace_vfio_query_precopy_size(migration->vbasedev->name,
migration->precopy_init_size,
migration->precopy_dirty_size, ret);
trace_vfio_query_precopy_size(vbasedev->name, migration->precopy_init_size,
migration->precopy_dirty_size, reinit, ret);
/*
* If we got new initial_bytes after previous initial_bytes were
* transferred, request a new switchover ACK. Don't request if legacy
* switchover-ack is used.
*/
if (reinit && migration->initial_data_sent &&
!migrate_switchover_ack_legacy()) {
migration->initial_data_sent = false;
migration->request_switchover_ack = true;
trace_vfio_query_precopy_size_request_switchover_ack(vbasedev->name);
}
return ret;
}
@@ -480,6 +500,26 @@ static void vfio_update_estimated_pending_data(VFIOMigration *migration,
data_size);
}
/* Returns true if the init data flag was sent, false otherwise */
static bool vfio_send_init_data_flag(QEMUFile *f, VFIOMigration *migration)
{
VFIODevice *vbasedev = migration->vbasedev;
if (!migrate_switchover_ack()) {
return false;
}
if (migration->precopy_init_size || migration->initial_data_sent) {
return false;
}
qemu_put_be64(f, VFIO_MIG_FLAG_DEV_INIT_DATA_SENT);
migration->initial_data_sent = true;
trace_vfio_send_init_data_flag(vbasedev->name);
return true;
}
static bool vfio_precopy_supported(VFIODevice *vbasedev)
{
VFIOMigration *migration = vbasedev->migration;
@@ -487,6 +527,14 @@ static bool vfio_precopy_supported(VFIODevice *vbasedev)
return migration->mig_flags & VFIO_MIGRATION_PRE_COPY;
}
static void vfio_request_switchover_ack_legacy(VFIODevice *vbasedev)
{
if (vfio_precopy_supported(vbasedev)) {
/* Precopy support implies switchover-ack is needed */
migration_request_switchover_ack_legacy(vbasedev->name);
}
}
/* ---------------------------------------------------------------------- */
static int vfio_save_prepare(void *opaque, Error **errp)
@@ -554,6 +602,9 @@ static int vfio_save_setup(QEMUFile *f, void *opaque, Error **errp)
}
vfio_query_precopy_size(migration);
if (migrate_switchover_ack() && !migrate_switchover_ack_legacy()) {
migration->request_switchover_ack = true;
}
break;
case VFIO_DEVICE_STATE_STOP:
@@ -606,6 +657,7 @@ static void vfio_save_cleanup(void *opaque)
migration->precopy_init_size = 0;
migration->precopy_dirty_size = 0;
migration->initial_data_sent = false;
migration->request_switchover_ack = false;
vfio_migration_cleanup(vbasedev);
trace_vfio_save_cleanup(vbasedev->name);
}
@@ -622,13 +674,22 @@ static void vfio_state_pending_sync(VFIODevice *vbasedev)
}
static void vfio_state_pending(void *opaque, MigPendingData *pending,
bool exact)
bool exact, bool final)
{
VFIODevice *vbasedev = opaque;
VFIOMigration *migration = vbasedev->migration;
uint64_t precopy_size, stopcopy_size;
bool request_switchover_ack = false;
if (exact) {
/*
* The final pending query runs during switchover downtime. VFIO does not
* need a fresh device pending-data query then to get the latest dirty
* data, so avoid the extra work and report the cached counters below.
* On the other hand, precopy sync is needed to check if switchover ACK was
* requested, but that's already done during guest stop when device is in
* PRE_COPY state.
*/
if (exact && !final) {
vfio_state_pending_sync(vbasedev);
}
@@ -643,10 +704,16 @@ static void vfio_state_pending(void *opaque, MigPendingData *pending,
pending->precopy_bytes += precopy_size;
pending->stopcopy_bytes += stopcopy_size;
if (migration->request_switchover_ack) {
pending->switchover_ack_pending++;
request_switchover_ack = true;
migration->request_switchover_ack = false;
}
trace_vfio_state_pending(vbasedev->name, migration->stopcopy_size,
migration->precopy_init_size,
migration->precopy_dirty_size, exact);
migration->precopy_dirty_size,
request_switchover_ack, exact, final);
}
static bool vfio_is_active_iterate(void *opaque)
@@ -680,11 +747,7 @@ static int vfio_save_iterate(QEMUFile *f, void *opaque)
vfio_update_estimated_pending_data(migration, data_size);
if (migrate_switchover_ack() && !migration->precopy_init_size &&
!migration->initial_data_sent) {
qemu_put_be64(f, VFIO_MIG_FLAG_DEV_INIT_DATA_SENT);
migration->initial_data_sent = true;
} else {
if (!vfio_send_init_data_flag(f, migration)) {
qemu_put_be64(f, VFIO_MIG_FLAG_END_OF_STATE);
}
@@ -771,6 +834,8 @@ static int vfio_load_setup(QEMUFile *f, void *opaque, Error **errp)
return ret;
}
vfio_request_switchover_ack_legacy(vbasedev);
return 0;
}
@@ -842,7 +907,7 @@ static int vfio_load_state(QEMUFile *f, void *opaque, int version_id)
return -EINVAL;
}
ret = qemu_loadvm_approve_switchover();
ret = qemu_loadvm_approve_switchover(vbasedev->name);
if (ret) {
error_report(
"%s: qemu_loadvm_approve_switchover failed, err=%d (%s)",
@@ -869,13 +934,6 @@ static int vfio_load_state(QEMUFile *f, void *opaque, int version_id)
return ret;
}
static bool vfio_switchover_ack_needed(void *opaque)
{
VFIODevice *vbasedev = opaque;
return vfio_precopy_supported(vbasedev);
}
static int vfio_switchover_start(void *opaque)
{
VFIODevice *vbasedev = opaque;
@@ -899,7 +957,6 @@ static const SaveVMHandlers savevm_vfio_handlers = {
.load_setup = vfio_load_setup,
.load_cleanup = vfio_load_cleanup,
.load_state = vfio_load_state,
.switchover_ack_needed = vfio_switchover_ack_needed,
/*
* Multifd support
*/
@@ -910,6 +967,26 @@ static const SaveVMHandlers savevm_vfio_handlers = {
/* ---------------------------------------------------------------------- */
static void vfio_final_precopy_reinit_check(VFIODevice *vbasedev)
{
VFIOMigration *migration = vbasedev->migration;
int ret;
if (!migration->precopy_info_v2_used || !migrate_switchover_ack() ||
migrate_switchover_ack_legacy()) {
return;
}
ret = vfio_query_precopy_size(migration);
if (ret) {
error_report("%s: Final precopy reinit check failed (err: %d)",
vbasedev->name, ret);
/* If query failed, assume reinit and request switchover-ack */
migration->request_switchover_ack = true;
migration->initial_data_sent = false;
}
}
static void vfio_vmstate_change_prepare(void *opaque, bool running,
RunState state)
{
@@ -923,6 +1000,15 @@ static void vfio_vmstate_change_prepare(void *opaque, bool running,
VFIO_DEVICE_STATE_PRE_COPY_P2P :
VFIO_DEVICE_STATE_RUNNING_P2P;
if (migration->device_state == VFIO_DEVICE_STATE_PRE_COPY) {
/*
* Now that vCPUs are stopped, check if new init_bytes are available
* since switchover decision, to be reported in the final
* save_query_pending.
*/
vfio_final_precopy_reinit_check(vbasedev);
}
ret = vfio_migration_set_state_or_reset(vbasedev, new_state, &local_err);
if (ret) {
/*
@@ -1020,6 +1106,27 @@ static int vfio_migration_query_flags(VFIODevice *vbasedev, uint64_t *mig_flags)
return 0;
}
/* Returns 1 on success, 0 if not supported and negative errno on failure */
static int vfio_migration_set_precopy_info_v2(VFIODevice *vbasedev)
{
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature),
sizeof(uint64_t))] = {};
struct vfio_device_feature *feature = (struct vfio_device_feature *)buf;
feature->argsz = sizeof(buf);
feature->flags =
VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2;
if (ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature)) {
if (errno == ENOTTY) {
return 0;
}
return -errno;
}
return 1;
}
static bool vfio_dma_logging_supported(VFIODevice *vbasedev)
{
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature),
@@ -1033,7 +1140,7 @@ static bool vfio_dma_logging_supported(VFIODevice *vbasedev)
return !ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature);
}
static int vfio_migration_init(VFIODevice *vbasedev)
static bool vfio_migration_init(VFIODevice *vbasedev, Error **errp)
{
int ret;
Object *obj;
@@ -1041,25 +1148,45 @@ static int vfio_migration_init(VFIODevice *vbasedev)
char id[256] = "";
g_autofree char *path = NULL, *oid = NULL;
uint64_t mig_flags = 0;
bool precopy_info_v2_used = false;
VMChangeStateHandler *prepare_cb;
if (!vbasedev->ops->vfio_get_object) {
return -EINVAL;
error_setg(errp, "no vfio_get_object handler");
return false;
}
obj = vbasedev->ops->vfio_get_object(vbasedev);
if (!obj) {
return -EINVAL;
error_setg(errp, "failed to get object");
return false;
}
ret = vfio_migration_query_flags(vbasedev, &mig_flags);
if (ret) {
return ret;
if (ret == -ENOTTY) {
error_setg_errno(errp, -ret,
"migration is not supported in kernel");
} else {
error_setg_errno(errp, -ret, "failed to query migration flags");
}
return false;
}
/* Basic migration functionality must be supported */
if (!(mig_flags & VFIO_MIGRATION_STOP_COPY)) {
return -EOPNOTSUPP;
error_setg(errp, "VFIO_MIGRATION_STOP_COPY is not supported");
return false;
}
if (mig_flags & VFIO_MIGRATION_PRE_COPY) {
ret = vfio_migration_set_precopy_info_v2(vbasedev);
if (ret < 0) {
error_setg_errno(errp, -ret, "failed to set precopy info v2");
return false;
}
precopy_info_v2_used = ret;
}
vbasedev->migration = g_new0(VFIOMigration, 1);
@@ -1068,6 +1195,7 @@ static int vfio_migration_init(VFIODevice *vbasedev)
migration->device_state = VFIO_DEVICE_STATE_RUNNING;
migration->data_fd = -1;
migration->mig_flags = mig_flags;
migration->precopy_info_v2_used = precopy_info_v2_used;
vbasedev->dirty_pages_supported = vfio_dma_logging_supported(vbasedev);
@@ -1090,7 +1218,11 @@ static int vfio_migration_init(VFIODevice *vbasedev)
migration_add_notifier(&migration->migration_state,
vfio_migration_state_notifier);
return 0;
trace_vfio_migration_init(vbasedev->name, migration->mig_flags,
migration->precopy_info_v2_used,
vbasedev->dirty_pages_supported);
return true;
}
static Error *multiple_devices_migration_blocker;
@@ -1256,18 +1388,8 @@ bool vfio_migration_realize(VFIODevice *vbasedev, Error **errp)
return !vfio_block_migration(vbasedev, err, errp);
}
ret = vfio_migration_init(vbasedev);
if (ret) {
if (ret == -ENOTTY) {
error_setg(&err, "%s: VFIO migration is not supported in kernel",
vbasedev->name);
} else {
error_setg(&err,
"%s: Migration couldn't be initialized for VFIO device, "
"err: %d (%s)",
vbasedev->name, ret, strerror(-ret));
}
if (!vfio_migration_init(vbasedev, &err)) {
error_prepend(&err, "%s: VFIO migration init failed: ", vbasedev->name);
return !vfio_block_migration(vbasedev, err, errp);
}

View File

@@ -1028,7 +1028,7 @@ static void vfio_update_msi(VFIOPCIDevice *vdev)
}
}
static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
static bool vfio_pci_load_rom(VFIOPCIDevice *vdev, Error **errp)
{
VFIODevice *vbasedev = &vdev->vbasedev;
struct vfio_region_info *reg_info = NULL;
@@ -1041,8 +1041,8 @@ static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
&reg_info);
if (ret != 0) {
error_report("vfio: Error getting ROM info: %s", strerror(-ret));
return;
error_setg_errno(errp, -ret, "vfio: Error getting ROM info");
return false;
}
trace_vfio_pci_load_rom(vbasedev->name, (unsigned long)reg_info->size,
@@ -1053,12 +1053,14 @@ static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
vdev->rom_offset = reg_info->offset;
if (!vdev->rom_size) {
vdev->rom_read_failed = true;
error_report("vfio-pci: Cannot read device rom at %s", vbasedev->name);
error_printf("Device option ROM contents are probably invalid "
"(check dmesg).\nSkip option ROM probe with rombar=0, "
"or load from file with romfile=\n");
return;
vdev->rom_size = 0;
vdev->rom_offset = 0;
error_setg(errp, "vfio-pci: Device ROM size is zero at %s",
vbasedev->name);
error_append_hint(errp, "Device option ROM contents are probably "
"invalid (check dmesg).\nSkip option ROM probe "
"with rombar=0, or load from file with romfile=\n");
return false;
}
vdev->rom = g_malloc(size);
@@ -1078,10 +1080,12 @@ static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
if (bytes == -EINTR || bytes == -EAGAIN) {
continue;
}
error_report("vfio: Error reading device ROM: %s",
strreaderror(bytes));
break;
error_setg_errno(errp, -bytes, "vfio: Error reading device ROM");
g_free(vdev->rom);
vdev->rom = NULL;
vdev->rom_size = 0;
vdev->rom_offset = 0;
return false;
}
}
@@ -1114,6 +1118,8 @@ static void vfio_pci_load_rom(VFIOPCIDevice *vdev)
data[6] = -csum;
}
}
return true;
}
/* "Raw" read of underlying config space. */
@@ -1142,12 +1148,17 @@ static uint64_t vfio_rom_read(void *opaque, hwaddr addr, unsigned size)
uint16_t word;
uint32_t dword;
uint64_t qword;
} val;
} val = { .qword = ~0ULL };
uint64_t data = 0;
/* Load the ROM lazily when the guest tries to read it */
if (unlikely(!vdev->rom && !vdev->rom_read_failed)) {
vfio_pci_load_rom(vdev);
Error *local_err = NULL;
vdev->rom_read_failed = !vfio_pci_load_rom(vdev, &local_err);
if (vdev->rom_read_failed) {
error_report_err(local_err);
}
}
memcpy(&val, vdev->rom + addr,
@@ -1780,6 +1791,14 @@ static bool vfio_msix_early_setup(VFIOPCIDevice *vdev, Error **errp)
msix->pba_offset = pba & ~PCI_MSIX_FLAGS_BIRMASK;
msix->entries = (ctrl & PCI_MSIX_FLAGS_QSIZE) + 1;
if (msix->table_bar >= ARRAY_SIZE(vdev->bars) ||
msix->pba_bar >= ARRAY_SIZE(vdev->bars)) {
error_setg(errp, "invalid MSI-X BIR, table_bar=%d pba_bar=%d",
msix->table_bar, msix->pba_bar);
g_free(msix);
return false;
}
ret = vfio_device_get_irq_info(&vdev->vbasedev, VFIO_PCI_MSIX_IRQ_INDEX,
&irq_info);
if (ret < 0) {
@@ -2547,10 +2566,53 @@ static bool vfio_pci_synthesize_pasid_cap(VFIOPCIDevice *vdev, Error **errp)
return true;
}
static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
/*
* Determine whether ATS capability should be advertised for @vdev, based on
* whether it was enabled on the command line and whether it is supported
* according to the kernel.
*
* Store whether ATS capability should be advertised in @ats_needed.
*
* Returns false only when ats=on is explicitly requested but the kernel
* reports it is not supported. Returns true in all other cases.
*/
static bool vfio_pci_ats_requested_and_supported(VFIOPCIDevice *vdev,
bool *ats_needed, Error **errp)
{
HostIOMMUDevice *hiod = vdev->vbasedev.hiod;
HostIOMMUDeviceClass *hiodc;
bool ats_supported;
*ats_needed = false;
if (vdev->ats == ON_OFF_AUTO_OFF) {
return true;
}
*ats_needed = true;
if (!hiod) {
return true;
}
hiodc = HOST_IOMMU_DEVICE_GET_CLASS(hiod);
if (!hiodc || !hiodc->support_ats) {
return true;
}
ats_supported = hiodc->support_ats(hiod);
if (vdev->ats == ON_OFF_AUTO_ON && !ats_supported) {
error_setg(errp, "vfio-pci: ATS requested but not supported by kernel");
*ats_needed = false;
return false;
}
*ats_needed = ats_supported;
return true;
}
static void vfio_add_ext_cap(VFIOPCIDevice *vdev, bool ats_needed)
{
PCIDevice *pdev = PCI_DEVICE(vdev);
bool pasid_cap_added = false;
bool ats_cap_present = false;
Error *err = NULL;
uint32_t header;
uint16_t cap_id, next, size;
@@ -2636,7 +2698,19 @@ static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
*/
case PCI_EXT_CAP_ID_PASID:
pasid_cap_added = true;
/* fallthrough */
pcie_add_capability(pdev, cap_id, cap_ver, next, size);
break;
case PCI_EXT_CAP_ID_ATS:
ats_cap_present = true;
/*
* If ATS is requested and supported according to the kernel, add
* the ATS capability. If not supported according to the kernel or
* disabled on the qemu command line, omit the ATS cap.
*/
if (ats_needed) {
pcie_add_capability(pdev, cap_id, cap_ver, next, size);
}
break;
default:
pcie_add_capability(pdev, cap_id, cap_ver, next, size);
}
@@ -2647,6 +2721,16 @@ static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
error_report_err(err);
}
if (vdev->ats == ON_OFF_AUTO_ON && !ats_cap_present) {
warn_report("vfio-pci: ats=on requested, but host device has no "
"ATS extended capability");
}
if (vdev->ats == ON_OFF_AUTO_AUTO && ats_cap_present && !ats_needed) {
warn_report("vfio-pci: host kernel reports ATS unsupported; "
"ATS capability will be masked");
}
/* Cleanup chain head ID if necessary */
if (pci_get_word(pdev->config + PCI_CONFIG_SPACE_SIZE) == 0xFFFF) {
pci_set_word(pdev->config + PCI_CONFIG_SPACE_SIZE, 0);
@@ -2658,6 +2742,7 @@ static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
bool vfio_pci_add_capabilities(VFIOPCIDevice *vdev, Error **errp)
{
PCIDevice *pdev = PCI_DEVICE(vdev);
bool ats_needed = false;
if (!(pdev->config[PCI_STATUS] & PCI_STATUS_CAP_LIST) ||
!pdev->config[PCI_CAPABILITY_LIST]) {
@@ -2668,7 +2753,11 @@ bool vfio_pci_add_capabilities(VFIOPCIDevice *vdev, Error **errp)
return false;
}
vfio_add_ext_cap(vdev);
if (!vfio_pci_ats_requested_and_supported(vdev, &ats_needed, errp)) {
return false;
}
vfio_add_ext_cap(vdev, ats_needed);
return true;
}
@@ -3818,6 +3907,7 @@ static const Property vfio_pci_properties[] = {
DEFINE_PROP_BOOL("skip-vsc-check", VFIOPCIDevice, skip_vsc_check, true),
DEFINE_PROP_UINT16("x-vpasid-cap-offset", VFIOPCIDevice,
vpasid_cap_offset, 0),
DEFINE_PROP_ON_OFF_AUTO("ats", VFIOPCIDevice, ats, ON_OFF_AUTO_AUTO),
};
static void vfio_pci_set_fd(Object *obj, const char *str, Error **errp)
@@ -3969,13 +4059,22 @@ static void vfio_pci_class_init(ObjectClass *klass, const void *data)
"destination when doing live "
"migration of device state via "
"multifd channels");
object_class_property_set_description(klass, /* 11.0 */
object_class_property_set_description(klass, /* 11.0 */
"x-vpasid-cap-offset",
"PCIe extended configuration space offset at which to place a "
"synthetic PASID extended capability when PASID is enabled via "
"a vIOMMU. A value of 0 (default) places the capability at the "
"end of the extended configuration space. The offset must be "
"4-byte aligned and within the PCIe extended configuration space");
object_class_property_set_description(klass, /* 11.1 */
"ats",
"Control guest visibility of the ATS PCIe extended capability. "
"Valid values are on, off, and auto (default). "
"'off' always masks ATS. "
"'on' requires ATS support for the device and fails realize if the "
"host kernel reports ATS as unavailable for this device. "
"'auto' masks ATS only when the host kernel reports "
"ATS as unavailable");
}
static const TypeInfo vfio_pci_info = {

View File

@@ -188,6 +188,7 @@ struct VFIOPCIDevice {
bool clear_parent_atomics_on_exit;
bool skip_vsc_check;
uint16_t vpasid_cap_offset;
OnOffAuto ats;
VFIODisplay *dpy;
Notifier irqchip_change_notifier;
VFIOPCICPR cpr;

View File

@@ -159,11 +159,13 @@ vfio_load_state_device_buffer_starved(const char *name, uint32_t idx) " (%s) idx
vfio_load_state_device_buffer_load_start(const char *name, uint32_t idx) " (%s) idx %"PRIu32
vfio_load_state_device_buffer_load_end(const char *name, uint32_t idx) " (%s) idx %"PRIu32
vfio_load_state_device_buffer_end(const char *name) " (%s)"
vfio_migration_init(const char *name, uint64_t mig_flags, bool precopy_info_v2_used, bool dirty_pages_supported) " (%s) mig_flags 0x%"PRIx64", precopy_info_v2_used %d, dirty_pages_supported %d"
vfio_migration_realize(const char *name) " (%s)"
vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s"
vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s"
vfio_migration_state_notifier(const char *name, int state) " (%s) state %d"
vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, int ret) " (%s) init %"PRIu64" dirty %"PRIu64" ret %d"
vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, bool reinit, int ret) " (%s) init %"PRIu64", dirty %"PRIu64", reinit %d, ret %d"
vfio_query_precopy_size_request_switchover_ack(const char *name) " (%s)"
vfio_query_stop_copy_size(const char *name, uint64_t size, int ret) " (%s) stopcopy size %"PRIu64" ret %d"
vfio_save_block(const char *name, int data_size) " (%s) data_size %d"
vfio_save_block_precopy_empty_hit(const char *name) " (%s)"
@@ -176,7 +178,8 @@ vfio_save_device_config_state(const char *name) " (%s)"
vfio_save_iterate(const char *name, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy initial size %"PRIu64" precopy dirty size %"PRIu64
vfio_save_iterate_start(const char *name) " (%s)"
vfio_save_setup(const char *name, uint64_t data_buffer_size) " (%s) data buffer size %"PRIu64
vfio_state_pending(const char *name, uint64_t stopcopy_size, uint64_t precopy_init_size, uint64_t precopy_dirty_size, bool exact) " (%s) stopcopy size %"PRIu64" precopy initial size %"PRIu64" precopy dirty size %"PRIu64 " exact %d"
vfio_state_pending(const char *name, uint64_t stopcopy_size, uint64_t precopy_init_size, uint64_t precopy_dirty_size, bool request_switchover_ack, bool exact, bool final) " (%s) stopcopy size %"PRIu64", precopy initial size %"PRIu64", precopy dirty size %"PRIu64", request switchover ack %d, exact %d, final %d"
vfio_send_init_data_flag(const char *name) " (%s)"
vfio_vmstate_change(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s"
vfio_vmstate_change_prepare(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s"

View File

@@ -45,6 +45,7 @@ typedef struct VFIOMigration {
void *data_buffer;
size_t data_buffer_size;
uint64_t mig_flags;
bool precopy_info_v2_used;
/*
* NOTE: all three sizes cached are reported from VFIO's uAPI, which
* are defined as estimate only. QEMU should not trust these values
@@ -58,6 +59,7 @@ typedef struct VFIOMigration {
bool multifd_transfer;
VFIOMultifd *multifd;
bool initial_data_sent;
bool request_switchover_ack;
bool event_save_iterate_started;
bool event_precopy_empty_hit;

View File

@@ -172,21 +172,6 @@ struct VFIOIOMMUClass {
int (*dma_map)(const VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
void *vaddr, bool readonly, MemoryRegion *mr);
/**
* @dma_map_file
*
* Map a file range for the container.
*
* @bcontainer: #VFIOContainer to use for map
* @iova: start address to map
* @size: size of the range to map
* @fd: descriptor of the file to map
* @start: starting file offset of the range to map
* @readonly: map read only if true
*/
int (*dma_map_file)(const VFIOContainer *bcontainer,
hwaddr iova, uint64_t size,
int fd, unsigned long start, bool readonly);
/**
* @dma_unmap
*

View File

@@ -13,6 +13,7 @@
/* properties */
bool migrate_send_switchover_start(void);
bool migrate_switchover_ack_legacy(void);
/* capabilities */

View File

@@ -159,4 +159,6 @@ bool multifd_device_state_save_thread_should_exit(void);
void multifd_abort_device_state_save_threads(void);
bool multifd_join_device_state_save_threads(void);
void migration_request_switchover_ack_legacy(const char *requester);
#endif

View File

@@ -23,6 +23,8 @@ typedef struct MigPendingData {
uint64_t postcopy_bytes;
/* Amount of pending bytes can be transferred only in stopcopy */
uint64_t stopcopy_bytes;
/* Number of new pending switchover ACKs */
uint32_t switchover_ack_pending;
/*
* Total pending data, modules do not need to update this field, it
* will be automatically calculated by migration core API.
@@ -171,6 +173,28 @@ typedef struct SaveVMHandlers {
*/
bool (*is_active_iterate)(void *opaque);
/**
* @save_query_pending
*
* This estimates the remaining data to transfer on the source side.
*
* When @exact is true, a module must report accurate results. When
* @exact is false, a module may report estimates.
*
* It's highly recommended that modules implement a faster version of
* the query path (for example, by proper caching on the counters) if
* an accurate query will be time-consuming.
*
* @opaque: data pointer passed to register_savevm_live()
* @pending: pointer to a MigPendingData struct
* @exact: set to true for an accurate (slow) query
* @final: set to true for the final query during switchover. When final is
* true, the query is called with BQL locked. Otherwise, it's called with
* BQL unlocked.
*/
void (*save_query_pending)(void *opaque, MigPendingData *pending,
bool exact, bool final);
/* This runs outside the BQL in the migration case, and
* within the lock in the savevm case. The callback had better only
* use data that is local to the migration thread or protected
@@ -210,25 +234,6 @@ typedef struct SaveVMHandlers {
*/
bool (*save_postcopy_prepare)(QEMUFile *f, void *opaque, Error **errp);
/**
* @save_query_pending
*
* This estimates the remaining data to transfer on the source side.
*
* When @exact is true, a module must report accurate results. When
* @exact is false, a module may report estimates.
*
* It's highly recommended that modules implement a faster version of
* the query path (for example, by proper caching on the counters) if
* an accurate query will be time-consuming.
*
* @opaque: data pointer passed to register_savevm_live()
* @pending: pointer to a MigPendingData struct
* @exact: set to true for an accurate (slow) query
*/
void (*save_query_pending)(void *opaque, MigPendingData *pending,
bool exact);
/**
* @load_state
*
@@ -299,19 +304,6 @@ typedef struct SaveVMHandlers {
*/
int (*resume_prepare)(MigrationState *s, void *opaque);
/**
* @switchover_ack_needed
*
* Checks if switchover ack should be used. Called only on
* destination.
*
* @opaque: data pointer passed to register_savevm_live()
*
* Returns true if switchover ack should be used and false
* otherwise
*/
bool (*switchover_ack_needed)(void *opaque);
/**
* @switchover_start
*

View File

@@ -133,6 +133,15 @@ struct HostIOMMUDeviceClass {
* Returns: true on success, false on failure.
*/
bool (*get_pasid_info)(HostIOMMUDevice *hiod, PasidInfo *pasid_info);
/**
* @support_ats: Returns true if ATS can be used by the device,
* false if the host IOMMU reports it is unavailable.
*
* @hiod: handle to the host IOMMU device
*
* Returns: true if ATS is supported, false otherwise
*/
bool (*support_ats)(HostIOMMUDevice *hiod);
};
/*

View File

@@ -767,13 +767,16 @@ static int dirty_bitmap_save_complete(QEMUFile *f, void *opaque)
}
static void dirty_bitmap_state_pending(void *opaque, MigPendingData *data,
bool exact)
bool exact, bool final)
{
DBMSaveState *s = &((DBMState *)opaque)->save;
SaveBitmapState *dbms;
uint64_t pending = 0;
bql_lock();
/* Final pending query is called with BQL locked */
if (!final) {
bql_lock();
}
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
uint64_t gran = bdrv_dirty_bitmap_granularity(dbms->bitmap);
@@ -783,7 +786,9 @@ static void dirty_bitmap_state_pending(void *opaque, MigPendingData *data,
pending += DIV_ROUND_UP(sectors * BDRV_SECTOR_SIZE, gran);
}
bql_unlock();
if (!final) {
bql_unlock();
}
trace_dirty_bitmap_state_pending(pending);

View File

@@ -409,6 +409,7 @@ static int colo_do_checkpoint_transaction(MigrationState *s,
QEMUFile *fb)
{
Error *local_err = NULL;
MigPendingData pending = {};
int ret = -1;
colo_send_message(s->to_dst_file, COLO_MESSAGE_CHECKPOINT_REQUEST,
@@ -465,6 +466,19 @@ static int colo_do_checkpoint_transaction(MigrationState *s,
if (migrate_auto_converge()) {
mig_throttle_counter_reset();
}
/*
* Run the final pending query so migration modules can flush their dirty
* state (e.g., RAM syncs its dirty bitmap) before this checkpoint's live
* state is saved. Unlike a regular switchover, COLO reaches completion
* repeatedly for every checkpoint, so this must be done on each one.
*/
if (!qemu_savevm_query_pending_final(s, &pending, &local_err)) {
ret = -1;
bql_unlock();
goto out;
}
/*
* Only save VM's live state, which not including device state.
* TODO: We may need a timeout mechanism to prevent COLO process

View File

@@ -1713,7 +1713,9 @@ int migrate_init(MigrationState *s, Error **errp)
s->vm_old_state = -1;
s->iteration_initial_bytes = 0;
s->threshold_size = 0;
s->switchover_acked = false;
/* Legacy switchover-ack sends a single ACK for all devices */
qatomic_set(&s->switchover_ack_pending_num,
migrate_switchover_ack_legacy() ? 1 : 0);
s->rdma_migration = false;
/*
@@ -2202,6 +2204,21 @@ void migration_rp_kick(MigrationState *s)
qemu_sem_post(&s->rp_state.rp_sem);
}
/* This is called only on destination side */
void migration_request_switchover_ack_legacy(const char *requester)
{
MigrationIncomingState *mis = migration_incoming_get_current();
if (!migrate_switchover_ack() || !migrate_switchover_ack_legacy()) {
return;
}
mis->switchover_ack_pending_num_legacy++;
trace_migration_request_switchover_ack_legacy(
requester, mis->switchover_ack_pending_num_legacy);
}
static struct rp_cmd_args {
ssize_t len; /* -1 = variable */
const char *name;
@@ -2448,9 +2465,18 @@ static void *source_return_path_thread(void *opaque)
break;
case MIG_RP_MSG_SWITCHOVER_ACK:
ms->switchover_acked = true;
trace_source_return_path_thread_switchover_acked();
{
uint32_t pending_num;
pending_num = qatomic_dec_fetch(&ms->switchover_ack_pending_num);
trace_source_return_path_thread_switchover_acked(pending_num);
if (pending_num == UINT32_MAX) {
error_setg(&err, "Switchover ack pending num underflowed");
goto out;
}
break;
}
default:
break;
@@ -2633,8 +2659,7 @@ static int postcopy_start(MigrationState *ms, Error **errp)
*/
qemu_savevm_send_postcopy_listen(fb);
ret = qemu_savevm_state_non_iterable(fb, errp);
if (ret) {
if (!qemu_savevm_state_non_iterable(fb, errp)) {
error_prepend(errp, "Postcopy save non-iterable states failed: ");
goto fail_closefb;
}
@@ -2793,12 +2818,24 @@ static bool migration_switchover_prepare(MigrationState *s)
static bool migration_switchover_start(MigrationState *s, Error **errp)
{
ERRP_GUARD();
MigPendingData pending = {};
if (!migration_switchover_prepare(s)) {
error_setg(errp, "Switchover is interrupted");
return false;
}
/*
* The final query to the whole system on dirty data to make sure we
* collect the latest status of the VM. For precopy, source QEMU will
* dump all the dirty data during switchover. For postcopy, this will
* properly update all the dirty bitmaps to finally generate the
* correct discard bitmaps; see ram_postcopy_send_discard_bitmap().
*/
if (!qemu_savevm_query_pending_final(s, &pending, errp)) {
return false;
}
/* Inactivate disks except in COLO */
if (!migrate_colo()) {
/*
@@ -2820,25 +2857,26 @@ static bool migration_switchover_start(MigrationState *s, Error **errp)
return true;
}
static int migration_completion_precopy(MigrationState *s)
static bool migration_completion_precopy(MigrationState *s, Error **errp)
{
int ret;
bool ret = false;
bql_lock();
if (!migrate_mode_is_cpr()) {
ret = migration_stop_vm(s, RUN_STATE_FINISH_MIGRATE);
if (ret < 0) {
int r = migration_stop_vm(s, RUN_STATE_FINISH_MIGRATE);
if (r < 0) {
error_setg_errno(errp, -r, "Failed to stop the VM");
goto out_unlock;
}
}
if (!migration_switchover_start(s, NULL)) {
ret = -EFAULT;
if (!migration_switchover_start(s, errp)) {
goto out_unlock;
}
ret = qemu_savevm_state_complete_precopy(s);
ret = qemu_savevm_state_complete_precopy(s, errp);
out_unlock:
bql_unlock();
return ret;
@@ -2871,18 +2909,17 @@ static void migration_completion_postcopy(MigrationState *s)
*/
static void migration_completion(MigrationState *s)
{
int ret = 0;
Error *local_err = NULL;
if (s->state == MIGRATION_STATUS_ACTIVE) {
ret = migration_completion_precopy(s);
if (!migration_completion_precopy(s, &local_err)) {
goto fail;
}
} else if (s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
migration_completion_postcopy(s);
} else {
ret = -1;
}
if (ret < 0) {
error_setg(&local_err, "Unexpected migration completion status %s",
MigrationStatus_str(s->state));
goto fail;
}
@@ -2906,10 +2943,7 @@ static void migration_completion(MigrationState *s)
return;
fail:
if (qemu_file_get_error_obj(s->to_dst_file, &local_err)) {
migrate_error_propagate(s, local_err);
} else if (ret) {
error_setg_errno(&local_err, -ret, "Error in migration completion");
if (local_err || qemu_file_get_error_obj(s->to_dst_file, &local_err)) {
migrate_error_propagate(s, local_err);
}
@@ -3244,7 +3278,7 @@ static bool migration_can_switchover(MigrationState *s)
return true;
}
return s->switchover_acked;
return qatomic_read(&s->switchover_ack_pending_num) == 0;
}
/* Migration thread iteration status */
@@ -3259,7 +3293,7 @@ static bool migration_iteration_next_ready(MigrationState *s,
MigPendingData *pending)
{
/*
* If the estimated values already suggest us to switchover, mark this
* If the estimated values already suggest us to switch over, mark this
* iteration finished, time to do a slow sync.
*/
if (pending->total_bytes <= s->threshold_size) {
@@ -3283,12 +3317,13 @@ static bool migration_iteration_next_ready(MigrationState *s,
return false;
}
static void migration_iteration_go_next(MigPendingData *pending)
static void migration_iteration_go_next(MigrationState *s,
MigPendingData *pending)
{
/*
* Do a slow sync first before boosting the iteration count.
*/
qemu_savevm_query_pending(pending, true);
qemu_savevm_query_pending_iter(s, pending, true);
/*
* Update the dirty information for the whole system for this
@@ -3334,12 +3369,12 @@ static MigIterateState migration_iteration_run(MigrationState *s)
Error *local_err = NULL;
bool in_postcopy = (s->state == MIGRATION_STATUS_POSTCOPY_DEVICE ||
s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE);
bool can_switchover = migration_can_switchover(s);
bool can_switchover;
MigPendingData pending = { };
bool complete_ready;
/* Fast path - get the estimated amount of pending data */
qemu_savevm_query_pending(&pending, false);
qemu_savevm_query_pending_iter(s, &pending, false);
if (in_postcopy) {
/*
@@ -3380,9 +3415,12 @@ static MigIterateState migration_iteration_run(MigrationState *s)
* during postcopy phase.
*/
if (migration_iteration_next_ready(s, &pending)) {
migration_iteration_go_next(&pending);
migration_iteration_go_next(s, &pending);
}
/* Check if we can switch over after qemu_savevm_query_pending() */
can_switchover = migration_can_switchover(s);
/* Should we switch to postcopy now? */
if (can_switchover && postcopy_should_start(s, &pending)) {
if (postcopy_start(s, &local_err)) {
@@ -3818,7 +3856,7 @@ static void *bg_migration_thread(void *opaque)
goto fail_with_bql;
}
if (qemu_savevm_state_non_iterable(fb, &local_err)) {
if (!qemu_savevm_state_non_iterable(fb, &local_err)) {
error_prepend(&local_err, "Failed to save non-iterable devices ");
goto fail_with_bql;
}

View File

@@ -246,7 +246,7 @@ struct MigrationIncomingState {
* zero an ACK that it's OK to do switchover is sent to the source. No lock
* is needed as this field is updated serially.
*/
unsigned int switchover_ack_pending_num;
unsigned int switchover_ack_pending_num_legacy;
/* Do exit on incoming migration failure */
bool exit_on_error;
@@ -487,6 +487,29 @@ struct MigrationState {
*/
uint8_t clear_bitmap_shift;
/*
* This decides whether to use legacy switchover-ack or new switchover-ack.
* The main difference between them is that the former allows acknowledging
* switchover only once while the latter multiple times.
*
* In legacy, the destination keeps track of a pending ACKs counter. As
* migration progresses, the devices on the destination acknowledge
* switchover, decreasing the counter. When the counter reaches zero, a
* single ACK message is sent to the source via the return path, indicating
* that it's OK to switch over.
*
* In new switchover-ack, the source is the one that keeps track of a
* pending ACKs counter. As migration progresses, the destination sends ACK
* message per-device via the return path, which decrements the source
* counter. When the counter reaches zero, it's OK to switch over. During
* precopy, source-side devices may request additional ACKs, which increment
* the counter again.
*
* In both legacy and new schemes, we rely on per-device protocol to request
* switchover ACK from the destination-side counterpart.
*/
bool switchover_ack_legacy;
/*
* This save hostname when out-going migration starts
*/
@@ -496,10 +519,13 @@ struct MigrationState {
JSONWriter *vmdesc;
/*
* Indicates whether an ACK from the destination that it's OK to do
* switchover has been received.
* Indicates the number of pending ACKs from the destination. The value may
* increase or decrease during precopy as new ACKs are requested or
* received. When zero is reached, it's OK to switch over. In legacy
* switchover-ack, it's initialized to 1 and decreased to zero upon ACK.
*/
bool switchover_acked;
uint32_t switchover_ack_pending_num;
/* Is this a rdma migration */
bool rdma_migration;

View File

@@ -110,6 +110,8 @@ const Property migration_properties[] = {
preempt_pre_7_2, false),
DEFINE_PROP_BOOL("multifd-clean-tls-termination", MigrationState,
multifd_clean_tls_termination, true),
DEFINE_PROP_BOOL("switchover-ack-legacy", MigrationState,
switchover_ack_legacy, false),
/* Migration parameters */
DEFINE_PROP_UINT8("x-throttle-trigger-threshold", MigrationState,
@@ -467,6 +469,13 @@ bool migrate_rdma(void)
return s->rdma_migration;
}
bool migrate_switchover_ack_legacy(void)
{
MigrationState *s = migrate_get_current();
return s->switchover_ack_legacy;
}
typedef enum WriteTrackingSupport {
WT_SUPPORT_UNKNOWN = 0,
WT_SUPPORT_ABSENT,

View File

@@ -2686,9 +2686,6 @@ void ram_postcopy_send_discard_bitmap(MigrationState *ms)
RCU_READ_LOCK_GUARD();
/* This should be our last sync, the src is now paused */
migration_bitmap_sync(rs, false);
/* Easiest way to make sure we don't resume in the middle of a host-page */
rs->pss[RAM_CHANNEL_PRECOPY].last_sent_block = NULL;
rs->last_seen_block = NULL;
@@ -3376,10 +3373,6 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
rs->last_stage = !migration_in_colo_state();
WITH_RCU_READ_LOCK_GUARD() {
if (!migration_in_postcopy()) {
migration_bitmap_sync_precopy(true);
}
ret = rdma_registration_start(f, RAM_CONTROL_FINISH);
if (ret < 0) {
qemu_file_set_error(f, ret);
@@ -3442,25 +3435,38 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
return qemu_fflush(f);
}
static void ram_state_pending_sync(bool exact, bool final)
{
/*
* Sync is not needed either with: (1) a fast query, or (2) after
* postcopy has started (no new dirty will generate anymore).
*/
if (!exact || migration_in_postcopy()) {
return;
}
/* Final pending query is called with BQL locked */
if (!final) {
bql_lock();
}
WITH_RCU_READ_LOCK_GUARD() {
migration_bitmap_sync_precopy(final);
}
if (!final) {
bql_unlock();
}
}
static void ram_state_pending(void *opaque, MigPendingData *pending,
bool exact)
bool exact, bool final)
{
RAMState **temp = opaque;
RAMState *rs = *temp;
uint64_t remaining_size;
/*
* Sync is not needed either with: (1) a fast query, or (2) after
* postcopy has started (no new dirty will generate anymore).
*/
if (exact && !migration_in_postcopy()) {
bql_lock();
WITH_RCU_READ_LOCK_GUARD() {
migration_bitmap_sync_precopy(false);
}
bql_unlock();
}
ram_state_pending_sync(exact, final);
remaining_size = rs->migration_dirty_pages * TARGET_PAGE_SIZE;
if (migrate_postcopy_ram()) {

View File

@@ -1737,13 +1737,12 @@ void qemu_savevm_state_end_precopy(MigrationState *s, QEMUFile *f)
qemu_savevm_state_vm_desc(s, f);
}
int qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp)
bool qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp)
{
MigrationState *ms = migrate_get_current();
int64_t start_ts_each, end_ts_each;
JSONWriter *vmdesc = ms->vmdesc;
SaveStateEntry *se;
int ret;
/* Making sure cpu states are synchronized before saving non-iterable */
cpu_synchronize_all_states();
@@ -1756,9 +1755,8 @@ int qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp)
start_ts_each = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
ret = vmstate_save(f, se, vmdesc, errp);
if (ret) {
return ret;
if (vmstate_save(f, se, vmdesc, errp) < 0) {
return false;
}
end_ts_each = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
@@ -1768,34 +1766,41 @@ int qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp)
trace_vmstate_downtime_checkpoint("src-non-iterable-saved");
return 0;
return true;
}
int qemu_savevm_state_complete_precopy(MigrationState *s)
bool qemu_savevm_state_complete_precopy(MigrationState *s, Error **errp)
{
ERRP_GUARD();
QEMUFile *f = s->to_dst_file;
Error *local_err = NULL;
int ret;
ret = qemu_savevm_state_complete_precopy_iterable(f, false);
if (ret) {
return ret;
qemu_file_get_error_obj(f, errp);
error_prepend(errp, "Failed to save iterable device state: ");
return false;
}
/* TODO: pass error upper */
ret = qemu_savevm_state_non_iterable(f, &local_err);
if (ret) {
migrate_error_propagate(s, error_copy(local_err));
error_report_err(local_err);
return ret;
if (!qemu_savevm_state_non_iterable(f, errp)) {
return false;
}
qemu_savevm_state_end_precopy(s, f);
return qemu_fflush(f);
ret = qemu_fflush(f);
if (ret) {
qemu_file_get_error_obj(f, errp);
error_prepend(errp, "Failed to flush QEMUFile: ");
return false;
}
return true;
}
void qemu_savevm_query_pending(MigPendingData *pending, bool exact)
static void qemu_savevm_query_pending(MigrationState *s,
MigPendingData *pending, bool exact,
bool final)
{
SaveStateEntry *se;
@@ -1808,7 +1813,7 @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact)
if (!qemu_savevm_state_active(se)) {
continue;
}
se->ops->save_query_pending(se->opaque, pending, exact);
se->ops->save_query_pending(se->opaque, pending, exact, final);
}
pending->total_bytes = pending->precopy_bytes +
@@ -1821,10 +1826,51 @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact)
*/
mig_stats.dirty_bytes_total = pending->total_bytes;
trace_qemu_savevm_query_pending(exact, pending->precopy_bytes,
pending->stopcopy_bytes,
pending->postcopy_bytes,
pending->total_bytes);
if (migrate_switchover_ack() && !migrate_switchover_ack_legacy() &&
pending->switchover_ack_pending) {
/*
* NOTE: Currently we rely on per-device protocol to request switchover
* ACK from the device on the destination side.
*/
qatomic_add(&s->switchover_ack_pending_num,
pending->switchover_ack_pending);
}
trace_qemu_savevm_query_pending(
exact, final, pending->precopy_bytes, pending->stopcopy_bytes,
pending->postcopy_bytes, pending->total_bytes,
pending->switchover_ack_pending,
qatomic_read(&s->switchover_ack_pending_num));
}
void qemu_savevm_query_pending_iter(MigrationState *s, MigPendingData *pending,
bool exact)
{
qemu_savevm_query_pending(s, pending, exact, false);
}
bool qemu_savevm_query_pending_final(MigrationState *s, MigPendingData *pending,
Error **errp)
{
g_assert(bql_locked());
qemu_savevm_query_pending(s, pending, true, true);
/*
* Switchover-ack requests done after switchover decision are not allowed.
* Fail the migration in this case since we currently don't support going
* back to precopy.
*/
if (migrate_switchover_ack() && !migrate_switchover_ack_legacy() &&
pending->switchover_ack_pending > 0) {
error_setg(errp,
"Switchover ACK was requested by %" PRIu32
" devices during switchover",
pending->switchover_ack_pending);
return false;
}
return true;
}
void qemu_savevm_state_cleanup(void)
@@ -1874,12 +1920,13 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp)
}
ret = qemu_file_get_error(f);
if (ret == 0) {
qemu_savevm_state_complete_precopy(ms);
ret = qemu_file_get_error(f);
}
if (ret != 0) {
if (ret) {
error_setg_errno(errp, -ret, "Error while writing VM state");
goto cleanup;
}
if (!qemu_savevm_state_complete_precopy(ms, errp)) {
ret = -1;
}
cleanup:
qemu_savevm_state_cleanup();
@@ -1914,9 +1961,8 @@ int qemu_save_device_state(QEMUFile *f, Error **errp)
return ret;
}
ret = qemu_savevm_state_non_iterable(f, errp);
if (ret) {
return ret;
if (!qemu_savevm_state_non_iterable(f, errp)) {
return -1;
}
qemu_savevm_state_end(f);
@@ -2461,6 +2507,31 @@ static int loadvm_postcopy_handle_switchover_start(Error **errp)
return 0;
}
/*
* If legacy switchover-ack is enabled but no device uses it, need to send an
* ACK to source that it's OK to switch over.
*/
static int loadvm_switchover_ack_no_users_legacy(MigrationIncomingState *mis,
Error **errp)
{
int ret;
if (!migrate_switchover_ack() || !migrate_switchover_ack_legacy()) {
return 0;
}
if (!mis->switchover_ack_pending_num_legacy) {
ret = migrate_send_rp_switchover_ack(mis);
if (ret) {
error_setg_errno(errp, -ret,
"Could not send switchover ack RP MSG");
return ret;
}
}
return 0;
}
/*
* Process an incoming 'QEMU_VM_COMMAND'
* 0 just a normal return
@@ -2510,18 +2581,9 @@ static int loadvm_process_command(QEMUFile *f, Error **errp)
}
mis->to_src_file = qemu_file_get_return_path(f);
/*
* Switchover ack is enabled but no device uses it, so send an ACK to
* source that it's OK to switchover. Do it here, after return path has
* been created.
*/
if (migrate_switchover_ack() && !mis->switchover_ack_pending_num) {
ret = migrate_send_rp_switchover_ack(mis);
if (ret) {
error_setg_errno(errp, -ret,
"Could not send switchover ack RP MSG");
return ret;
}
ret = loadvm_switchover_ack_no_users_legacy(mis, errp);
if (ret) {
return ret;
}
return 0;
@@ -2783,23 +2845,6 @@ static int qemu_loadvm_state_header(QEMUFile *f, Error **errp)
return 0;
}
static void qemu_loadvm_state_switchover_ack_needed(MigrationIncomingState *mis)
{
SaveStateEntry *se;
QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
if (!se->ops || !se->ops->switchover_ack_needed) {
continue;
}
if (se->ops->switchover_ack_needed(se->opaque)) {
mis->switchover_ack_pending_num++;
}
}
trace_loadvm_state_switchover_ack_needed(mis->switchover_ack_pending_num);
}
static int qemu_loadvm_state_setup(QEMUFile *f, Error **errp)
{
ERRP_GUARD();
@@ -3061,10 +3106,6 @@ int qemu_loadvm_state(QEMUFile *f, Error **errp)
return -EINVAL;
}
if (migrate_switchover_ack()) {
qemu_loadvm_state_switchover_ack_needed(mis);
}
cpu_synchronize_all_pre_loadvm();
ret = qemu_loadvm_state_main(f, mis, errp);
@@ -3157,24 +3198,42 @@ int qemu_load_device_state(QEMUFile *f, Error **errp)
return 0;
}
int qemu_loadvm_approve_switchover(void)
static int qemu_loadvm_approve_switchover_legacy(const char *approver)
{
MigrationIncomingState *mis = migration_incoming_get_current();
if (!mis->switchover_ack_pending_num) {
if (!mis->switchover_ack_pending_num_legacy) {
return -EINVAL;
}
mis->switchover_ack_pending_num--;
trace_loadvm_approve_switchover(mis->switchover_ack_pending_num);
mis->switchover_ack_pending_num_legacy--;
trace_loadvm_approve_switchover_legacy(
approver, mis->switchover_ack_pending_num_legacy);
if (mis->switchover_ack_pending_num) {
if (mis->switchover_ack_pending_num_legacy) {
return 0;
}
return migrate_send_rp_switchover_ack(mis);
}
int qemu_loadvm_approve_switchover(const char *approver)
{
MigrationIncomingState *mis = migration_incoming_get_current();
if (!migrate_switchover_ack()) {
return 0;
}
if (migrate_switchover_ack_legacy()) {
return qemu_loadvm_approve_switchover_legacy(approver);
}
trace_loadvm_approve_switchover(approver);
return migrate_send_rp_switchover_ack(mis);
}
bool qemu_loadvm_load_state_buffer(const char *idstr, uint32_t instance_id,
char *buf, size_t len, Error **errp)
{

View File

@@ -44,8 +44,11 @@ void qemu_savevm_state_header(QEMUFile *f);
int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy);
void qemu_savevm_state_cleanup(void);
void qemu_savevm_state_complete_postcopy(QEMUFile *f);
int qemu_savevm_state_complete_precopy(MigrationState *s);
void qemu_savevm_query_pending(MigPendingData *pending, bool exact);
bool qemu_savevm_state_complete_precopy(MigrationState *s, Error **errp);
void qemu_savevm_query_pending_iter(MigrationState *s, MigPendingData *pending,
bool exact);
bool qemu_savevm_query_pending_final(MigrationState *s,
MigPendingData *pending, Error **errp);
int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy);
bool qemu_savevm_state_postcopy_prepare(QEMUFile *f, Error **errp);
void qemu_savevm_state_end(QEMUFile *f);
@@ -70,8 +73,8 @@ void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
Error **errp);
int qemu_load_device_state(QEMUFile *f, Error **errp);
int qemu_loadvm_approve_switchover(void);
int qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp);
int qemu_loadvm_approve_switchover(const char *approver);
bool qemu_savevm_state_non_iterable(QEMUFile *f, Error **errp);
int qemu_savevm_state_non_iterable_early(QEMUFile *f,
JSONWriter *vmdesc,
Error **errp);

View File

@@ -7,8 +7,7 @@ qemu_loadvm_state_section_partend(uint32_t section_id) "%u"
qemu_loadvm_state_post_main(int ret) "%d"
qemu_loadvm_state_section_startfull(uint32_t section_id, const char *idstr, uint32_t instance_id, uint32_t version_id) "%u(%s) %u %u"
qemu_savevm_send_packaged(void) ""
qemu_savevm_query_pending(bool exact, uint64_t precopy, uint64_t stopcopy, uint64_t postcopy, uint64_t total) "exact=%d, precopy=%"PRIu64", stopcopy=%"PRIu64", postcopy=%"PRIu64", total=%"PRIu64
loadvm_state_switchover_ack_needed(unsigned int switchover_ack_pending_num) "Switchover ack pending num=%u"
qemu_savevm_query_pending(bool exact, bool final, uint64_t precopy, uint64_t stopcopy, uint64_t postcopy, uint64_t total, uint32_t switchover_ack_pending, uint32_t total_switchover_ack_pending) "exact=%d, final=%d, precopy=%"PRIu64", stopcopy=%"PRIu64", postcopy=%"PRIu64", total=%"PRIu64", collected switchover ack pending=%"PRIu32", total switchover ack pending=%"PRIu32
loadvm_state_setup(void) ""
loadvm_state_cleanup(void) ""
loadvm_handle_cmd_packaged(unsigned int length) "%u"
@@ -24,7 +23,8 @@ loadvm_postcopy_ram_handle_discard_end(void) ""
loadvm_postcopy_ram_handle_discard_header(const char *ramid, uint16_t len) "%s: %ud"
loadvm_process_command(const char *s, uint16_t len) "com=%s len=%d"
loadvm_process_command_ping(uint32_t val) "0x%x"
loadvm_approve_switchover(unsigned int switchover_ack_pending_num) "Switchover ack pending num=%u"
loadvm_approve_switchover_legacy(const char *approver, unsigned int switchover_ack_pending_num_legacy) "Approver %s, switchover_ack_pending_num_legacy %u"
loadvm_approve_switchover(const char *approver) "Approver %s"
postcopy_ram_listen_thread_exit(void) ""
postcopy_ram_listen_thread_start(void) ""
qemu_savevm_send_postcopy_advise(void) ""
@@ -190,7 +190,7 @@ source_return_path_thread_loop_top(void) ""
source_return_path_thread_pong(uint32_t val) "0x%x"
source_return_path_thread_shut(uint32_t val) "0x%x"
source_return_path_thread_resume_ack(uint32_t v) "%"PRIu32
source_return_path_thread_switchover_acked(void) ""
source_return_path_thread_switchover_acked(uint32_t pending_num) "switchover_ack_pending_num %" PRIu32
source_return_path_thread_postcopy_package_loaded(void) ""
migration_thread_low_pending(uint64_t pending) "%" PRIu64
migrate_transferred(uint64_t transferred, uint64_t time_spent, uint64_t bandwidth, uint64_t avail_bw, uint64_t size) "transferred %" PRIu64 " time_spent %" PRIu64 " bandwidth %" PRIu64 " switchover_bw %" PRIu64 " max_size %" PRId64
@@ -199,6 +199,7 @@ process_incoming_migration_co_postcopy_end_main(void) ""
postcopy_preempt_enabled(bool value) "%d"
migration_precopy_complete(void) ""
migration_call_notifiers(int type) "type=%d"
migration_request_switchover_ack_legacy(const char *requester, unsigned int switchover_ack_pending_num_legacy) "Requester %s, switchover_ack_pending_num_legacy %u"
# migration-stats
migration_transferred_bytes(uint64_t qemu_file, uint64_t multifd, uint64_t rdma) "qemu_file %" PRIu64 " multifd %" PRIu64 " RDMA %" PRIu64

View File

@@ -508,14 +508,12 @@
# (since 7.1)
#
# @switchover-ack: If enabled, migration will not stop the source VM
# and complete the migration until an ACK is received from the
# destination that it's OK to do so. Exactly when this ACK is
# sent depends on the migrated devices that use this feature. For
# example, a device can use it to make sure some of its data is
# sent and loaded in the destination before doing switchover.
# This can reduce downtime if devices that support this capability
# are present. 'return-path' capability must be enabled to use
# it. (since 8.1)
# and complete the migration until the destination has
# acknowledged that it is OK to switch over. The acknowledgement
# may depend, for example, on some device's data being loaded in
# the destination before doing switchover. This can reduce
# downtime if devices that support this capability are present.
# Capability @return-path must be enabled to use it. (since 8.1)
#
# @dirty-limit: If enabled, migration will throttle vCPUs as needed to
# keep their dirty page rate within @vcpu-dirty-limit. This can
@@ -921,7 +919,7 @@
# migration can use during switchover phase, in bytes per
# second. **Note:** this does not limit the bandwidth during
# switchover, but only for calculations when making decisions to
# switchover. By default, this value is zero, which means QEMU
# switch over. By default, this value is zero, which means QEMU
# will estimate the bandwidth automatically. This can be set
# when the estimated value is not accurate, while the user is
# able to guarantee such bandwidth is available when switching