migration 1st pull for 9.0

- We lost Juan and Leo in the maintainers file
 - Steven's suspend state fix
 - Steven's fix for coverity on migrate_mode
 - Avihai's migration cleanup series
 -----BEGIN PGP SIGNATURE-----
 
 iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZZY0TxIccGV0ZXJ4QHJl
 ZGhhdC5jb20ACgkQO1/MzfOr1wbSxgEAoM5g3wkc22lpAlRpU+hJUqT9NVOVQSK+
 Fk7XJYTdSgABAKzykA6hAmU5Kj+yVI6jI874SVZbs2FWpFs4osvsKk4D
 =sfuM
 -----END PGP SIGNATURE-----

Merge tag 'migration-20240104-pull-request' of https://gitlab.com/peterx/qemu into staging

migration 1st pull for 9.0

- We lost Juan and Leo in the maintainers file
- Steven's suspend state fix
- Steven's fix for coverity on migrate_mode
- Avihai's migration cleanup series

# -----BEGIN PGP SIGNATURE-----
#
# iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZZY0TxIccGV0ZXJ4QHJl
# ZGhhdC5jb20ACgkQO1/MzfOr1wbSxgEAoM5g3wkc22lpAlRpU+hJUqT9NVOVQSK+
# Fk7XJYTdSgABAKzykA6hAmU5Kj+yVI6jI874SVZbs2FWpFs4osvsKk4D
# =sfuM
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 04 Jan 2024 04:30:07 GMT
# gpg:                using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706
# gpg:                issuer "peterx@redhat.com"
# gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown]
# gpg:                 aka "Peter Xu <peterx@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D  D1A9 3B5F CCCD F3AB D706

* tag 'migration-20240104-pull-request' of https://gitlab.com/peterx/qemu: (26 commits)
  migration: fix coverity migrate_mode finding
  migration/multifd: Remove unnecessary usage of local Error
  migration: Remove unnecessary usage of local Error
  migration: Fix migration_channel_read_peek() error path
  migration/multifd: Remove error_setg() in migration_ioc_process_incoming()
  migration/multifd: Fix leaking of Error in TLS error flow
  migration/multifd: Simplify multifd_channel_connect() if else statement
  migration/multifd: Fix error message in multifd_recv_initial_packet()
  migration: Remove errp parameter in migration_fd_process_incoming()
  migration: Refactor migration_incoming_setup()
  migration: Remove nulling of hostname in migrate_init()
  migration: Remove migrate_max_downtime() declaration
  tests/qtest: postcopy migration with suspend
  tests/qtest: precopy migration with suspend
  tests/qtest: option to suspend during migration
  tests/qtest: migration events
  migration: preserve suspended for bg_migration
  migration: preserve suspended for snapshot
  migration: preserve suspended runstate
  migration: propagate suspended runstate
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2024-01-05 13:35:25 +00:00
commit c8193acc07
28 changed files with 386 additions and 200 deletions

View File

@ -81,6 +81,7 @@ Greg Kurz <groug@kaod.org> <gkurz@linux.vnet.ibm.com>
Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com> Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com>
Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn> Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn>
James Hogan <jhogan@kernel.org> <james.hogan@imgtec.com> James Hogan <jhogan@kernel.org> <james.hogan@imgtec.com>
Juan Quintela <quintela@trasno.org> <quintela@redhat.com>
Leif Lindholm <quic_llindhol@quicinc.com> <leif.lindholm@linaro.org> Leif Lindholm <quic_llindhol@quicinc.com> <leif.lindholm@linaro.org>
Leif Lindholm <quic_llindhol@quicinc.com> <leif@nuviainc.com> Leif Lindholm <quic_llindhol@quicinc.com> <leif@nuviainc.com>
Luc Michel <luc@lmichel.fr> <luc.michel@git.antfield.fr> Luc Michel <luc@lmichel.fr> <luc.michel@git.antfield.fr>

View File

@ -70,7 +70,6 @@ R: Daniel P. Berrangé <berrange@redhat.com>
R: Thomas Huth <thuth@redhat.com> R: Thomas Huth <thuth@redhat.com>
R: Markus Armbruster <armbru@redhat.com> R: Markus Armbruster <armbru@redhat.com>
R: Philippe Mathieu-Daudé <philmd@linaro.org> R: Philippe Mathieu-Daudé <philmd@linaro.org>
R: Juan Quintela <quintela@redhat.com>
W: https://www.qemu.org/docs/master/devel/index.html W: https://www.qemu.org/docs/master/devel/index.html
S: Odd Fixes S: Odd Fixes
F: docs/devel/style.rst F: docs/devel/style.rst
@ -3355,10 +3354,8 @@ S: Odd Fixes
F: scripts/checkpatch.pl F: scripts/checkpatch.pl
Migration Migration
M: Juan Quintela <quintela@redhat.com>
M: Peter Xu <peterx@redhat.com> M: Peter Xu <peterx@redhat.com>
M: Fabiano Rosas <farosas@suse.de> M: Fabiano Rosas <farosas@suse.de>
R: Leonardo Bras <leobras@redhat.com>
S: Maintained S: Maintained
F: hw/core/vmstate-if.c F: hw/core/vmstate-if.c
F: include/hw/vmstate-if.h F: include/hw/vmstate-if.h
@ -3375,10 +3372,8 @@ F: util/userfaultfd.c
X: migration/rdma* X: migration/rdma*
RDMA Migration RDMA Migration
M: Juan Quintela <quintela@redhat.com>
R: Li Zhijian <lizhijian@fujitsu.com> R: Li Zhijian <lizhijian@fujitsu.com>
R: Peter Xu <peterx@redhat.com> R: Peter Xu <peterx@redhat.com>
R: Leonardo Bras <leobras@redhat.com>
S: Odd Fixes S: Odd Fixes
F: migration/rdma* F: migration/rdma*

View File

@ -904,7 +904,7 @@ static void tpm_emulator_vm_state_change(void *opaque, bool running,
trace_tpm_emulator_vm_state_change(running, state); trace_tpm_emulator_vm_state_change(running, state);
if (!running || state != RUN_STATE_RUNNING || !tpm_emu->relock_storage) { if (!running || !tpm_emu->relock_storage) {
return; return;
} }

View File

@ -2451,7 +2451,7 @@ static void usb_ehci_vm_state_change(void *opaque, bool running, RunState state)
* USB-devices which have async handled packages have a packet in the * USB-devices which have async handled packages have a packet in the
* ep queue to match the completion with. * ep queue to match the completion with.
*/ */
if (state == RUN_STATE_RUNNING) { if (running) {
ehci_advance_async_state(ehci); ehci_advance_async_state(ehci);
} }

View File

@ -1403,7 +1403,7 @@ static void usbredir_vm_state_change(void *priv, bool running, RunState state)
{ {
USBRedirDevice *dev = priv; USBRedirDevice *dev = priv;
if (state == RUN_STATE_RUNNING && dev->parser != NULL) { if (running && dev->parser != NULL) {
usbredirparser_do_write(dev->parser); /* Flush any pending writes */ usbredirparser_do_write(dev->parser); /* Flush any pending writes */
} }
} }

View File

@ -623,7 +623,7 @@ void xen_hvm_change_state_handler(void *opaque, bool running,
xen_set_ioreq_server_state(xen_domid, xen_set_ioreq_server_state(xen_domid,
state->ioservid, state->ioservid,
(rstate == RUN_STATE_RUNNING)); running);
} }
void xen_exit_notifier(Notifier *n, void *data) void xen_exit_notifier(Notifier *n, void *data)

View File

@ -16,6 +16,7 @@
#define QEMU_MIGRATION_SNAPSHOT_H #define QEMU_MIGRATION_SNAPSHOT_H
#include "qapi/qapi-builtin-types.h" #include "qapi/qapi-builtin-types.h"
#include "qapi/qapi-types-run-state.h"
/** /**
* save_snapshot: Save an internal snapshot. * save_snapshot: Save an internal snapshot.
@ -61,4 +62,10 @@ bool delete_snapshot(const char *name,
bool has_devices, strList *devices, bool has_devices, strList *devices,
Error **errp); Error **errp);
/**
* load_snapshot_resume: Restore runstate after loading snapshot.
* @state: state to restore
*/
void load_snapshot_resume(RunState state);
#endif #endif

View File

@ -40,6 +40,15 @@ static inline bool shutdown_caused_by_guest(ShutdownCause cause)
return cause >= SHUTDOWN_CAUSE_GUEST_SHUTDOWN; return cause >= SHUTDOWN_CAUSE_GUEST_SHUTDOWN;
} }
/*
* In a "live" state, the vcpu clock is ticking, and the runstate notifiers
* think we are running.
*/
static inline bool runstate_is_live(RunState state)
{
return state == RUN_STATE_RUNNING || state == RUN_STATE_SUSPENDED;
}
void vm_start(void); void vm_start(void);
/** /**
@ -48,9 +57,20 @@ void vm_start(void);
* @step_pending: whether any of the CPUs is about to be single-stepped by gdb * @step_pending: whether any of the CPUs is about to be single-stepped by gdb
*/ */
int vm_prepare_start(bool step_pending); int vm_prepare_start(bool step_pending);
/**
* vm_resume: If @state is a live state, start the vm and set the state,
* else just set the state.
*
* @state: the state to restore
*/
void vm_resume(RunState state);
int vm_stop(RunState state); int vm_stop(RunState state);
int vm_stop_force_state(RunState state); int vm_stop_force_state(RunState state);
int vm_shutdown(void); int vm_shutdown(void);
void vm_set_suspended(bool suspended);
bool vm_get_suspended(void);
typedef enum WakeupReason { typedef enum WakeupReason {
/* Always keep QEMU_WAKEUP_REASON_NONE = 0 */ /* Always keep QEMU_WAKEUP_REASON_NONE = 0 */

View File

@ -117,9 +117,12 @@ int migration_channel_read_peek(QIOChannel *ioc,
len = qio_channel_readv_full(ioc, &iov, 1, NULL, NULL, len = qio_channel_readv_full(ioc, &iov, 1, NULL, NULL,
QIO_CHANNEL_READ_FLAG_MSG_PEEK, errp); QIO_CHANNEL_READ_FLAG_MSG_PEEK, errp);
if (len <= 0 && len != QIO_CHANNEL_ERR_BLOCK) { if (len < 0 && len != QIO_CHANNEL_ERR_BLOCK) {
error_setg(errp, return -1;
"Failed to peek at channel"); }
if (len == 0) {
error_setg(errp, "Failed to peek at channel");
return -1; return -1;
} }

View File

@ -22,7 +22,16 @@
typedef struct { typedef struct {
uint32_t size; uint32_t size;
uint8_t runstate[100];
/*
* runstate was 100 bytes, zero padded, but we trimmed it to add a
* few fields and maintain backwards compatibility.
*/
uint8_t runstate[32];
uint8_t has_vm_was_suspended;
uint8_t vm_was_suspended;
uint8_t unused[66];
RunState state; RunState state;
bool received; bool received;
} GlobalState; } GlobalState;
@ -35,6 +44,10 @@ static void global_state_do_store(RunState state)
assert(strlen(state_str) < sizeof(global_state.runstate)); assert(strlen(state_str) < sizeof(global_state.runstate));
strpadcpy((char *)global_state.runstate, sizeof(global_state.runstate), strpadcpy((char *)global_state.runstate, sizeof(global_state.runstate),
state_str, '\0'); state_str, '\0');
global_state.has_vm_was_suspended = true;
global_state.vm_was_suspended = vm_get_suspended();
memset(global_state.unused, 0, sizeof(global_state.unused));
} }
void global_state_store(void) void global_state_store(void)
@ -59,24 +72,7 @@ RunState global_state_get_runstate(void)
static bool global_state_needed(void *opaque) static bool global_state_needed(void *opaque)
{ {
GlobalState *s = opaque; return migrate_get_current()->store_global_state;
char *runstate = (char *)s->runstate;
/* If it is not optional, it is mandatory */
if (migrate_get_current()->store_global_state) {
return true;
}
/* If state is running or paused, it is not needed */
if (strcmp(runstate, "running") == 0 ||
strcmp(runstate, "paused") == 0) {
return false;
}
/* for any other state it is needed */
return true;
} }
static int global_state_post_load(void *opaque, int version_id) static int global_state_post_load(void *opaque, int version_id)
@ -93,7 +89,7 @@ static int global_state_post_load(void *opaque, int version_id)
sizeof(s->runstate)) == sizeof(s->runstate)) { sizeof(s->runstate)) == sizeof(s->runstate)) {
/* /*
* This condition should never happen during migration, because * This condition should never happen during migration, because
* all runstate names are shorter than 100 bytes (the size of * all runstate names are shorter than 32 bytes (the size of
* s->runstate). However, a malicious stream could overflow * s->runstate). However, a malicious stream could overflow
* the qapi_enum_parse() call, so we force the last character * the qapi_enum_parse() call, so we force the last character
* to a NUL byte. * to a NUL byte.
@ -110,6 +106,14 @@ static int global_state_post_load(void *opaque, int version_id)
} }
s->state = r; s->state = r;
/*
* global_state is saved on the outgoing side before forcing a stopped
* state, so it may have saved state=suspended and vm_was_suspended=0.
* Now we are in a paused state, and when we later call vm_start, it must
* restore the suspended state, so we must set vm_was_suspended=1 here.
*/
vm_set_suspended(s->vm_was_suspended || r == RUN_STATE_SUSPENDED);
return 0; return 0;
} }
@ -134,6 +138,9 @@ static const VMStateDescription vmstate_globalstate = {
.fields = (const VMStateField[]) { .fields = (const VMStateField[]) {
VMSTATE_UINT32(size, GlobalState), VMSTATE_UINT32(size, GlobalState),
VMSTATE_BUFFER(runstate, GlobalState), VMSTATE_BUFFER(runstate, GlobalState),
VMSTATE_UINT8(has_vm_was_suspended, GlobalState),
VMSTATE_UINT8(vm_was_suspended, GlobalState),
VMSTATE_BUFFER(unused, GlobalState),
VMSTATE_END_OF_LIST() VMSTATE_END_OF_LIST()
}, },
}; };

View File

@ -399,15 +399,17 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
void hmp_loadvm(Monitor *mon, const QDict *qdict) void hmp_loadvm(Monitor *mon, const QDict *qdict)
{ {
int saved_vm_running = runstate_is_running(); RunState saved_state = runstate_get();
const char *name = qdict_get_str(qdict, "name"); const char *name = qdict_get_str(qdict, "name");
Error *err = NULL; Error *err = NULL;
vm_stop(RUN_STATE_RESTORE_VM); vm_stop(RUN_STATE_RESTORE_VM);
if (load_snapshot(name, NULL, false, NULL, &err) && saved_vm_running) { if (load_snapshot(name, NULL, false, NULL, &err)) {
vm_start(); load_snapshot_resume(saved_state);
} }
hmp_handle_error(mon, err); hmp_handle_error(mon, err);
} }

View File

@ -604,7 +604,7 @@ static void process_incoming_migration_bh(void *opaque)
*/ */
if (!migrate_late_block_activate() || if (!migrate_late_block_activate() ||
(autostart && (!global_state_received() || (autostart && (!global_state_received() ||
global_state_get_runstate() == RUN_STATE_RUNNING))) { runstate_is_live(global_state_get_runstate())))) {
/* Make sure all file formats throw away their mutable metadata. /* Make sure all file formats throw away their mutable metadata.
* If we get an error here, just don't restart the VM yet. */ * If we get an error here, just don't restart the VM yet. */
bdrv_activate_all(&local_err); bdrv_activate_all(&local_err);
@ -628,7 +628,7 @@ static void process_incoming_migration_bh(void *opaque)
dirty_bitmap_mig_before_vm_start(); dirty_bitmap_mig_before_vm_start();
if (!global_state_received() || if (!global_state_received() ||
global_state_get_runstate() == RUN_STATE_RUNNING) { runstate_is_live(global_state_get_runstate())) {
if (autostart) { if (autostart) {
vm_start(); vm_start();
} else { } else {
@ -724,11 +724,8 @@ fail:
/** /**
* migration_incoming_setup: Setup incoming migration * migration_incoming_setup: Setup incoming migration
* @f: file for main migration channel * @f: file for main migration channel
* @errp: where to put errors
*
* Returns: %true on success, %false on error.
*/ */
static bool migration_incoming_setup(QEMUFile *f, Error **errp) static void migration_incoming_setup(QEMUFile *f)
{ {
MigrationIncomingState *mis = migration_incoming_get_current(); MigrationIncomingState *mis = migration_incoming_get_current();
@ -736,7 +733,6 @@ static bool migration_incoming_setup(QEMUFile *f, Error **errp)
mis->from_src_file = f; mis->from_src_file = f;
} }
qemu_file_set_blocking(f, false); qemu_file_set_blocking(f, false);
return true;
} }
void migration_incoming_process(void) void migration_incoming_process(void)
@ -778,11 +774,9 @@ static bool postcopy_try_recover(void)
return false; return false;
} }
void migration_fd_process_incoming(QEMUFile *f, Error **errp) void migration_fd_process_incoming(QEMUFile *f)
{ {
if (!migration_incoming_setup(f, errp)) { migration_incoming_setup(f);
return;
}
if (postcopy_try_recover()) { if (postcopy_try_recover()) {
return; return;
} }
@ -836,10 +830,9 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
* issue is not possible. * issue is not possible.
*/ */
ret = migration_channel_read_peek(ioc, (void *)&channel_magic, ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
sizeof(channel_magic), &local_err); sizeof(channel_magic), errp);
if (ret != 0) { if (ret != 0) {
error_propagate(errp, local_err);
return; return;
} }
@ -849,16 +842,12 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
} }
if (multifd_load_setup(errp) != 0) { if (multifd_load_setup(errp) != 0) {
error_setg(errp, "Failed to setup multifd channels");
return; return;
} }
if (default_channel) { if (default_channel) {
f = qemu_file_new_input(ioc); f = qemu_file_new_input(ioc);
migration_incoming_setup(f);
if (!migration_incoming_setup(f, errp)) {
return;
}
} else { } else {
/* Multiple connections */ /* Multiple connections */
assert(migration_needs_multiple_sockets()); assert(migration_needs_multiple_sockets());
@ -1588,7 +1577,6 @@ int migrate_init(MigrationState *s, Error **errp)
s->migration_thread_running = false; s->migration_thread_running = false;
error_free(s->error); error_free(s->error);
s->error = NULL; s->error = NULL;
s->hostname = NULL;
s->vmdesc = NULL; s->vmdesc = NULL;
migrate_set_state(&s->state, MIGRATION_STATUS_NONE, MIGRATION_STATUS_SETUP); migrate_set_state(&s->state, MIGRATION_STATUS_NONE, MIGRATION_STATUS_SETUP);
@ -1836,8 +1824,6 @@ bool migration_is_blocked(Error **errp)
static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc, static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
bool resume, Error **errp) bool resume, Error **errp)
{ {
Error *local_err = NULL;
if (blk_inc) { if (blk_inc) {
warn_report("parameter 'inc' is deprecated;" warn_report("parameter 'inc' is deprecated;"
" use blockdev-mirror with NBD instead"); " use blockdev-mirror with NBD instead");
@ -1907,8 +1893,7 @@ static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
"current migration capabilities"); "current migration capabilities");
return false; return false;
} }
if (!migrate_cap_set(MIGRATION_CAPABILITY_BLOCK, true, &local_err)) { if (!migrate_cap_set(MIGRATION_CAPABILITY_BLOCK, true, errp)) {
error_propagate(errp, local_err);
return false; return false;
} }
s->must_remove_block_options = true; s->must_remove_block_options = true;
@ -2416,7 +2401,6 @@ static int postcopy_start(MigrationState *ms, Error **errp)
migration_downtime_start(ms); migration_downtime_start(ms);
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
global_state_store(); global_state_store();
ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE); ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE);
if (ret < 0) { if (ret < 0) {
@ -2615,7 +2599,6 @@ static int migration_completion_precopy(MigrationState *s,
qemu_mutex_lock_iothread(); qemu_mutex_lock_iothread();
migration_downtime_start(s); migration_downtime_start(s);
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
s->vm_old_state = runstate_get(); s->vm_old_state = runstate_get();
global_state_store(); global_state_store();
@ -3136,7 +3119,7 @@ static void migration_iteration_finish(MigrationState *s)
case MIGRATION_STATUS_FAILED: case MIGRATION_STATUS_FAILED:
case MIGRATION_STATUS_CANCELLED: case MIGRATION_STATUS_CANCELLED:
case MIGRATION_STATUS_CANCELLING: case MIGRATION_STATUS_CANCELLING:
if (s->vm_old_state == RUN_STATE_RUNNING) { if (runstate_is_live(s->vm_old_state)) {
if (!runstate_check(RUN_STATE_SHUTDOWN)) { if (!runstate_check(RUN_STATE_SHUTDOWN)) {
vm_start(); vm_start();
} }
@ -3392,7 +3375,7 @@ static void bg_migration_vm_start_bh(void *opaque)
qemu_bh_delete(s->vm_start_bh); qemu_bh_delete(s->vm_start_bh);
s->vm_start_bh = NULL; s->vm_start_bh = NULL;
vm_start(); vm_resume(s->vm_old_state);
migration_downtime_end(s); migration_downtime_end(s);
} }
@ -3464,11 +3447,6 @@ static void *bg_migration_thread(void *opaque)
qemu_mutex_lock_iothread(); qemu_mutex_lock_iothread();
/*
* If VM is currently in suspended state, then, to make a valid runstate
* transition in vm_stop_force_state() we need to wakeup it up.
*/
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
s->vm_old_state = runstate_get(); s->vm_old_state = runstate_get();
global_state_store(); global_state_store();

View File

@ -474,14 +474,12 @@ struct MigrationState {
void migrate_set_state(int *state, int old_state, int new_state); void migrate_set_state(int *state, int old_state, int new_state);
void migration_fd_process_incoming(QEMUFile *f, Error **errp); void migration_fd_process_incoming(QEMUFile *f);
void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp); void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp);
void migration_incoming_process(void); void migration_incoming_process(void);
bool migration_has_all_channels(void); bool migration_has_all_channels(void);
uint64_t migrate_max_downtime(void);
void migrate_set_error(MigrationState *s, const Error *error); void migrate_set_error(MigrationState *s, const Error *error);
bool migrate_has_error(MigrationState *s); bool migrate_has_error(MigrationState *s);

View File

@ -228,8 +228,8 @@ static int multifd_recv_initial_packet(QIOChannel *c, Error **errp)
} }
if (msg.id > migrate_multifd_channels()) { if (msg.id > migrate_multifd_channels()) {
error_setg(errp, "multifd: received channel version %u " error_setg(errp, "multifd: received channel id %u is greater than "
"expected %u", msg.version, MULTIFD_VERSION); "number of channels %u", msg.id, migrate_multifd_channels());
return -1; return -1;
} }
@ -787,6 +787,7 @@ static void multifd_tls_outgoing_handshake(QIOTask *task,
trace_multifd_tls_outgoing_handshake_error(ioc, error_get_pretty(err)); trace_multifd_tls_outgoing_handshake_error(ioc, error_get_pretty(err));
migrate_set_error(migrate_get_current(), err);
/* /*
* Error happen, mark multifd_send_thread status as 'quit' although it * Error happen, mark multifd_send_thread status as 'quit' although it
* is not created, and then tell who pay attention to me. * is not created, and then tell who pay attention to me.
@ -794,6 +795,7 @@ static void multifd_tls_outgoing_handshake(QIOTask *task,
p->quit = true; p->quit = true;
qemu_sem_post(&multifd_send_state->channels_ready); qemu_sem_post(&multifd_send_state->channels_ready);
qemu_sem_post(&p->sem_sync); qemu_sem_post(&p->sem_sync);
error_free(err);
} }
static void *multifd_tls_handshake_thread(void *opaque) static void *multifd_tls_handshake_thread(void *opaque)
@ -847,14 +849,13 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
* so we mustn't call multifd_send_thread until then * so we mustn't call multifd_send_thread until then
*/ */
return multifd_tls_channel_connect(p, ioc, errp); return multifd_tls_channel_connect(p, ioc, errp);
}
} else {
migration_ioc_register_yank(ioc); migration_ioc_register_yank(ioc);
p->registered_yank = true; p->registered_yank = true;
p->c = ioc; p->c = ioc;
qemu_thread_create(&p->thread, p->name, multifd_send_thread, p, qemu_thread_create(&p->thread, p->name, multifd_send_thread, p,
QEMU_THREAD_JOINABLE); QEMU_THREAD_JOINABLE);
}
return true; return true;
} }
@ -950,12 +951,10 @@ int multifd_save_setup(Error **errp)
for (i = 0; i < thread_count; i++) { for (i = 0; i < thread_count; i++) {
MultiFDSendParams *p = &multifd_send_state->params[i]; MultiFDSendParams *p = &multifd_send_state->params[i];
Error *local_err = NULL;
int ret; int ret;
ret = multifd_send_state->ops->send_setup(p, &local_err); ret = multifd_send_state->ops->send_setup(p, errp);
if (ret) { if (ret) {
error_propagate(errp, local_err);
return ret; return ret;
} }
} }
@ -1194,12 +1193,10 @@ int multifd_load_setup(Error **errp)
for (i = 0; i < thread_count; i++) { for (i = 0; i < thread_count; i++) {
MultiFDRecvParams *p = &multifd_recv_state->params[i]; MultiFDRecvParams *p = &multifd_recv_state->params[i];
Error *local_err = NULL;
int ret; int ret;
ret = multifd_recv_state->ops->recv_setup(p, &local_err); ret = multifd_recv_state->ops->recv_setup(p, errp);
if (ret) { if (ret) {
error_propagate(errp, local_err);
return ret; return ret;
} }
} }

View File

@ -833,8 +833,10 @@ uint64_t migrate_max_postcopy_bandwidth(void)
MigMode migrate_mode(void) MigMode migrate_mode(void)
{ {
MigrationState *s = migrate_get_current(); MigrationState *s = migrate_get_current();
MigMode mode = s->parameters.mode;
return s->parameters.mode; assert(mode >= 0 && mode < MIG_MODE__MAX);
return mode;
} }
int migrate_multifd_channels(void) int migrate_multifd_channels(void)

View File

@ -4035,7 +4035,6 @@ static void rdma_accept_incoming_migration(void *opaque)
{ {
RDMAContext *rdma = opaque; RDMAContext *rdma = opaque;
QEMUFile *f; QEMUFile *f;
Error *local_err = NULL;
trace_qemu_rdma_accept_incoming_migration(); trace_qemu_rdma_accept_incoming_migration();
if (qemu_rdma_accept(rdma) < 0) { if (qemu_rdma_accept(rdma) < 0) {
@ -4057,10 +4056,7 @@ static void rdma_accept_incoming_migration(void *opaque)
} }
rdma->migration_started_on_destination = 1; rdma->migration_started_on_destination = 1;
migration_fd_process_incoming(f, &local_err); migration_fd_process_incoming(f);
if (local_err) {
error_reportf_err(local_err, "RDMA ERROR:");
}
} }
void rdma_start_incoming_migration(InetSocketAddress *host_port, void rdma_start_incoming_migration(InetSocketAddress *host_port,

View File

@ -3046,7 +3046,7 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
QEMUSnapshotInfo sn1, *sn = &sn1; QEMUSnapshotInfo sn1, *sn = &sn1;
int ret = -1, ret2; int ret = -1, ret2;
QEMUFile *f; QEMUFile *f;
int saved_vm_running; RunState saved_state = runstate_get();
uint64_t vm_state_size; uint64_t vm_state_size;
g_autoptr(GDateTime) now = g_date_time_new_now_local(); g_autoptr(GDateTime) now = g_date_time_new_now_local();
@ -3092,8 +3092,6 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
return false; return false;
} }
saved_vm_running = runstate_is_running();
global_state_store(); global_state_store();
vm_stop(RUN_STATE_SAVE_VM); vm_stop(RUN_STATE_SAVE_VM);
@ -3147,9 +3145,7 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
the_end: the_end:
bdrv_drain_all_end(); bdrv_drain_all_end();
if (saved_vm_running) { vm_resume(saved_state);
vm_start();
}
return ret == 0; return ret == 0;
} }
@ -3317,6 +3313,14 @@ err_drain:
return false; return false;
} }
void load_snapshot_resume(RunState state)
{
vm_resume(state);
if (state == RUN_STATE_RUNNING && runstate_get() == RUN_STATE_SUSPENDED) {
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, &error_abort);
}
}
bool delete_snapshot(const char *name, bool has_devices, bool delete_snapshot(const char *name, bool has_devices,
strList *devices, Error **errp) strList *devices, Error **errp)
{ {
@ -3381,16 +3385,15 @@ static void snapshot_load_job_bh(void *opaque)
{ {
Job *job = opaque; Job *job = opaque;
SnapshotJob *s = container_of(job, SnapshotJob, common); SnapshotJob *s = container_of(job, SnapshotJob, common);
int orig_vm_running; RunState orig_state = runstate_get();
job_progress_set_remaining(&s->common, 1); job_progress_set_remaining(&s->common, 1);
orig_vm_running = runstate_is_running();
vm_stop(RUN_STATE_RESTORE_VM); vm_stop(RUN_STATE_RESTORE_VM);
s->ret = load_snapshot(s->tag, s->vmstate, true, s->devices, s->errp); s->ret = load_snapshot(s->tag, s->vmstate, true, s->devices, s->errp);
if (s->ret && orig_vm_running) { if (s->ret) {
vm_start(); load_snapshot_resume(orig_state);
} }
job_progress_update(&s->common, 1); job_progress_update(&s->common, 1);

View File

@ -134,7 +134,7 @@
## ##
# @stop: # @stop:
# #
# Stop all guest VCPU execution. # Stop guest VM execution.
# #
# Since: 0.14 # Since: 0.14
# #
@ -143,6 +143,9 @@
# the guest remains paused once migration finishes, as if the -S # the guest remains paused once migration finishes, as if the -S
# option was passed on the command line. # option was passed on the command line.
# #
# In the "suspended" state, it will completely stop the VM and
# cause a transition to the "paused" state. (Since 9.0)
#
# Example: # Example:
# #
# -> { "execute": "stop" } # -> { "execute": "stop" }
@ -153,7 +156,7 @@
## ##
# @cont: # @cont:
# #
# Resume guest VCPU execution. # Resume guest VM execution.
# #
# Since: 0.14 # Since: 0.14
# #
@ -165,6 +168,10 @@
# guest starts once migration finishes, removing the effect of the # guest starts once migration finishes, removing the effect of the
# -S command line option if it was passed. # -S command line option if it was passed.
# #
# If the VM was previously suspended, and not been reset or woken,
# this command will transition back to the "suspended" state.
# (Since 9.0)
#
# Example: # Example:
# #
# -> { "execute": "cont" } # -> { "execute": "cont" }

View File

@ -102,7 +102,7 @@
## ##
# @StatusInfo: # @StatusInfo:
# #
# Information about VCPU run state # Information about VM run state
# #
# @running: true if all VCPUs are runnable, false if not runnable # @running: true if all VCPUs are runnable, false if not runnable
# #
@ -130,9 +130,9 @@
## ##
# @query-status: # @query-status:
# #
# Query the run status of all VCPUs # Query the run status of the VM
# #
# Returns: @StatusInfo reflecting all VCPUs # Returns: @StatusInfo reflecting the VM
# #
# Since: 0.14 # Since: 0.14
# #

View File

@ -259,14 +259,33 @@ void cpu_interrupt(CPUState *cpu, int mask)
} }
} }
/*
* True if the vm was previously suspended, and has not been woken or reset.
*/
static int vm_was_suspended;
void vm_set_suspended(bool suspended)
{
vm_was_suspended = suspended;
}
bool vm_get_suspended(void)
{
return vm_was_suspended;
}
static int do_vm_stop(RunState state, bool send_stop) static int do_vm_stop(RunState state, bool send_stop)
{ {
int ret = 0; int ret = 0;
RunState oldstate = runstate_get();
if (runstate_is_running()) { if (runstate_is_live(oldstate)) {
vm_was_suspended = (oldstate == RUN_STATE_SUSPENDED);
runstate_set(state); runstate_set(state);
cpu_disable_ticks(); cpu_disable_ticks();
if (oldstate == RUN_STATE_RUNNING) {
pause_all_vcpus(); pause_all_vcpus();
}
vm_state_notify(0, state); vm_state_notify(0, state);
if (send_stop) { if (send_stop) {
qapi_event_send_stop(); qapi_event_send_stop();
@ -679,11 +698,13 @@ int vm_stop(RunState state)
/** /**
* Prepare for (re)starting the VM. * Prepare for (re)starting the VM.
* Returns -1 if the vCPUs are not to be restarted (e.g. if they are already * Returns 0 if the vCPUs should be restarted, -1 on an error condition,
* running or in case of an error condition), 0 otherwise. * and 1 otherwise.
*/ */
int vm_prepare_start(bool step_pending) int vm_prepare_start(bool step_pending)
{ {
int ret = vm_was_suspended ? 1 : 0;
RunState state = vm_was_suspended ? RUN_STATE_SUSPENDED : RUN_STATE_RUNNING;
RunState requested; RunState requested;
qemu_vmstop_requested(&requested); qemu_vmstop_requested(&requested);
@ -714,9 +735,10 @@ int vm_prepare_start(bool step_pending)
qapi_event_send_resume(); qapi_event_send_resume();
cpu_enable_ticks(); cpu_enable_ticks();
runstate_set(RUN_STATE_RUNNING); runstate_set(state);
vm_state_notify(1, RUN_STATE_RUNNING); vm_state_notify(1, state);
return 0; vm_was_suspended = false;
return ret;
} }
void vm_start(void) void vm_start(void)
@ -726,11 +748,20 @@ void vm_start(void)
} }
} }
void vm_resume(RunState state)
{
if (runstate_is_live(state)) {
vm_start();
} else {
runstate_set(state);
}
}
/* does a state transition even if the VM is already stopped, /* does a state transition even if the VM is already stopped,
current state is forgotten forever */ current state is forgotten forever */
int vm_stop_force_state(RunState state) int vm_stop_force_state(RunState state)
{ {
if (runstate_is_running()) { if (runstate_is_live(runstate_get())) {
return vm_stop(state); return vm_stop(state);
} else { } else {
int ret; int ret;

View File

@ -77,6 +77,7 @@ typedef struct {
static const RunStateTransition runstate_transitions_def[] = { static const RunStateTransition runstate_transitions_def[] = {
{ RUN_STATE_PRELAUNCH, RUN_STATE_INMIGRATE }, { RUN_STATE_PRELAUNCH, RUN_STATE_INMIGRATE },
{ RUN_STATE_PRELAUNCH, RUN_STATE_SUSPENDED },
{ RUN_STATE_DEBUG, RUN_STATE_RUNNING }, { RUN_STATE_DEBUG, RUN_STATE_RUNNING },
{ RUN_STATE_DEBUG, RUN_STATE_FINISH_MIGRATE }, { RUN_STATE_DEBUG, RUN_STATE_FINISH_MIGRATE },
@ -108,6 +109,7 @@ static const RunStateTransition runstate_transitions_def[] = {
{ RUN_STATE_PAUSED, RUN_STATE_POSTMIGRATE }, { RUN_STATE_PAUSED, RUN_STATE_POSTMIGRATE },
{ RUN_STATE_PAUSED, RUN_STATE_PRELAUNCH }, { RUN_STATE_PAUSED, RUN_STATE_PRELAUNCH },
{ RUN_STATE_PAUSED, RUN_STATE_COLO}, { RUN_STATE_PAUSED, RUN_STATE_COLO},
{ RUN_STATE_PAUSED, RUN_STATE_SUSPENDED},
{ RUN_STATE_POSTMIGRATE, RUN_STATE_RUNNING }, { RUN_STATE_POSTMIGRATE, RUN_STATE_RUNNING },
{ RUN_STATE_POSTMIGRATE, RUN_STATE_FINISH_MIGRATE }, { RUN_STATE_POSTMIGRATE, RUN_STATE_FINISH_MIGRATE },
@ -131,6 +133,7 @@ static const RunStateTransition runstate_transitions_def[] = {
{ RUN_STATE_RESTORE_VM, RUN_STATE_RUNNING }, { RUN_STATE_RESTORE_VM, RUN_STATE_RUNNING },
{ RUN_STATE_RESTORE_VM, RUN_STATE_PRELAUNCH }, { RUN_STATE_RESTORE_VM, RUN_STATE_PRELAUNCH },
{ RUN_STATE_RESTORE_VM, RUN_STATE_SUSPENDED },
{ RUN_STATE_COLO, RUN_STATE_RUNNING }, { RUN_STATE_COLO, RUN_STATE_RUNNING },
{ RUN_STATE_COLO, RUN_STATE_PRELAUNCH }, { RUN_STATE_COLO, RUN_STATE_PRELAUNCH },
@ -149,6 +152,7 @@ static const RunStateTransition runstate_transitions_def[] = {
{ RUN_STATE_RUNNING, RUN_STATE_COLO}, { RUN_STATE_RUNNING, RUN_STATE_COLO},
{ RUN_STATE_SAVE_VM, RUN_STATE_RUNNING }, { RUN_STATE_SAVE_VM, RUN_STATE_RUNNING },
{ RUN_STATE_SAVE_VM, RUN_STATE_SUSPENDED },
{ RUN_STATE_SHUTDOWN, RUN_STATE_PAUSED }, { RUN_STATE_SHUTDOWN, RUN_STATE_PAUSED },
{ RUN_STATE_SHUTDOWN, RUN_STATE_FINISH_MIGRATE }, { RUN_STATE_SHUTDOWN, RUN_STATE_FINISH_MIGRATE },
@ -161,6 +165,10 @@ static const RunStateTransition runstate_transitions_def[] = {
{ RUN_STATE_SUSPENDED, RUN_STATE_FINISH_MIGRATE }, { RUN_STATE_SUSPENDED, RUN_STATE_FINISH_MIGRATE },
{ RUN_STATE_SUSPENDED, RUN_STATE_PRELAUNCH }, { RUN_STATE_SUSPENDED, RUN_STATE_PRELAUNCH },
{ RUN_STATE_SUSPENDED, RUN_STATE_COLO}, { RUN_STATE_SUSPENDED, RUN_STATE_COLO},
{ RUN_STATE_SUSPENDED, RUN_STATE_PAUSED},
{ RUN_STATE_SUSPENDED, RUN_STATE_SAVE_VM },
{ RUN_STATE_SUSPENDED, RUN_STATE_RESTORE_VM },
{ RUN_STATE_SUSPENDED, RUN_STATE_SHUTDOWN },
{ RUN_STATE_WATCHDOG, RUN_STATE_RUNNING }, { RUN_STATE_WATCHDOG, RUN_STATE_RUNNING },
{ RUN_STATE_WATCHDOG, RUN_STATE_FINISH_MIGRATE }, { RUN_STATE_WATCHDOG, RUN_STATE_FINISH_MIGRATE },
@ -502,6 +510,7 @@ void qemu_system_reset(ShutdownCause reason)
qapi_event_send_reset(shutdown_caused_by_guest(reason), reason); qapi_event_send_reset(shutdown_caused_by_guest(reason), reason);
} }
cpu_synchronize_all_post_reset(); cpu_synchronize_all_post_reset();
vm_set_suspended(false);
} }
/* /*

View File

@ -2710,7 +2710,9 @@ void qmp_x_exit_preconfig(Error **errp)
qemu_machine_creation_done(); qemu_machine_creation_done();
if (loadvm) { if (loadvm) {
RunState state = autostart ? RUN_STATE_RUNNING : runstate_get();
load_snapshot(loadvm, NULL, false, NULL, &error_fatal); load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
load_snapshot_resume(state);
} }
if (replay_mode != REPLAY_MODE_NONE) { if (replay_mode != REPLAY_MODE_NONE) {
replay_vmstate_init(); replay_vmstate_init();

View File

@ -4,9 +4,10 @@
.PHONY: all clean .PHONY: all clean
all: a-b-bootblock.h all: a-b-bootblock.h
a-b-bootblock.h: x86.bootsect a-b-bootblock.h: x86.bootsect x86.o
echo "$$__note" > header.tmp echo "$$__note" > header.tmp
xxd -i $< | sed -e 's/.*int.*//' >> header.tmp xxd -i $< | sed -e 's/.*int.*//' >> header.tmp
nm x86.o | awk '{print "#define SYM_"$$3" 0x"$$1}' >> header.tmp
mv header.tmp $@ mv header.tmp $@
x86.bootsect: x86.boot x86.bootsect: x86.boot
@ -16,7 +17,7 @@ x86.boot: x86.o
$(CROSS_PREFIX)objcopy -O binary $< $@ $(CROSS_PREFIX)objcopy -O binary $< $@
x86.o: a-b-bootblock.S x86.o: a-b-bootblock.S
$(CROSS_PREFIX)gcc -m32 -march=i486 -c $< -o $@ $(CROSS_PREFIX)gcc -I.. -m32 -march=i486 -c $< -o $@
clean: clean:
@rm -rf *.boot *.o *.bootsect @rm -rf *.boot *.o *.bootsect

View File

@ -9,6 +9,23 @@
# #
# Author: dgilbert@redhat.com # Author: dgilbert@redhat.com
#include "migration-test.h"
#define ACPI_ENABLE 0xf1
#define ACPI_PORT_SMI_CMD 0xb2
#define ACPI_PM_BASE 0x600
#define PM1A_CNT_OFFSET 4
#define ACPI_SCI_ENABLE 0x0001
#define ACPI_SLEEP_TYPE 0x0400
#define ACPI_SLEEP_ENABLE 0x2000
#define SLEEP (ACPI_SCI_ENABLE + ACPI_SLEEP_TYPE + ACPI_SLEEP_ENABLE)
#define LOW_ADDR X86_TEST_MEM_START
#define HIGH_ADDR X86_TEST_MEM_END
/* Save the suspended status at an address that is not written in the loop. */
#define suspended (X86_TEST_MEM_START + 4)
.code16 .code16
.org 0x7c00 .org 0x7c00
@ -35,8 +52,8 @@ start: # at 0x7c00 ?
mov %eax,%ds mov %eax,%ds
# Start from 1MB # Start from 1MB
.set TEST_MEM_START, (1024*1024) .set TEST_MEM_START, X86_TEST_MEM_START
.set TEST_MEM_END, (100*1024*1024) .set TEST_MEM_END, X86_TEST_MEM_END
mov $65,%ax mov $65,%ax
mov $0x3f8,%dx mov $0x3f8,%dx
@ -69,7 +86,30 @@ innerloop:
mov $0x3f8,%dx mov $0x3f8,%dx
outb %al,%dx outb %al,%dx
jmp mainloop # should this test suspend?
mov (suspend_me),%eax
cmp $0,%eax
je mainloop
# are we waking after suspend? do not suspend again.
mov $suspended,%eax
mov (%eax),%eax
cmp $1,%eax
je mainloop
# enable acpi
mov $ACPI_ENABLE,%al
outb %al,$ACPI_PORT_SMI_CMD
# suspend to ram
mov $suspended,%eax
movl $1,(%eax)
mov $SLEEP,%ax
mov $(ACPI_PM_BASE + PM1A_CNT_OFFSET),%dx
outw %ax,%dx
# not reached. The wakeup causes reset and restart at 0x7c00, and we
# do not save and restore registers as a real kernel would do.
# GDT magic from old (GPLv2) Grub startup.S # GDT magic from old (GPLv2) Grub startup.S
.p2align 2 /* force 4-byte alignment */ .p2align 2 /* force 4-byte alignment */
@ -95,6 +135,10 @@ gdtdesc:
.word 0x27 /* limit */ .word 0x27 /* limit */
.long gdt /* addr */ .long gdt /* addr */
/* test launcher can poke a 1 here to exercise suspend */
suspend_me:
.int 0
/* I'm a bootable disk */ /* I'm a bootable disk */
.org 0x7dfe .org 0x7dfe
.byte 0x55 .byte 0x55

View File

@ -4,7 +4,7 @@
* the header and the assembler differences in your patch submission. * the header and the assembler differences in your patch submission.
*/ */
unsigned char x86_bootsect[] = { unsigned char x86_bootsect[] = {
0xfa, 0x0f, 0x01, 0x16, 0x8c, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xfa, 0x0f, 0x01, 0x16, 0xb8, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00, 0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02,
0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41, 0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41,
@ -13,13 +13,13 @@ unsigned char x86_bootsect[] = {
0x40, 0x06, 0x7c, 0xf1, 0xb8, 0x00, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x05, 0x40, 0x06, 0x7c, 0xf1, 0xb8, 0x00, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x05,
0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40, 0x06, 0x7c, 0xf2, 0xfe, 0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40, 0x06, 0x7c, 0xf2, 0xfe,
0xc3, 0x80, 0xe3, 0x3f, 0x75, 0xe6, 0x66, 0xb8, 0x42, 0x00, 0x66, 0xba, 0xc3, 0x80, 0xe3, 0x3f, 0x75, 0xe6, 0x66, 0xb8, 0x42, 0x00, 0x66, 0xba,
0xf8, 0x03, 0xee, 0xeb, 0xdb, 0x8d, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0xee, 0xa1, 0xbe, 0x7c, 0x00, 0x00, 0x83, 0xf8, 0x00, 0x74,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, 0xd3, 0xb8, 0x04, 0x00, 0x10, 0x00, 0x8b, 0x00, 0x83, 0xf8, 0x01, 0x74,
0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0x74, 0x7c, 0xc7, 0xb0, 0xf1, 0xe6, 0xb2, 0xb8, 0x04, 0x00, 0x10, 0x00, 0xc7, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x66, 0xb8, 0x01, 0x24, 0x66, 0xba, 0x04, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xef, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, 0xff, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0xa0, 0x7c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -49,3 +49,13 @@ unsigned char x86_bootsect[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
}; };
#define SYM_do_zero 0x00007c3d
#define SYM_gdt 0x00007ca0
#define SYM_gdtdesc 0x00007cb8
#define SYM_innerloop 0x00007c51
#define SYM_mainloop 0x00007c4c
#define SYM_pre_zero 0x00007c38
#define SYM_start 0x00007c00
#define SYM_suspend_me 0x00007cbe
#define SYM_TEST_MEM_END 0x06400000
#define SYM_TEST_MEM_START 0x00100000

View File

@ -24,26 +24,19 @@
*/ */
#define MIGRATION_STATUS_WAIT_TIMEOUT 120 #define MIGRATION_STATUS_WAIT_TIMEOUT 120
bool migrate_watch_for_stop(QTestState *who, const char *name, bool migrate_watch_for_events(QTestState *who, const char *name,
QDict *event, void *opaque) QDict *event, void *opaque)
{ {
bool *seen = opaque; QTestMigrationState *state = opaque;
if (g_str_equal(name, "STOP")) { if (g_str_equal(name, "STOP")) {
*seen = true; state->stop_seen = true;
return true; return true;
} } else if (g_str_equal(name, "SUSPEND")) {
state->suspend_seen = true;
return false; return true;
} } else if (g_str_equal(name, "RESUME")) {
state->resume_seen = true;
bool migrate_watch_for_resume(QTestState *who, const char *name,
QDict *event, void *opaque)
{
bool *seen = opaque;
if (g_str_equal(name, "RESUME")) {
*seen = true;
return true; return true;
} }

View File

@ -15,9 +15,14 @@
#include "libqtest.h" #include "libqtest.h"
bool migrate_watch_for_stop(QTestState *who, const char *name, typedef struct QTestMigrationState {
QDict *event, void *opaque); bool stop_seen;
bool migrate_watch_for_resume(QTestState *who, const char *name, bool resume_seen;
bool suspend_seen;
bool suspend_me;
} QTestMigrationState;
bool migrate_watch_for_events(QTestState *who, const char *name,
QDict *event, void *opaque); QDict *event, void *opaque);
G_GNUC_PRINTF(3, 4) G_GNUC_PRINTF(3, 4)

View File

@ -43,8 +43,8 @@
unsigned start_address; unsigned start_address;
unsigned end_address; unsigned end_address;
static bool uffd_feature_thread_id; static bool uffd_feature_thread_id;
static bool got_src_stop; static QTestMigrationState src_state;
static bool got_dst_resume; static QTestMigrationState dst_state;
/* /*
* An initial 3 MB offset is used as that corresponds * An initial 3 MB offset is used as that corresponds
@ -133,7 +133,7 @@ static char *bootpath;
#include "tests/migration/aarch64/a-b-kernel.h" #include "tests/migration/aarch64/a-b-kernel.h"
#include "tests/migration/s390x/a-b-bios.h" #include "tests/migration/s390x/a-b-bios.h"
static void bootfile_create(char *dir) static void bootfile_create(char *dir, bool suspend_me)
{ {
const char *arch = qtest_get_arch(); const char *arch = qtest_get_arch();
unsigned char *content; unsigned char *content;
@ -143,6 +143,7 @@ static void bootfile_create(char *dir)
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
/* the assembled x86 boot sector should be exactly one sector large */ /* the assembled x86 boot sector should be exactly one sector large */
g_assert(sizeof(x86_bootsect) == 512); g_assert(sizeof(x86_bootsect) == 512);
x86_bootsect[SYM_suspend_me - SYM_start] = suspend_me;
content = x86_bootsect; content = x86_bootsect;
len = sizeof(x86_bootsect); len = sizeof(x86_bootsect);
} else if (g_str_equal(arch, "s390x")) { } else if (g_str_equal(arch, "s390x")) {
@ -177,7 +178,7 @@ static void bootfile_delete(void)
/* /*
* Wait for some output in the serial output file, * Wait for some output in the serial output file,
* we get an 'A' followed by an endless string of 'B's * we get an 'A' followed by an endless string of 'B's
* but on the destination we won't have the A. * but on the destination we won't have the A (unless we enabled suspend/resume)
*/ */
static void wait_for_serial(const char *side) static void wait_for_serial(const char *side)
{ {
@ -230,6 +231,27 @@ static void wait_for_serial(const char *side)
} while (true); } while (true);
} }
static void wait_for_stop(QTestState *who, QTestMigrationState *state)
{
if (!state->stop_seen) {
qtest_qmp_eventwait(who, "STOP");
}
}
static void wait_for_resume(QTestState *who, QTestMigrationState *state)
{
if (!state->resume_seen) {
qtest_qmp_eventwait(who, "RESUME");
}
}
static void wait_for_suspend(QTestState *who, QTestMigrationState *state)
{
if (state->suspend_me && !state->suspend_seen) {
qtest_qmp_eventwait(who, "SUSPEND");
}
}
/* /*
* It's tricky to use qemu's migration event capability with qtest, * It's tricky to use qemu's migration event capability with qtest,
* events suddenly appearing confuse the qmp()/hmp() responses. * events suddenly appearing confuse the qmp()/hmp() responses.
@ -277,21 +299,19 @@ static void read_blocktime(QTestState *who)
qobject_unref(rsp_return); qobject_unref(rsp_return);
} }
/*
* Wait for two changes in the migration pass count, but bail if we stop.
*/
static void wait_for_migration_pass(QTestState *who) static void wait_for_migration_pass(QTestState *who)
{ {
uint64_t initial_pass = get_migration_pass(who); uint64_t pass, prev_pass = 0, changes = 0;
uint64_t pass;
/* Wait for the 1st sync */ while (changes < 2 && !src_state.stop_seen && !src_state.suspend_seen) {
while (!got_src_stop && !initial_pass) {
usleep(1000);
initial_pass = get_migration_pass(who);
}
do {
usleep(1000); usleep(1000);
pass = get_migration_pass(who); pass = get_migration_pass(who);
} while (pass == initial_pass && !got_src_stop); changes += (pass != prev_pass);
prev_pass = pass;
}
} }
static void check_guests_ram(QTestState *who) static void check_guests_ram(QTestState *who)
@ -571,6 +591,12 @@ static void migrate_wait_for_dirty_mem(QTestState *from,
usleep(1000 * 10); usleep(1000 * 10);
} while (qtest_readq(to, marker_address) != MAGIC_MARKER); } while (qtest_readq(to, marker_address) != MAGIC_MARKER);
/* If suspended, src only iterates once, and watch_byte may never change */
if (src_state.suspend_me) {
return;
}
/* /*
* Now ensure that already transferred bytes are * Now ensure that already transferred bytes are
* dirty again from the guest workload. Note the * dirty again from the guest workload. Note the
@ -617,10 +643,7 @@ static void migrate_postcopy_start(QTestState *from, QTestState *to)
{ {
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }"); qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
qtest_qmp_eventwait(to, "RESUME"); qtest_qmp_eventwait(to, "RESUME");
} }
@ -637,6 +660,8 @@ typedef struct {
bool use_dirty_ring; bool use_dirty_ring;
const char *opts_source; const char *opts_source;
const char *opts_target; const char *opts_target;
/* suspend the src before migrating to dest. */
bool suspend_me;
} MigrateStart; } MigrateStart;
/* /*
@ -756,8 +781,11 @@ static int test_migrate_start(QTestState **from, QTestState **to,
} }
} }
got_src_stop = false; dst_state = (QTestMigrationState) { };
got_dst_resume = false; src_state = (QTestMigrationState) { };
bootfile_create(tmpfs, args->suspend_me);
src_state.suspend_me = args->suspend_me;
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
memory_size = "150M"; memory_size = "150M";
@ -848,8 +876,8 @@ static int test_migrate_start(QTestState **from, QTestState **to,
if (!args->only_target) { if (!args->only_target) {
*from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source); *from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source);
qtest_qmp_set_event_callback(*from, qtest_qmp_set_event_callback(*from,
migrate_watch_for_stop, migrate_watch_for_events,
&got_src_stop); &src_state);
} }
cmd_target = g_strdup_printf("-accel kvm%s -accel tcg " cmd_target = g_strdup_printf("-accel kvm%s -accel tcg "
@ -869,8 +897,8 @@ static int test_migrate_start(QTestState **from, QTestState **to,
ignore_stderr); ignore_stderr);
*to = qtest_init_with_env(QEMU_ENV_DST, cmd_target); *to = qtest_init_with_env(QEMU_ENV_DST, cmd_target);
qtest_qmp_set_event_callback(*to, qtest_qmp_set_event_callback(*to,
migrate_watch_for_resume, migrate_watch_for_events,
&got_dst_resume); &dst_state);
/* /*
* Remove shmem file immediately to avoid memory leak in test failed case. * Remove shmem file immediately to avoid memory leak in test failed case.
@ -1319,6 +1347,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
/* Wait for the first serial output from the source */ /* Wait for the first serial output from the source */
wait_for_serial("src_serial"); wait_for_serial("src_serial");
wait_for_suspend(from, &src_state);
g_autofree char *uri = migrate_get_socket_address(to, "socket-address"); g_autofree char *uri = migrate_get_socket_address(to, "socket-address");
migrate_qmp(from, uri, "{}"); migrate_qmp(from, uri, "{}");
@ -1336,6 +1365,11 @@ static void migrate_postcopy_complete(QTestState *from, QTestState *to,
{ {
wait_for_migration_complete(from); wait_for_migration_complete(from);
if (args->start.suspend_me) {
/* wakeup succeeds only if guest is suspended */
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
}
/* Make sure we get at least one "B" on destination */ /* Make sure we get at least one "B" on destination */
wait_for_serial("dest_serial"); wait_for_serial("dest_serial");
@ -1369,6 +1403,15 @@ static void test_postcopy(void)
test_postcopy_common(&args); test_postcopy_common(&args);
} }
static void test_postcopy_suspend(void)
{
MigrateCommon args = {
.start.suspend_me = true,
};
test_postcopy_common(&args);
}
static void test_postcopy_compress(void) static void test_postcopy_compress(void)
{ {
MigrateCommon args = { MigrateCommon args = {
@ -1703,6 +1746,7 @@ static void test_precopy_common(MigrateCommon *args)
/* Wait for the first serial output from the source */ /* Wait for the first serial output from the source */
if (args->result == MIG_TEST_SUCCEED) { if (args->result == MIG_TEST_SUCCEED) {
wait_for_serial("src_serial"); wait_for_serial("src_serial");
wait_for_suspend(from, &src_state);
} }
if (args->live) { if (args->live) {
@ -1717,9 +1761,7 @@ static void test_precopy_common(MigrateCommon *args)
*/ */
if (args->result == MIG_TEST_SUCCEED) { if (args->result == MIG_TEST_SUCCEED) {
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}"); qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
migrate_ensure_converge(from); migrate_ensure_converge(from);
} }
} }
@ -1765,9 +1807,8 @@ static void test_precopy_common(MigrateCommon *args)
*/ */
wait_for_migration_complete(from); wait_for_migration_complete(from);
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
} else { } else {
wait_for_migration_complete(from); wait_for_migration_complete(from);
/* /*
@ -1780,8 +1821,11 @@ static void test_precopy_common(MigrateCommon *args)
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}"); qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
} }
if (!got_dst_resume) { wait_for_resume(to, &dst_state);
qtest_qmp_eventwait(to, "RESUME");
if (args->start.suspend_me) {
/* wakeup succeeds only if guest is suspended */
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
} }
wait_for_serial("dest_serial"); wait_for_serial("dest_serial");
@ -1821,9 +1865,7 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
if (stop_src) { if (stop_src) {
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}"); qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
} }
if (args->result == MIG_TEST_QMP_ERROR) { if (args->result == MIG_TEST_QMP_ERROR) {
@ -1844,10 +1886,7 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
if (stop_src) { if (stop_src) {
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}"); qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
} }
wait_for_resume(to, &dst_state);
if (!got_dst_resume) {
qtest_qmp_eventwait(to, "RESUME");
}
wait_for_serial("dest_serial"); wait_for_serial("dest_serial");
@ -1875,6 +1914,34 @@ static void test_precopy_unix_plain(void)
test_precopy_common(&args); test_precopy_common(&args);
} }
static void test_precopy_unix_suspend_live(void)
{
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
MigrateCommon args = {
.listen_uri = uri,
.connect_uri = uri,
/*
* despite being live, the test is fast because the src
* suspends immediately.
*/
.live = true,
.start.suspend_me = true,
};
test_precopy_common(&args);
}
static void test_precopy_unix_suspend_notlive(void)
{
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
MigrateCommon args = {
.listen_uri = uri,
.connect_uri = uri,
.start.suspend_me = true,
};
test_precopy_common(&args);
}
static void test_precopy_unix_dirty_ring(void) static void test_precopy_unix_dirty_ring(void)
{ {
@ -1966,9 +2033,7 @@ static void test_ignore_shared(void)
migrate_wait_for_dirty_mem(from, to); migrate_wait_for_dirty_mem(from, to);
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
qtest_qmp_eventwait(to, "RESUME"); qtest_qmp_eventwait(to, "RESUME");
@ -2503,7 +2568,7 @@ static void test_migrate_auto_converge(void)
break; break;
} }
usleep(20); usleep(20);
g_assert_false(got_src_stop); g_assert_false(src_state.stop_seen);
} while (true); } while (true);
/* The first percentage of throttling should be at least init_pct */ /* The first percentage of throttling should be at least init_pct */
g_assert_cmpint(percentage, >=, init_pct); g_assert_cmpint(percentage, >=, init_pct);
@ -2842,9 +2907,7 @@ static void test_multifd_tcp_cancel(void)
migrate_ensure_converge(from); migrate_ensure_converge(from);
if (!got_src_stop) { wait_for_stop(from, &src_state);
qtest_qmp_eventwait(from, "STOP");
}
qtest_qmp_eventwait(to2, "RESUME"); qtest_qmp_eventwait(to2, "RESUME");
wait_for_serial("dest_serial"); wait_for_serial("dest_serial");
@ -2985,7 +3048,9 @@ static int64_t get_limit_rate(QTestState *who)
static QTestState *dirtylimit_start_vm(void) static QTestState *dirtylimit_start_vm(void)
{ {
QTestState *vm = NULL; QTestState *vm = NULL;
g_autofree gchar * g_autofree gchar *cmd = NULL;
bootfile_create(tmpfs, false);
cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 " cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 "
"-name dirtylimit-test,debug-threads=on " "-name dirtylimit-test,debug-threads=on "
"-m 150M -smp 1 " "-m 150M -smp 1 "
@ -3177,7 +3242,7 @@ static void test_migrate_dirty_limit(void)
throttle_us_per_full = throttle_us_per_full =
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round"); read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
usleep(100); usleep(100);
g_assert_false(got_src_stop); g_assert_false(src_state.stop_seen);
} }
/* Now cancel migrate and wait for dirty limit throttle switch off */ /* Now cancel migrate and wait for dirty limit throttle switch off */
@ -3189,7 +3254,7 @@ static void test_migrate_dirty_limit(void)
throttle_us_per_full = throttle_us_per_full =
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round"); read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
usleep(100); usleep(100);
g_assert_false(got_src_stop); g_assert_false(src_state.stop_seen);
} while (throttle_us_per_full != 0 && --max_try_count); } while (throttle_us_per_full != 0 && --max_try_count);
/* Assert dirty limit is not in service */ /* Assert dirty limit is not in service */
@ -3218,7 +3283,7 @@ static void test_migrate_dirty_limit(void)
throttle_us_per_full = throttle_us_per_full =
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round"); read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
usleep(100); usleep(100);
g_assert_false(got_src_stop); g_assert_false(src_state.stop_seen);
} }
/* /*
@ -3277,7 +3342,7 @@ static bool kvm_dirty_ring_supported(void)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
bool has_kvm, has_tcg; bool has_kvm, has_tcg;
bool has_uffd; bool has_uffd, is_x86;
const char *arch; const char *arch;
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
const char *qemu_src = getenv(QEMU_ENV_SRC); const char *qemu_src = getenv(QEMU_ENV_SRC);
@ -3307,6 +3372,7 @@ int main(int argc, char **argv)
has_uffd = ufd_version_check(); has_uffd = ufd_version_check();
arch = qtest_get_arch(); arch = qtest_get_arch();
is_x86 = !strcmp(arch, "i386") || !strcmp(arch, "x86_64");
/* /*
* On ppc64, the test only works with kvm-hv, but not with kvm-pr and TCG * On ppc64, the test only works with kvm-hv, but not with kvm-pr and TCG
@ -3334,10 +3400,16 @@ int main(int argc, char **argv)
g_get_tmp_dir(), err->message); g_get_tmp_dir(), err->message);
} }
g_assert(tmpfs); g_assert(tmpfs);
bootfile_create(tmpfs);
module_call_init(MODULE_INIT_QOM); module_call_init(MODULE_INIT_QOM);
if (is_x86) {
qtest_add_func("/migration/precopy/unix/suspend/live",
test_precopy_unix_suspend_live);
qtest_add_func("/migration/precopy/unix/suspend/notlive",
test_precopy_unix_suspend_notlive);
}
if (has_uffd) { if (has_uffd) {
qtest_add_func("/migration/postcopy/plain", test_postcopy); qtest_add_func("/migration/postcopy/plain", test_postcopy);
qtest_add_func("/migration/postcopy/recovery/plain", qtest_add_func("/migration/postcopy/recovery/plain",
@ -3355,7 +3427,10 @@ int main(int argc, char **argv)
qtest_add_func("/migration/postcopy/recovery/double-failures", qtest_add_func("/migration/postcopy/recovery/double-failures",
test_postcopy_recovery_double_fail); test_postcopy_recovery_double_fail);
#endif /* _WIN32 */ #endif /* _WIN32 */
if (is_x86) {
qtest_add_func("/migration/postcopy/suspend",
test_postcopy_suspend);
}
} }
qtest_add_func("/migration/bad_dest", test_baddest); qtest_add_func("/migration/bad_dest", test_baddest);