vhost-user: Interface for migration state transfer
Add the interface for transferring the back-end's state during migration as defined previously in vhost-user.rst. Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Hanna Czenczek <hreitz@redhat.com> Message-Id: <20231016134243.68248-6-hreitz@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
parent
019233096c
commit
cda83adc62
@ -103,6 +103,8 @@ typedef enum VhostUserRequest {
|
|||||||
VHOST_USER_SET_STATUS = 39,
|
VHOST_USER_SET_STATUS = 39,
|
||||||
VHOST_USER_GET_STATUS = 40,
|
VHOST_USER_GET_STATUS = 40,
|
||||||
VHOST_USER_GET_SHARED_OBJECT = 41,
|
VHOST_USER_GET_SHARED_OBJECT = 41,
|
||||||
|
VHOST_USER_SET_DEVICE_STATE_FD = 42,
|
||||||
|
VHOST_USER_CHECK_DEVICE_STATE = 43,
|
||||||
VHOST_USER_MAX
|
VHOST_USER_MAX
|
||||||
} VhostUserRequest;
|
} VhostUserRequest;
|
||||||
|
|
||||||
@ -201,6 +203,12 @@ typedef struct {
|
|||||||
uint32_t size; /* the following payload size */
|
uint32_t size; /* the following payload size */
|
||||||
} QEMU_PACKED VhostUserHeader;
|
} QEMU_PACKED VhostUserHeader;
|
||||||
|
|
||||||
|
/* Request payload of VHOST_USER_SET_DEVICE_STATE_FD */
|
||||||
|
typedef struct VhostUserTransferDeviceState {
|
||||||
|
uint32_t direction;
|
||||||
|
uint32_t phase;
|
||||||
|
} VhostUserTransferDeviceState;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
#define VHOST_USER_VRING_IDX_MASK (0xff)
|
#define VHOST_USER_VRING_IDX_MASK (0xff)
|
||||||
#define VHOST_USER_VRING_NOFD_MASK (0x1 << 8)
|
#define VHOST_USER_VRING_NOFD_MASK (0x1 << 8)
|
||||||
@ -216,6 +224,7 @@ typedef union {
|
|||||||
VhostUserVringArea area;
|
VhostUserVringArea area;
|
||||||
VhostUserInflight inflight;
|
VhostUserInflight inflight;
|
||||||
VhostUserShared object;
|
VhostUserShared object;
|
||||||
|
VhostUserTransferDeviceState transfer_state;
|
||||||
} VhostUserPayload;
|
} VhostUserPayload;
|
||||||
|
|
||||||
typedef struct VhostUserMsg {
|
typedef struct VhostUserMsg {
|
||||||
@ -2855,6 +2864,140 @@ static void vhost_user_reset_status(struct vhost_dev *dev)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool vhost_user_supports_device_state(struct vhost_dev *dev)
|
||||||
|
{
|
||||||
|
return virtio_has_feature(dev->protocol_features,
|
||||||
|
VHOST_USER_PROTOCOL_F_DEVICE_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vhost_user_set_device_state_fd(struct vhost_dev *dev,
|
||||||
|
VhostDeviceStateDirection direction,
|
||||||
|
VhostDeviceStatePhase phase,
|
||||||
|
int fd,
|
||||||
|
int *reply_fd,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct vhost_user *vu = dev->opaque;
|
||||||
|
VhostUserMsg msg = {
|
||||||
|
.hdr = {
|
||||||
|
.request = VHOST_USER_SET_DEVICE_STATE_FD,
|
||||||
|
.flags = VHOST_USER_VERSION,
|
||||||
|
.size = sizeof(msg.payload.transfer_state),
|
||||||
|
},
|
||||||
|
.payload.transfer_state = {
|
||||||
|
.direction = direction,
|
||||||
|
.phase = phase,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
*reply_fd = -1;
|
||||||
|
|
||||||
|
if (!vhost_user_supports_device_state(dev)) {
|
||||||
|
close(fd);
|
||||||
|
error_setg(errp, "Back-end does not support migration state transfer");
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = vhost_user_write(dev, &msg, &fd, 1);
|
||||||
|
close(fd);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_setg_errno(errp, -ret,
|
||||||
|
"Failed to send SET_DEVICE_STATE_FD message");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = vhost_user_read(dev, &msg);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_setg_errno(errp, -ret,
|
||||||
|
"Failed to receive SET_DEVICE_STATE_FD reply");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.hdr.request != VHOST_USER_SET_DEVICE_STATE_FD) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Received unexpected message type, expected %d, received %d",
|
||||||
|
VHOST_USER_SET_DEVICE_STATE_FD, msg.hdr.request);
|
||||||
|
return -EPROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.hdr.size != sizeof(msg.payload.u64)) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Received bad message size, expected %zu, received %" PRIu32,
|
||||||
|
sizeof(msg.payload.u64), msg.hdr.size);
|
||||||
|
return -EPROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((msg.payload.u64 & 0xff) != 0) {
|
||||||
|
error_setg(errp, "Back-end did not accept migration state transfer");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(msg.payload.u64 & VHOST_USER_VRING_NOFD_MASK)) {
|
||||||
|
*reply_fd = qemu_chr_fe_get_msgfd(vu->user->chr);
|
||||||
|
if (*reply_fd < 0) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Failed to get back-end-provided transfer pipe FD");
|
||||||
|
*reply_fd = -1;
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vhost_user_check_device_state(struct vhost_dev *dev, Error **errp)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
VhostUserMsg msg = {
|
||||||
|
.hdr = {
|
||||||
|
.request = VHOST_USER_CHECK_DEVICE_STATE,
|
||||||
|
.flags = VHOST_USER_VERSION,
|
||||||
|
.size = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!vhost_user_supports_device_state(dev)) {
|
||||||
|
error_setg(errp, "Back-end does not support migration state transfer");
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = vhost_user_write(dev, &msg, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_setg_errno(errp, -ret,
|
||||||
|
"Failed to send CHECK_DEVICE_STATE message");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = vhost_user_read(dev, &msg);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_setg_errno(errp, -ret,
|
||||||
|
"Failed to receive CHECK_DEVICE_STATE reply");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.hdr.request != VHOST_USER_CHECK_DEVICE_STATE) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Received unexpected message type, expected %d, received %d",
|
||||||
|
VHOST_USER_CHECK_DEVICE_STATE, msg.hdr.request);
|
||||||
|
return -EPROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.hdr.size != sizeof(msg.payload.u64)) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Received bad message size, expected %zu, received %" PRIu32,
|
||||||
|
sizeof(msg.payload.u64), msg.hdr.size);
|
||||||
|
return -EPROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.payload.u64 != 0) {
|
||||||
|
error_setg(errp, "Back-end failed to process its internal state");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const VhostOps user_ops = {
|
const VhostOps user_ops = {
|
||||||
.backend_type = VHOST_BACKEND_TYPE_USER,
|
.backend_type = VHOST_BACKEND_TYPE_USER,
|
||||||
.vhost_backend_init = vhost_user_backend_init,
|
.vhost_backend_init = vhost_user_backend_init,
|
||||||
@ -2890,4 +3033,7 @@ const VhostOps user_ops = {
|
|||||||
.vhost_set_inflight_fd = vhost_user_set_inflight_fd,
|
.vhost_set_inflight_fd = vhost_user_set_inflight_fd,
|
||||||
.vhost_dev_start = vhost_user_dev_start,
|
.vhost_dev_start = vhost_user_dev_start,
|
||||||
.vhost_reset_status = vhost_user_reset_status,
|
.vhost_reset_status = vhost_user_reset_status,
|
||||||
|
.vhost_supports_device_state = vhost_user_supports_device_state,
|
||||||
|
.vhost_set_device_state_fd = vhost_user_set_device_state_fd,
|
||||||
|
.vhost_check_device_state = vhost_user_check_device_state,
|
||||||
};
|
};
|
||||||
|
@ -2159,3 +2159,40 @@ int vhost_reset_device(struct vhost_dev *hdev)
|
|||||||
|
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool vhost_supports_device_state(struct vhost_dev *dev)
|
||||||
|
{
|
||||||
|
if (dev->vhost_ops->vhost_supports_device_state) {
|
||||||
|
return dev->vhost_ops->vhost_supports_device_state(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vhost_set_device_state_fd(struct vhost_dev *dev,
|
||||||
|
VhostDeviceStateDirection direction,
|
||||||
|
VhostDeviceStatePhase phase,
|
||||||
|
int fd,
|
||||||
|
int *reply_fd,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
if (dev->vhost_ops->vhost_set_device_state_fd) {
|
||||||
|
return dev->vhost_ops->vhost_set_device_state_fd(dev, direction, phase,
|
||||||
|
fd, reply_fd, errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_setg(errp,
|
||||||
|
"vhost transport does not support migration state transfer");
|
||||||
|
return -ENOSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vhost_check_device_state(struct vhost_dev *dev, Error **errp)
|
||||||
|
{
|
||||||
|
if (dev->vhost_ops->vhost_check_device_state) {
|
||||||
|
return dev->vhost_ops->vhost_check_device_state(dev, errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_setg(errp,
|
||||||
|
"vhost transport does not support migration state transfer");
|
||||||
|
return -ENOSYS;
|
||||||
|
}
|
||||||
|
@ -26,6 +26,18 @@ typedef enum VhostSetConfigType {
|
|||||||
VHOST_SET_CONFIG_TYPE_MIGRATION = 1,
|
VHOST_SET_CONFIG_TYPE_MIGRATION = 1,
|
||||||
} VhostSetConfigType;
|
} VhostSetConfigType;
|
||||||
|
|
||||||
|
typedef enum VhostDeviceStateDirection {
|
||||||
|
/* Transfer state from back-end (device) to front-end */
|
||||||
|
VHOST_TRANSFER_STATE_DIRECTION_SAVE = 0,
|
||||||
|
/* Transfer state from front-end to back-end (device) */
|
||||||
|
VHOST_TRANSFER_STATE_DIRECTION_LOAD = 1,
|
||||||
|
} VhostDeviceStateDirection;
|
||||||
|
|
||||||
|
typedef enum VhostDeviceStatePhase {
|
||||||
|
/* The device (and all its vrings) is stopped */
|
||||||
|
VHOST_TRANSFER_STATE_PHASE_STOPPED = 0,
|
||||||
|
} VhostDeviceStatePhase;
|
||||||
|
|
||||||
struct vhost_inflight;
|
struct vhost_inflight;
|
||||||
struct vhost_dev;
|
struct vhost_dev;
|
||||||
struct vhost_log;
|
struct vhost_log;
|
||||||
@ -129,6 +141,15 @@ typedef int (*vhost_set_config_call_op)(struct vhost_dev *dev,
|
|||||||
|
|
||||||
typedef void (*vhost_reset_status_op)(struct vhost_dev *dev);
|
typedef void (*vhost_reset_status_op)(struct vhost_dev *dev);
|
||||||
|
|
||||||
|
typedef bool (*vhost_supports_device_state_op)(struct vhost_dev *dev);
|
||||||
|
typedef int (*vhost_set_device_state_fd_op)(struct vhost_dev *dev,
|
||||||
|
VhostDeviceStateDirection direction,
|
||||||
|
VhostDeviceStatePhase phase,
|
||||||
|
int fd,
|
||||||
|
int *reply_fd,
|
||||||
|
Error **errp);
|
||||||
|
typedef int (*vhost_check_device_state_op)(struct vhost_dev *dev, Error **errp);
|
||||||
|
|
||||||
typedef struct VhostOps {
|
typedef struct VhostOps {
|
||||||
VhostBackendType backend_type;
|
VhostBackendType backend_type;
|
||||||
vhost_backend_init vhost_backend_init;
|
vhost_backend_init vhost_backend_init;
|
||||||
@ -176,6 +197,9 @@ typedef struct VhostOps {
|
|||||||
vhost_force_iommu_op vhost_force_iommu;
|
vhost_force_iommu_op vhost_force_iommu;
|
||||||
vhost_set_config_call_op vhost_set_config_call;
|
vhost_set_config_call_op vhost_set_config_call;
|
||||||
vhost_reset_status_op vhost_reset_status;
|
vhost_reset_status_op vhost_reset_status;
|
||||||
|
vhost_supports_device_state_op vhost_supports_device_state;
|
||||||
|
vhost_set_device_state_fd_op vhost_set_device_state_fd;
|
||||||
|
vhost_check_device_state_op vhost_check_device_state;
|
||||||
} VhostOps;
|
} VhostOps;
|
||||||
|
|
||||||
int vhost_backend_update_device_iotlb(struct vhost_dev *dev,
|
int vhost_backend_update_device_iotlb(struct vhost_dev *dev,
|
||||||
|
@ -31,6 +31,7 @@ enum VhostUserProtocolFeature {
|
|||||||
VHOST_USER_PROTOCOL_F_STATUS = 16,
|
VHOST_USER_PROTOCOL_F_STATUS = 16,
|
||||||
/* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */
|
/* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */
|
||||||
VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18,
|
VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18,
|
||||||
|
VHOST_USER_PROTOCOL_F_DEVICE_STATE = 19,
|
||||||
VHOST_USER_PROTOCOL_F_MAX
|
VHOST_USER_PROTOCOL_F_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -351,4 +351,82 @@ static inline int vhost_reset_device(struct vhost_dev *hdev)
|
|||||||
}
|
}
|
||||||
#endif /* CONFIG_VHOST */
|
#endif /* CONFIG_VHOST */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vhost_supports_device_state(): Checks whether the back-end supports
|
||||||
|
* transferring internal device state for the purpose of migration.
|
||||||
|
* Support for this feature is required for vhost_set_device_state_fd()
|
||||||
|
* and vhost_check_device_state().
|
||||||
|
*
|
||||||
|
* @dev: The vhost device
|
||||||
|
*
|
||||||
|
* Returns true if the device supports these commands, and false if it
|
||||||
|
* does not.
|
||||||
|
*/
|
||||||
|
bool vhost_supports_device_state(struct vhost_dev *dev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vhost_set_device_state_fd(): Begin transfer of internal state from/to
|
||||||
|
* the back-end for the purpose of migration. Data is to be transferred
|
||||||
|
* over a pipe according to @direction and @phase. The sending end must
|
||||||
|
* only write to the pipe, and the receiving end must only read from it.
|
||||||
|
* Once the sending end is done, it closes its FD. The receiving end
|
||||||
|
* must take this as the end-of-transfer signal and close its FD, too.
|
||||||
|
*
|
||||||
|
* @fd is the back-end's end of the pipe: The write FD for SAVE, and the
|
||||||
|
* read FD for LOAD. This function transfers ownership of @fd to the
|
||||||
|
* back-end, i.e. closes it in the front-end.
|
||||||
|
*
|
||||||
|
* The back-end may optionally reply with an FD of its own, if this
|
||||||
|
* improves efficiency on its end. In this case, the returned FD is
|
||||||
|
* stored in *reply_fd. The back-end will discard the FD sent to it,
|
||||||
|
* and the front-end must use *reply_fd for transferring state to/from
|
||||||
|
* the back-end.
|
||||||
|
*
|
||||||
|
* @dev: The vhost device
|
||||||
|
* @direction: The direction in which the state is to be transferred.
|
||||||
|
* For outgoing migrations, this is SAVE, and data is read
|
||||||
|
* from the back-end and stored by the front-end in the
|
||||||
|
* migration stream.
|
||||||
|
* For incoming migrations, this is LOAD, and data is read
|
||||||
|
* by the front-end from the migration stream and sent to
|
||||||
|
* the back-end to restore the saved state.
|
||||||
|
* @phase: Which migration phase we are in. Currently, there is only
|
||||||
|
* STOPPED (device and all vrings are stopped), in the future,
|
||||||
|
* more phases such as PRE_COPY or POST_COPY may be added.
|
||||||
|
* @fd: Back-end's end of the pipe through which to transfer state; note
|
||||||
|
* that ownership is transferred to the back-end, so this function
|
||||||
|
* closes @fd in the front-end.
|
||||||
|
* @reply_fd: If the back-end wishes to use a different pipe for state
|
||||||
|
* transfer, this will contain an FD for the front-end to
|
||||||
|
* use. Otherwise, -1 is stored here.
|
||||||
|
* @errp: Potential error description
|
||||||
|
*
|
||||||
|
* Returns 0 on success, and -errno on failure.
|
||||||
|
*/
|
||||||
|
int vhost_set_device_state_fd(struct vhost_dev *dev,
|
||||||
|
VhostDeviceStateDirection direction,
|
||||||
|
VhostDeviceStatePhase phase,
|
||||||
|
int fd,
|
||||||
|
int *reply_fd,
|
||||||
|
Error **errp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vhost_set_device_state_fd(): After transferring state from/to the
|
||||||
|
* back-end via vhost_set_device_state_fd(), i.e. once the sending end
|
||||||
|
* has closed the pipe, inquire the back-end to report any potential
|
||||||
|
* errors that have occurred on its side. This allows to sense errors
|
||||||
|
* like:
|
||||||
|
* - During outgoing migration, when the source side had already started
|
||||||
|
* to produce its state, something went wrong and it failed to finish
|
||||||
|
* - During incoming migration, when the received state is somehow
|
||||||
|
* invalid and cannot be processed by the back-end
|
||||||
|
*
|
||||||
|
* @dev: The vhost device
|
||||||
|
* @errp: Potential error description
|
||||||
|
*
|
||||||
|
* Returns 0 when the back-end reports successful state transfer and
|
||||||
|
* processing, and -errno when an error occurred somewhere.
|
||||||
|
*/
|
||||||
|
int vhost_check_device_state(struct vhost_dev *dev, Error **errp);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user