mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:56:38 +00:00
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:
@@ -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[] = {
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
^^^^^
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
137
hw/vfio/pci.c
137
hw/vfio/pci.c
@@ -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)
|
||||
®_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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
/* properties */
|
||||
bool migrate_send_switchover_start(void);
|
||||
bool migrate_switchover_ack_legacy(void);
|
||||
|
||||
/* capabilities */
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user