aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backends/tpm/tpm_emulator.c40
-rw-r--r--crypto/tlssession.c7
-rw-r--r--docs/conf.py2
-rw-r--r--docs/devel/migration/CPR.rst112
-rw-r--r--docs/devel/migration/main.rst19
-rw-r--r--docs/interop/firmware.json4
-rw-r--r--docs/specs/riscv-iommu.rst35
-rw-r--r--docs/sphinx-static/theme_overrides.css3
-rw-r--r--hmp-commands.hx2
-rw-r--r--hw/char/sifive_uart.c36
-rw-r--r--hw/char/trace-events14
-rw-r--r--hw/core/cpu-common.c1
-rw-r--r--hw/cxl/cxl-host.c7
-rw-r--r--hw/display/virtio-gpu.c13
-rw-r--r--hw/intc/apic_common.c1
-rw-r--r--hw/intc/riscv_aclint.c7
-rw-r--r--hw/misc/ivshmem-flat.c8
-rw-r--r--hw/net/virtio-net.c8
-rw-r--r--hw/pci/pci.c5
-rw-r--r--hw/remote/vfio-user-obj.c13
-rw-r--r--hw/riscv/riscv-iommu.c154
-rw-r--r--hw/s390x/virtio-ccw.c4
-rw-r--r--hw/scsi/spapr_vscsi.c6
-rw-r--r--hw/vfio/container-legacy.c3
-rw-r--r--hw/vfio/cpr-iommufd.c3
-rw-r--r--hw/vfio/cpr-legacy.c9
-rw-r--r--hw/vfio/cpr.c13
-rw-r--r--hw/vfio/pci.c9
-rw-r--r--hw/virtio/virtio-mmio.c5
-rw-r--r--hw/virtio/virtio-pci.c4
-rw-r--r--hw/virtio/virtio.c13
-rw-r--r--include/crypto/tlssession.h10
-rw-r--r--include/exec/cpu-common.h10
-rw-r--r--include/exec/gdbstub.h3
-rw-r--r--include/hw/core/cpu.h1
-rw-r--r--include/hw/intc/riscv_aclint.h4
-rw-r--r--include/migration/colo.h2
-rw-r--r--include/migration/cpr.h10
-rw-r--r--include/migration/misc.h12
-rw-r--r--include/migration/vmstate.h29
-rw-r--r--include/qapi/error.h6
-rw-r--r--include/qemu/log-for-trace.h4
-rw-r--r--include/qemu/log.h44
-rw-r--r--include/qemu/osdep.h11
-rw-r--r--include/system/memory.h24
-rw-r--r--include/system/os-win32.h5
-rw-r--r--io/channel-socket.c4
-rw-r--r--io/channel-tls.c21
-rw-r--r--io/channel-watch.c6
-rw-r--r--linux-user/syscall.c89
-rw-r--r--migration/colo.c10
-rw-r--r--migration/cpr-exec.c194
-rw-r--r--migration/cpr.c51
-rw-r--r--migration/meson.build1
-rw-r--r--migration/migration-hmp-cmds.c44
-rw-r--r--migration/migration.c116
-rw-r--r--migration/multifd.c65
-rw-r--r--migration/options.c14
-rw-r--r--migration/postcopy-ram.c9
-rw-r--r--migration/postcopy-ram.h2
-rw-r--r--migration/qemu-file.c7
-rw-r--r--migration/ram.c17
-rw-r--r--migration/ram.h4
-rw-r--r--migration/savevm.c329
-rw-r--r--migration/savevm.h7
-rw-r--r--migration/trace-events1
-rw-r--r--migration/vmstate-types.c61
-rw-r--r--migration/vmstate.c103
-rw-r--r--monitor/hmp-cmds.c7
-rw-r--r--net/slirp.c9
-rw-r--r--pc-bios/opensbi-riscv32-generic-fw_dynamic.binbin268312 -> 268752 bytes
-rw-r--r--pc-bios/opensbi-riscv64-generic-fw_dynamic.binbin272504 -> 273048 bytes
-rw-r--r--qapi/migration.json46
m---------roms/opensbi0
-rw-r--r--rust/Cargo.lock8
-rw-r--r--rust/Cargo.toml1
-rw-r--r--rust/hw/char/pl011/Cargo.toml1
-rw-r--r--rust/hw/char/pl011/meson.build1
-rw-r--r--rust/hw/char/pl011/src/device.rs57
-rw-r--r--rust/hw/core/src/qdev.rs33
-rw-r--r--rust/meson.build2
-rw-r--r--rust/trace/Cargo.toml19
-rw-r--r--rust/trace/meson.build19
-rw-r--r--rust/trace/src/lib.rs39
-rw-r--r--rust/util/src/log.rs2
-rwxr-xr-xscripts/analyse-locks-simpletrace.py1
-rw-r--r--scripts/modinfo-collect.py1
-rw-r--r--scripts/modinfo-generate.py1
-rwxr-xr-xscripts/oss-fuzz/minimize_qtest_trace.py1
-rwxr-xr-xscripts/oss-fuzz/output_reproducer.py1
-rwxr-xr-xscripts/oss-fuzz/reorder_fuzzer_qtest_trace.py1
-rw-r--r--scripts/probe-gdb-support.py1
-rw-r--r--scripts/qapi/error.py2
-rw-r--r--scripts/qapi/expr.py2
-rw-r--r--scripts/qapi/gen.py2
-rw-r--r--scripts/qapi/parser.py2
-rw-r--r--scripts/qapi/schema.py2
-rwxr-xr-xscripts/qemu-plugin-symbols.py1
-rw-r--r--scripts/qemugdb/tcg.py2
-rw-r--r--scripts/qemugdb/timers.py1
-rwxr-xr-xscripts/replay-dump.py1
-rwxr-xr-xscripts/tracetool.py1
-rw-r--r--scripts/tracetool/__init__.py202
-rw-r--r--scripts/tracetool/backend/__init__.py43
-rw-r--r--scripts/tracetool/backend/dtrace.py2
-rw-r--r--scripts/tracetool/backend/ftrace.py26
-rw-r--r--scripts/tracetool/backend/log.py22
-rw-r--r--scripts/tracetool/backend/simple.py17
-rw-r--r--scripts/tracetool/backend/syslog.py17
-rw-r--r--scripts/tracetool/backend/ust.py2
-rw-r--r--scripts/tracetool/format/__init__.py4
-rw-r--r--scripts/tracetool/format/c.py2
-rw-r--r--scripts/tracetool/format/d.py2
-rw-r--r--scripts/tracetool/format/h.py20
-rw-r--r--scripts/tracetool/format/log_stap.py4
-rw-r--r--scripts/tracetool/format/rs.py64
-rw-r--r--scripts/tracetool/format/simpletrace_stap.py4
-rw-r--r--scripts/tracetool/format/stap.py2
-rw-r--r--scripts/tracetool/format/ust_events_c.py2
-rw-r--r--scripts/tracetool/format/ust_events_h.py2
-rw-r--r--stubs/cpu-destroy-address-spaces.c15
-rw-r--r--stubs/meson.build1
-rw-r--r--system/memory.c20
-rw-r--r--system/physmem.c32
-rw-r--r--system/vl.c4
-rw-r--r--target/riscv/cpu.c2
-rw-r--r--target/riscv/cpu.h3
-rw-r--r--target/riscv/csr.c5
-rw-r--r--target/riscv/helper.h21
-rw-r--r--target/riscv/insn_trans/trans_rvv.c.inc50
-rw-r--r--target/riscv/insn_trans/trans_rvzce.c.inc6
-rw-r--r--target/riscv/insn_trans/trans_rvzicfiss.c.inc9
-rw-r--r--target/riscv/kvm/kvm-cpu.c2
-rw-r--r--target/riscv/machine.c28
-rw-r--r--target/riscv/op_helper.c49
-rw-r--r--target/riscv/riscv-qmp-cmds.c148
-rw-r--r--target/riscv/tcg/tcg-cpu.c21
-rw-r--r--target/riscv/translate.c3
-rw-r--r--target/riscv/vector_helper.c20
-rw-r--r--tcg/region.c7
-rw-r--r--tests/qtest/bios-tables-test.c2
-rw-r--r--tests/qtest/cpu-plug-test.c2
-rw-r--r--tests/qtest/libqtest.c120
-rw-r--r--tests/qtest/libqtest.h25
-rw-r--r--tests/qtest/migration/bootfile.c5
-rw-r--r--tests/qtest/migration/bootfile.h1
-rw-r--r--tests/qtest/migration/cpr-tests.c138
-rw-r--r--tests/qtest/migration/framework.c107
-rw-r--r--tests/qtest/migration/framework.h7
-rw-r--r--tests/qtest/migration/migration-qmp.c16
-rw-r--r--tests/qtest/migration/migration-qmp.h2
-rw-r--r--tests/qtest/riscv-csr-test.c4
-rw-r--r--tests/tracetool/ftrace.h28
-rw-r--r--tests/tracetool/ftrace.rs40
-rw-r--r--tests/tracetool/log.h16
-rw-r--r--tests/tracetool/log.rs44
-rw-r--r--tests/tracetool/simple.rs40
-rw-r--r--tests/tracetool/syslog.rs40
-rwxr-xr-xtests/tracetool/tracetool-test.py2
-rw-r--r--tests/unit/test-error-report.c17
-rw-r--r--tests/unit/test-vmstate.c83
-rw-r--r--trace/ftrace.c15
-rw-r--r--trace/ftrace.h1
-rw-r--r--trace/meson.build8
-rw-r--r--ui/dbus-listener.c22
-rw-r--r--ui/gtk.c6
-rw-r--r--ui/qemu-pixman.c5
-rw-r--r--ui/spice-core.c6
-rw-r--r--ui/vdagent.c8
-rw-r--r--util/aio-win32.c2
-rw-r--r--util/error.c5
-rw-r--r--util/log.c2
-rw-r--r--util/oslib-posix.c9
-rw-r--r--util/oslib-win32.c29
174 files changed, 3025 insertions, 872 deletions
diff --git a/backends/tpm/tpm_emulator.c b/backends/tpm/tpm_emulator.c
index 4a234ab..dacfca5 100644
--- a/backends/tpm/tpm_emulator.c
+++ b/backends/tpm/tpm_emulator.c
@@ -819,7 +819,8 @@ static int tpm_emulator_get_state_blobs(TPMEmulator *tpm_emu)
static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
uint32_t type,
TPMSizedBuffer *tsb,
- uint32_t flags)
+ uint32_t flags,
+ Error **errp)
{
ssize_t n;
ptm_setstate pss;
@@ -838,17 +839,18 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
/* write the header only */
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_SET_STATEBLOB, &pss,
offsetof(ptm_setstate, u.req.data), 0, 0) < 0) {
- error_report("tpm-emulator: could not set state blob type %d : %s",
- type, strerror(errno));
+ error_setg_errno(errp, errno,
+ "tpm-emulator: could not set state blob type %d",
+ type);
return -1;
}
/* now the body */
n = qemu_chr_fe_write_all(&tpm_emu->ctrl_chr, tsb->buffer, tsb->size);
if (n != tsb->size) {
- error_report("tpm-emulator: Writing the stateblob (type %d) "
- "failed; could not write %u bytes, but only %zd",
- type, tsb->size, n);
+ error_setg(errp, "tpm-emulator: Writing the stateblob (type %d) "
+ "failed; could not write %u bytes, but only %zd",
+ type, tsb->size, n);
return -1;
}
@@ -856,17 +858,17 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr,
(uint8_t *)&pss, sizeof(pss.u.resp));
if (n != sizeof(pss.u.resp)) {
- error_report("tpm-emulator: Reading response from writing stateblob "
- "(type %d) failed; expected %zu bytes, got %zd", type,
- sizeof(pss.u.resp), n);
+ error_setg(errp, "tpm-emulator: Reading response from writing "
+ "stateblob (type %d) failed; expected %zu bytes, "
+ "got %zd", type, sizeof(pss.u.resp), n);
return -1;
}
tpm_result = be32_to_cpu(pss.u.resp.tpm_result);
if (tpm_result != 0) {
- error_report("tpm-emulator: Setting the stateblob (type %d) failed "
- "with a TPM error 0x%x %s", type, tpm_result,
- tpm_emulator_strerror(tpm_result));
+ error_setg(errp, "tpm-emulator: Setting the stateblob (type %d) "
+ "failed with a TPM error 0x%x %s", type, tpm_result,
+ tpm_emulator_strerror(tpm_result));
return -1;
}
@@ -880,7 +882,7 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
*
* Returns a negative errno code in case of error.
*/
-static int tpm_emulator_set_state_blobs(TPMBackend *tb)
+static int tpm_emulator_set_state_blobs(TPMBackend *tb, Error **errp)
{
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
@@ -894,13 +896,13 @@ static int tpm_emulator_set_state_blobs(TPMBackend *tb)
if (tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
&state_blobs->permanent,
- state_blobs->permanent_flags) < 0 ||
+ state_blobs->permanent_flags, errp) < 0 ||
tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
&state_blobs->volatil,
- state_blobs->volatil_flags) < 0 ||
+ state_blobs->volatil_flags, errp) < 0 ||
tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
&state_blobs->savestate,
- state_blobs->savestate_flags) < 0) {
+ state_blobs->savestate_flags, errp) < 0) {
return -EIO;
}
@@ -948,12 +950,12 @@ static void tpm_emulator_vm_state_change(void *opaque, bool running,
*
* Returns negative errno codes in case of error.
*/
-static int tpm_emulator_post_load(void *opaque, int version_id)
+static int tpm_emulator_post_load(void *opaque, int version_id, Error **errp)
{
TPMBackend *tb = opaque;
int ret;
- ret = tpm_emulator_set_state_blobs(tb);
+ ret = tpm_emulator_set_state_blobs(tb, errp);
if (ret < 0) {
return ret;
}
@@ -969,7 +971,7 @@ static const VMStateDescription vmstate_tpm_emulator = {
.name = "tpm-emulator",
.version_id = 0,
.pre_save = tpm_emulator_pre_save,
- .post_load = tpm_emulator_post_load,
+ .post_load_errp = tpm_emulator_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(state_blobs.permanent_flags, TPMEmulator),
VMSTATE_UINT32(state_blobs.permanent.size, TPMEmulator),
diff --git a/crypto/tlssession.c b/crypto/tlssession.c
index 86d407a..ac38c21 100644
--- a/crypto/tlssession.c
+++ b/crypto/tlssession.c
@@ -552,7 +552,6 @@ ssize_t
qcrypto_tls_session_read(QCryptoTLSSession *session,
char *buf,
size_t len,
- bool gracefulTermination,
Error **errp)
{
ssize_t ret;
@@ -570,9 +569,8 @@ qcrypto_tls_session_read(QCryptoTLSSession *session,
if (ret < 0) {
if (ret == GNUTLS_E_AGAIN) {
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
- } else if ((ret == GNUTLS_E_PREMATURE_TERMINATION) &&
- gracefulTermination){
- return 0;
+ } else if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
+ return QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION;
} else {
if (session->rerr) {
error_propagate(errp, session->rerr);
@@ -789,7 +787,6 @@ ssize_t
qcrypto_tls_session_read(QCryptoTLSSession *sess,
char *buf,
size_t len,
- bool gracefulTermination,
Error **errp)
{
error_setg(errp, "TLS requires GNUTLS support");
diff --git a/docs/conf.py b/docs/conf.py
index e09769e..0c9ec74 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# QEMU documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 31 16:40:14 2019.
#
diff --git a/docs/devel/migration/CPR.rst b/docs/devel/migration/CPR.rst
index 0a0fd4f..b617856 100644
--- a/docs/devel/migration/CPR.rst
+++ b/docs/devel/migration/CPR.rst
@@ -5,7 +5,7 @@ CPR is the umbrella name for a set of migration modes in which the
VM is migrated to a new QEMU instance on the same host. It is
intended for use when the goal is to update host software components
that run the VM, such as QEMU or even the host kernel. At this time,
-the cpr-reboot and cpr-transfer modes are available.
+the cpr-reboot, cpr-transfer, and cpr-exec modes are available.
Because QEMU is restarted on the same host, with access to the same
local devices, CPR is allowed in certain cases where normal migration
@@ -324,3 +324,113 @@ descriptors from old to new QEMU. In the future, descriptors for
vhost, and char devices could be transferred,
preserving those devices and their kernel state without interruption,
even if they do not explicitly support live migration.
+
+cpr-exec mode
+-------------
+
+In this mode, QEMU stops the VM, writes VM state to the migration
+URI, and directly exec's a new version of QEMU on the same host,
+replacing the original process while retaining its PID. Guest RAM is
+preserved in place, albeit with new virtual addresses. The user
+completes the migration by specifying the ``-incoming`` option, and
+by issuing the ``migrate-incoming`` command if necessary; see details
+below.
+
+This mode supports VFIO/IOMMUFD devices by preserving device
+descriptors and hence kernel state across the exec, even for devices
+that do not support live migration.
+
+Because the old and new QEMU instances are not active concurrently,
+the URI cannot be a type that streams data from one instance to the
+other.
+
+This mode does not require a channel of type ``cpr``. The information
+that is passed over that channel for cpr-transfer mode is instead
+serialized to a memfd, the number of the fd is saved in the
+QEMU_CPR_EXEC_STATE environment variable during the exec of new QEMU.
+and new QEMU mmaps the memfd.
+
+Usage
+^^^^^
+
+Arguments for the new QEMU process are taken from the
+@cpr-exec-command parameter. The first argument should be the
+path of a new QEMU binary, or a prefix command that exec's the
+new QEMU binary, and the arguments should include the ''-incoming''
+option.
+
+Memory backend objects must have the ``share=on`` attribute.
+The VM must be started with the ``-machine aux-ram-share=on`` option.
+
+Outgoing:
+ * Set the migration mode parameter to ``cpr-exec``.
+ * Set the ``cpr-exec-command`` parameter.
+ * Issue the ``migrate`` command. It is recommended that the URI be
+ a ``file`` type, but one can use other types such as ``exec``,
+ provided the command captures all the data from the outgoing side,
+ and provides all the data to the incoming side.
+
+Incoming:
+ * You do not need to explicitly start new QEMU. It is started as
+ a side effect of the migrate command above.
+ * If the VM was running when the outgoing ``migrate`` command was
+ issued, then QEMU automatically resumes VM execution.
+
+Example 1: incoming URI
+^^^^^^^^^^^^^^^^^^^^^^^
+
+In these examples, we simply restart the same version of QEMU, but in
+a real scenario one would set a new QEMU binary path in
+cpr-exec-command.
+
+::
+
+ # qemu-kvm -monitor stdio
+ -object memory-backend-memfd,id=ram0,size=4G
+ -machine memory-backend=ram0
+ -machine aux-ram-share=on
+ ...
+
+ QEMU 10.2.50 monitor - type 'help' for more information
+ (qemu) info status
+ VM status: running
+ (qemu) migrate_set_parameter mode cpr-exec
+ (qemu) migrate_set_parameter cpr-exec-command qemu-kvm ... -incoming file:vm.state
+ (qemu) migrate -d file:vm.state
+ (qemu) QEMU 10.2.50 monitor - type 'help' for more information
+ (qemu) info status
+ VM status: running
+
+Example 2: incoming defer
+^^^^^^^^^^^^^^^^^^^^^^^^^
+::
+
+ # qemu-kvm -monitor stdio
+ -object memory-backend-memfd,id=ram0,size=4G
+ -machine memory-backend=ram0
+ -machine aux-ram-share=on
+ ...
+
+ QEMU 10.2.50 monitor - type 'help' for more information
+ (qemu) info status
+ VM status: running
+ (qemu) migrate_set_parameter mode cpr-exec
+ (qemu) migrate_set_parameter cpr-exec-command qemu-kvm ... -incoming defer
+ (qemu) migrate -d file:vm.state
+ (qemu) QEMU 10.2.50 monitor - type 'help' for more information
+ (qemu) info status
+ status: paused (inmigrate)
+ (qemu) migrate_incoming file:vm.state
+ (qemu) info status
+ VM status: running
+
+Caveats
+^^^^^^^
+
+cpr-exec mode may not be used with postcopy, background-snapshot,
+or COLO.
+
+cpr-exec mode requires permission to use the exec system call, which
+is denied by certain sandbox options, such as spawn.
+
+The guest pause time increases for large guest RAM backed by small pages.
diff --git a/docs/devel/migration/main.rst b/docs/devel/migration/main.rst
index 6493c1d..1afe7b9 100644
--- a/docs/devel/migration/main.rst
+++ b/docs/devel/migration/main.rst
@@ -444,6 +444,25 @@ The functions to do that are inside a vmstate definition, and are called:
This function is called after we save the state of one device
(even upon failure, unless the call to pre_save returned an error).
+Following are the errp variants of these functions.
+
+- ``int (*pre_load_errp)(void *opaque, Error **errp);``
+
+ This function is called before we load the state of one device.
+
+- ``int (*post_load_errp)(void *opaque, int version_id, Error **errp);``
+
+ This function is called after we load the state of one device.
+
+- ``int (*pre_save_errp)(void *opaque, Error **errp);``
+
+ This function is called before we save the state of one device.
+
+New impls should preferentally use 'errp' variants of these
+methods and existing impls incrementally converted.
+The variants without 'errp' are intended to be removed
+once all usage is converted.
+
Example: You can look at hpet.c, that uses the first three functions
to massage the state that is transferred.
diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json
index 6bbe2cc..ccbfaf8 100644
--- a/docs/interop/firmware.json
+++ b/docs/interop/firmware.json
@@ -85,12 +85,14 @@
#
# @loongarch64: 64-bit LoongArch. (since: 7.1)
#
+# @riscv64: 64-bit RISC-V.
+#
# @x86_64: 64-bit x86.
#
# Since: 3.0
##
{ 'enum' : 'FirmwareArchitecture',
- 'data' : [ 'aarch64', 'arm', 'i386', 'loongarch64', 'x86_64' ] }
+ 'data' : [ 'aarch64', 'arm', 'i386', 'loongarch64', 'riscv64', 'x86_64' ] }
##
# @FirmwareTarget:
diff --git a/docs/specs/riscv-iommu.rst b/docs/specs/riscv-iommu.rst
index 991d376..571a6a6 100644
--- a/docs/specs/riscv-iommu.rst
+++ b/docs/specs/riscv-iommu.rst
@@ -30,15 +30,15 @@ This will add a RISC-V IOMMU PCI device in the board following any additional
PCI parameters (like PCI bus address). The behavior of the RISC-V IOMMU is
defined by the spec but its operation is OS dependent.
-As of this writing the existing Linux kernel support `linux-v8`_, not yet merged,
-does not have support for features like VFIO passthrough. The IOMMU emulation
-was tested using a public Ventana Micro Systems kernel repository in
-`ventana-linux`_. This kernel is based on `linux-v8`_ with additional patches that
-enable features like KVM VFIO passthrough with irqbypass. Until the kernel support
-is feature complete feel free to use the kernel available in the Ventana Micro Systems
-mirror.
-
-The current Linux kernel support will use the IOMMU device to create IOMMU groups
+Linux kernel iommu support was merged in v6.13. QEMU IOMMU emulation can be
+used with mainline kernels for simple IOMMU PCIe support.
+
+As of v6.17, it does not have support for features like VFIO passthrough.
+There is a `VFIO`_ RFC series that is not yet merged. The public Ventana Micro
+Systems kernel repository in `ventana-linux`_ can be used for testing the VFIO
+functions.
+
+The v6.13+ Linux kernel support uses the IOMMU device to create IOMMU groups
with any eligible cards available in the system, regardless of factors such as the
order in which the devices are added in the command line.
@@ -49,7 +49,7 @@ IOMMU kernel driver behaves:
$ qemu-system-riscv64 \
-M virt,aia=aplic-imsic,aia-guests=5 \
- -device riscv-iommu-pci,addr=1.0,vendor-id=0x1efd,device-id=0xedf1 \
+ -device riscv-iommu-pci,addr=1.0 \
-device e1000e,netdev=net1 -netdev user,id=net1,net=192.168.0.0/24 \
-device e1000e,netdev=net2 -netdev user,id=net2,net=192.168.200.0/24 \
(...)
@@ -58,21 +58,11 @@ IOMMU kernel driver behaves:
-M virt,aia=aplic-imsic,aia-guests=5 \
-device e1000e,netdev=net1 -netdev user,id=net1,net=192.168.0.0/24 \
-device e1000e,netdev=net2 -netdev user,id=net2,net=192.168.200.0/24 \
- -device riscv-iommu-pci,addr=1.0,vendor-id=0x1efd,device-id=0xedf1 \
+ -device riscv-iommu-pci,addr=3.0 \
(...)
Both will create iommu groups for the two e1000e cards.
-Another thing to notice on `linux-v8`_ and `ventana-linux`_ is that the kernel driver
-considers an IOMMU identified as a Rivos device, i.e. it uses Rivos vendor ID. To
-use the riscv-iommu-pci device with the existing kernel support we need to emulate
-a Rivos PCI IOMMU by setting 'vendor-id' and 'device-id':
-
-.. code-block:: bash
-
- $ qemu-system-riscv64 -M virt \
- -device riscv-iommu-pci,vendor-id=0x1efd,device-id=0xedf1 (...)
-
Several options are available to control the capabilities of the device, namely:
- "bus": the bus that the IOMMU device uses
@@ -84,6 +74,7 @@ Several options are available to control the capabilities of the device, namely:
- "g-stage": enable g-stage support
- "hpm-counters": number of hardware performance counters available. Maximum value is 31.
Default value is 31. Use 0 (zero) to disable HPM support
+- "vendor-id"/"device-id": pci device ID. Defaults to 1b36:0014 (Redhat)
riscv-iommu-sys device
----------------------
@@ -111,6 +102,6 @@ riscv-iommu options:
.. _iommu1.0.0: https://github.com/riscv-non-isa/riscv-iommu/releases/download/v1.0.0/riscv-iommu.pdf
-.. _linux-v8: https://lore.kernel.org/linux-riscv/cover.1718388908.git.tjeznach@rivosinc.com/
+.. _VFIO: https://lore.kernel.org/linux-riscv/20241114161845.502027-17-ajones@ventanamicro.com/
.. _ventana-linux: https://github.com/ventanamicro/linux/tree/dev-upstream
diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css
index b225bf7..f312e9b 100644
--- a/docs/sphinx-static/theme_overrides.css
+++ b/docs/sphinx-static/theme_overrides.css
@@ -1,5 +1,4 @@
-/* -*- coding: utf-8; mode: css -*-
- *
+/*
* Sphinx HTML theme customization: read the doc
* Based on Linux Documentation/sphinx-static/theme_overrides.css
*/
diff --git a/hmp-commands.hx b/hmp-commands.hx
index d0e4f35..3cace8f 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1009,7 +1009,7 @@ ERST
{
.name = "migrate_set_parameter",
- .args_type = "parameter:s,value:s",
+ .args_type = "parameter:s,value:S",
.params = "parameter value",
.help = "Set the parameter for migration",
.cmd = hmp_migrate_set_parameter,
diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c
index 9bc697a..e7357d5 100644
--- a/hw/char/sifive_uart.c
+++ b/hw/char/sifive_uart.c
@@ -28,23 +28,18 @@
#define TX_INTERRUPT_TRIGGER_DELAY_NS 100
-/*
- * Not yet implemented:
- *
- * Transmit FIFO using "qemu/fifo8.h"
- */
-
/* Returns the state of the IP (interrupt pending) register */
-static uint64_t sifive_uart_ip(SiFiveUARTState *s)
+static uint32_t sifive_uart_ip(SiFiveUARTState *s)
{
- uint64_t ret = 0;
+ uint32_t ret = 0;
- uint64_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
- uint64_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
+ uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
+ uint32_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
- if (txcnt != 0) {
+ if (fifo8_num_used(&s->tx_fifo) < txcnt) {
ret |= SIFIVE_UART_IP_TXWM;
}
+
if (s->rx_fifo_len > rxcnt) {
ret |= SIFIVE_UART_IP_RXWM;
}
@@ -55,15 +50,14 @@ static uint64_t sifive_uart_ip(SiFiveUARTState *s)
static void sifive_uart_update_irq(SiFiveUARTState *s)
{
int cond = 0;
- if ((s->ie & SIFIVE_UART_IE_TXWM) ||
- ((s->ie & SIFIVE_UART_IE_RXWM) && s->rx_fifo_len)) {
+ uint32_t ip = sifive_uart_ip(s);
+
+ if (((ip & SIFIVE_UART_IP_TXWM) && (s->ie & SIFIVE_UART_IE_TXWM)) ||
+ ((ip & SIFIVE_UART_IP_RXWM) && (s->ie & SIFIVE_UART_IE_RXWM))) {
cond = 1;
}
- if (cond) {
- qemu_irq_raise(s->irq);
- } else {
- qemu_irq_lower(s->irq);
- }
+
+ qemu_set_irq(s->irq, cond);
}
static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond,
@@ -119,10 +113,12 @@ static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
if (size > fifo8_num_free(&s->tx_fifo)) {
size = fifo8_num_free(&s->tx_fifo);
- qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow");
+ qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow.\n");
}
- fifo8_push_all(&s->tx_fifo, buf, size);
+ if (size > 0) {
+ fifo8_push_all(&s->tx_fifo, buf, size);
+ }
if (fifo8_is_full(&s->tx_fifo)) {
s->txfifo |= SIFIVE_UART_TXFIFO_FULL;
diff --git a/hw/char/trace-events b/hw/char/trace-events
index 05a3303..9e74be2 100644
--- a/hw/char/trace-events
+++ b/hw/char/trace-events
@@ -58,15 +58,15 @@ imx_serial_write(const char *chrname, uint64_t addr, uint64_t value) "%s:[0x%03"
imx_serial_put_data(const char *chrname, uint32_t value) "%s: 0x%" PRIx32
# pl011.c
-pl011_irq_state(int level) "irq state %d"
-pl011_read(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s"
-pl011_read_fifo(unsigned rx_fifo_used, size_t rx_fifo_depth) "RX FIFO read, used %u/%zu"
-pl011_write(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s"
-pl011_can_receive(uint32_t lcr, unsigned rx_fifo_used, size_t rx_fifo_depth, unsigned rx_fifo_available) "LCR 0x%02x, RX FIFO used %u/%zu, can_receive %u chars"
-pl011_fifo_rx_put(uint32_t c, unsigned read_count, size_t rx_fifo_depth) "RX FIFO push char [0x%02x] %d/%zu depth used"
+pl011_irq_state(bool level) "irq state %d"
+pl011_read(uint64_t addr, uint32_t value, const char *regname) "addr 0x%03" PRIx64 " value 0x%08x reg %s"
+pl011_read_fifo(unsigned rx_fifo_used, unsigned rx_fifo_depth) "RX FIFO read, used %u/%u"
+pl011_write(uint64_t addr, uint32_t value, const char *regname) "addr 0x%03" PRIx64 " value 0x%08x reg %s"
+pl011_can_receive(uint32_t lcr, unsigned rx_fifo_used, unsigned rx_fifo_depth, unsigned rx_fifo_available) "LCR 0x%02x, RX FIFO used %u/%u, can_receive %u chars"
+pl011_fifo_rx_put(uint32_t c, unsigned read_count, unsigned rx_fifo_depth) "RX FIFO push char [0x%02x] %d/%u depth used"
pl011_fifo_rx_full(void) "RX FIFO now full, RXFF set"
pl011_baudrate_change(unsigned int baudrate, uint64_t clock, uint32_t ibrd, uint32_t fbrd) "new baudrate %u (clk: %" PRIu64 "hz, ibrd: %" PRIu32 ", fbrd: %" PRIu32 ")"
-pl011_receive(int size) "recv %d chars"
+pl011_receive(size_t size) "recv %zd chars"
# cmsdk-apb-uart.c
cmsdk_apb_uart_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c
index 41a3399..8c306c8 100644
--- a/hw/core/cpu-common.c
+++ b/hw/core/cpu-common.c
@@ -294,6 +294,7 @@ void cpu_exec_unrealizefn(CPUState *cpu)
* accel_cpu_common_unrealize, which may free fields using call_rcu.
*/
accel_cpu_common_unrealize(cpu);
+ cpu_destroy_address_spaces(cpu);
}
static void cpu_common_initfn(Object *obj)
diff --git a/hw/cxl/cxl-host.c b/hw/cxl/cxl-host.c
index 5c2ce25..0d891c6 100644
--- a/hw/cxl/cxl-host.c
+++ b/hw/cxl/cxl-host.c
@@ -72,6 +72,7 @@ static void cxl_fixed_memory_window_config(CXLFixedMemoryWindowOptions *object,
static int cxl_fmws_link(Object *obj, void *opaque)
{
+ Error **errp = opaque;
struct CXLFixedWindow *fw;
int i;
@@ -87,9 +88,9 @@ static int cxl_fmws_link(Object *obj, void *opaque)
o = object_resolve_path_type(fw->targets[i], TYPE_PXB_CXL_DEV,
&ambig);
if (!o) {
- error_setg(&error_fatal, "Could not resolve CXLFM target %s",
+ error_setg(errp, "Could not resolve CXLFM target %s",
fw->targets[i]);
- return 1;
+ return -1;
}
fw->target_hbs[i] = PXB_CXL_DEV(o);
}
@@ -99,7 +100,7 @@ static int cxl_fmws_link(Object *obj, void *opaque)
void cxl_fmws_link_targets(Error **errp)
{
/* Order doesn't matter for this, so no need to build list */
- object_child_foreach_recursive(object_get_root(), cxl_fmws_link, NULL);
+ object_child_foreach_recursive(object_get_root(), cxl_fmws_link, errp);
}
static bool cxl_hdm_find_target(uint32_t *cache_mem, hwaddr addr,
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index 0a1a625..3a55512 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -242,6 +242,7 @@ static uint32_t calc_image_hostmem(pixman_format_code_t pformat,
static void virtio_gpu_resource_create_2d(VirtIOGPU *g,
struct virtio_gpu_ctrl_command *cmd)
{
+ Error *err = NULL;
pixman_format_code_t pformat;
struct virtio_gpu_simple_resource *res;
struct virtio_gpu_resource_create_2d c2d;
@@ -293,7 +294,8 @@ static void virtio_gpu_resource_create_2d(VirtIOGPU *g,
c2d.width,
c2d.height,
c2d.height ? res->hostmem / c2d.height : 0,
- &error_warn)) {
+ &err)) {
+ warn_report_err(err);
goto end;
}
}
@@ -1246,7 +1248,8 @@ static int virtio_gpu_save(QEMUFile *f, void *opaque, size_t size,
}
qemu_put_be32(f, 0); /* end of list */
- return vmstate_save_state(f, &vmstate_virtio_gpu_scanouts, g, NULL);
+ return vmstate_save_state(f, &vmstate_virtio_gpu_scanouts, g, NULL,
+ &error_fatal);
}
static bool virtio_gpu_load_restore_mapping(VirtIOGPU *g,
@@ -1282,6 +1285,7 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
const VMStateField *field)
{
VirtIOGPU *g = opaque;
+ Error *err = NULL;
struct virtio_gpu_simple_resource *res;
uint32_t resource_id, pformat;
int i;
@@ -1317,7 +1321,8 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
res->width,
res->height,
res->height ? res->hostmem / res->height : 0,
- &error_warn)) {
+ &err)) {
+ warn_report_err(err);
g_free(res);
return -EINVAL;
}
@@ -1343,7 +1348,7 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
}
/* load & apply scanout state */
- vmstate_load_state(f, &vmstate_virtio_gpu_scanouts, g, 1);
+ vmstate_load_state(f, &vmstate_virtio_gpu_scanouts, g, 1, &error_fatal);
return 0;
}
diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c
index 37a7a70..394fe02 100644
--- a/hw/intc/apic_common.c
+++ b/hw/intc/apic_common.c
@@ -379,6 +379,7 @@ static const VMStateDescription vmstate_apic_common = {
.pre_load = apic_pre_load,
.pre_save = apic_dispatch_pre_save,
.post_load = apic_dispatch_post_load,
+ .priority = MIG_PRI_APIC,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(apicbase, APICCommonState),
VMSTATE_UINT8(id, APICCommonState),
diff --git a/hw/intc/riscv_aclint.c b/hw/intc/riscv_aclint.c
index 4623cfa0..9f4c36e 100644
--- a/hw/intc/riscv_aclint.c
+++ b/hw/intc/riscv_aclint.c
@@ -323,12 +323,15 @@ static void riscv_aclint_mtimer_reset_enter(Object *obj, ResetType type)
static const VMStateDescription vmstate_riscv_mtimer = {
.name = "riscv_mtimer",
- .version_id = 1,
- .minimum_version_id = 1,
+ .version_id = 3,
+ .minimum_version_id = 3,
.fields = (const VMStateField[]) {
+ VMSTATE_UINT64(time_delta, RISCVAclintMTimerState),
VMSTATE_VARRAY_UINT32(timecmp, RISCVAclintMTimerState,
num_harts, 0,
vmstate_info_uint64, uint64_t),
+ VMSTATE_TIMER_PTR_VARRAY(timers, RISCVAclintMTimerState,
+ num_harts),
VMSTATE_END_OF_LIST()
}
};
diff --git a/hw/misc/ivshmem-flat.c b/hw/misc/ivshmem-flat.c
index e83e6c6..27ee8c9 100644
--- a/hw/misc/ivshmem-flat.c
+++ b/hw/misc/ivshmem-flat.c
@@ -138,6 +138,8 @@ static void ivshmem_flat_remove_peer(IvshmemFTState *s, uint16_t peer_id)
static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer,
int vector_fd)
{
+ Error *err = NULL;
+
if (peer->vector_counter >= IVSHMEM_MAX_VECTOR_NUM) {
trace_ivshmem_flat_add_vector_failure(peer->vector_counter,
vector_fd, peer->id);
@@ -154,8 +156,10 @@ static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer,
* peer.
*/
peer->vector[peer->vector_counter].id = peer->vector_counter;
- /* WARNING: qemu_socket_set_nonblock() return code ignored */
- qemu_set_blocking(vector_fd, false, &error_warn);
+ if (!qemu_set_blocking(vector_fd, false, &err)) {
+ /* FIXME handle the error */
+ warn_report_err(err);
+ }
event_notifier_init_fd(&peer->vector[peer->vector_counter].event_notifier,
vector_fd);
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 6b5b5da..7848e26 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -1289,6 +1289,8 @@ exit:
static bool virtio_net_load_ebpf(VirtIONet *n, Error **errp)
{
+ Error *err = NULL;
+
if (!virtio_net_attach_ebpf_to_backend(n->nic, -1)) {
return true;
}
@@ -1306,7 +1308,11 @@ static bool virtio_net_load_ebpf(VirtIONet *n, Error **errp)
return virtio_net_load_ebpf_fds(n, errp);
}
- ebpf_rss_load(&n->ebpf_rss, &error_warn);
+ ebpf_rss_load(&n->ebpf_rss, &err);
+ /* Beware, ebpf_rss_load() can return false with @err unset */
+ if (err) {
+ warn_report_err(err);
+ }
return true;
}
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index c3df9d6..5e2c3c6 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -926,7 +926,7 @@ void pci_device_save(PCIDevice *s, QEMUFile *f)
* This makes us compatible with old devices
* which never set or clear this bit. */
s->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
- vmstate_save_state(f, &vmstate_pci_device, s, NULL);
+ vmstate_save_state(f, &vmstate_pci_device, s, NULL, &error_fatal);
/* Restore the interrupt status bit. */
pci_update_irq_status(s);
}
@@ -934,7 +934,8 @@ void pci_device_save(PCIDevice *s, QEMUFile *f)
int pci_device_load(PCIDevice *s, QEMUFile *f)
{
int ret;
- ret = vmstate_load_state(f, &vmstate_pci_device, s, s->version_id);
+ ret = vmstate_load_state(f, &vmstate_pci_device, s, s->version_id,
+ &error_fatal);
/* Restore the interrupt status bit. */
pci_update_irq_status(s);
return ret;
diff --git a/hw/remote/vfio-user-obj.c b/hw/remote/vfio-user-obj.c
index ea6165e..216b487 100644
--- a/hw/remote/vfio-user-obj.c
+++ b/hw/remote/vfio-user-obj.c
@@ -75,12 +75,17 @@ OBJECT_DECLARE_TYPE(VfuObject, VfuObjectClass, VFU_OBJECT)
*/
#define VFU_OBJECT_ERROR(o, fmt, ...) \
{ \
+ error_report((fmt), ## __VA_ARGS__); \
if (vfu_object_auto_shutdown()) { \
- error_setg(&error_abort, (fmt), ## __VA_ARGS__); \
- } else { \
- error_report((fmt), ## __VA_ARGS__); \
+ /* \
+ * FIXME This looks inappropriate. The error is serious \
+ * enough programming error to warrant aborting the process \
+ * when auto-shutdown is enabled, yet harmless enough to \
+ * permit carrying on when it's disabled. Makes no sense. \
+ */ \
+ abort(); \
} \
- } \
+ }
struct VfuObjectClass {
ObjectClass parent_class;
diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
index 96a7fbd..b33c7fe 100644
--- a/hw/riscv/riscv-iommu.c
+++ b/hw/riscv/riscv-iommu.c
@@ -558,6 +558,7 @@ static MemTxResult riscv_iommu_msi_write(RISCVIOMMUState *s,
MemTxResult res;
dma_addr_t addr;
uint64_t intn;
+ size_t offset;
uint32_t n190;
uint64_t pte[2];
int fault_type = RISCV_IOMMU_FQ_TTYPE_UADDR_WR;
@@ -565,16 +566,18 @@ static MemTxResult riscv_iommu_msi_write(RISCVIOMMUState *s,
/* Interrupt File Number */
intn = riscv_iommu_pext_u64(PPN_DOWN(gpa), ctx->msi_addr_mask);
- if (intn >= 256) {
+ offset = intn * sizeof(pte);
+
+ /* fetch MSI PTE */
+ addr = PPN_PHYS(get_field(ctx->msiptp, RISCV_IOMMU_DC_MSIPTP_PPN));
+ if (addr & offset) {
/* Interrupt file number out of range */
res = MEMTX_ACCESS_ERROR;
cause = RISCV_IOMMU_FQ_CAUSE_MSI_LOAD_FAULT;
goto err;
}
- /* fetch MSI PTE */
- addr = PPN_PHYS(get_field(ctx->msiptp, RISCV_IOMMU_DC_MSIPTP_PPN));
- addr = addr | (intn * sizeof(pte));
+ addr |= offset;
res = dma_memory_read(s->target_as, addr, &pte, sizeof(pte),
MEMTXATTRS_UNSPECIFIED);
if (res != MEMTX_OK) {
@@ -866,6 +869,145 @@ static bool riscv_iommu_validate_process_ctx(RISCVIOMMUState *s,
return true;
}
+/**
+ * pdt_memory_read: PDT wrapper of dma_memory_read.
+ *
+ * @s: IOMMU Device State
+ * @ctx: Device Translation Context with devid and pasid set
+ * @addr: address within that address space
+ * @buf: buffer with the data transferred
+ * @len: length of the data transferred
+ * @attrs: memory transaction attributes
+ */
+static MemTxResult pdt_memory_read(RISCVIOMMUState *s,
+ RISCVIOMMUContext *ctx,
+ dma_addr_t addr,
+ void *buf, dma_addr_t len,
+ MemTxAttrs attrs)
+{
+ uint64_t gatp_mode, pte;
+ struct {
+ unsigned char step;
+ unsigned char levels;
+ unsigned char ptidxbits;
+ unsigned char ptesize;
+ } sc;
+ MemTxResult ret;
+ dma_addr_t base = addr;
+
+ /* G stages translation mode */
+ gatp_mode = get_field(ctx->gatp, RISCV_IOMMU_ATP_MODE_FIELD);
+ if (gatp_mode == RISCV_IOMMU_DC_IOHGATP_MODE_BARE) {
+ goto out;
+ }
+
+ /* G stages translation tables root pointer */
+ base = PPN_PHYS(get_field(ctx->gatp, RISCV_IOMMU_ATP_PPN_FIELD));
+
+ /* Start at step 0 */
+ sc.step = 0;
+
+ if (s->fctl & RISCV_IOMMU_FCTL_GXL) {
+ /* 32bit mode for GXL == 1 */
+ switch (gatp_mode) {
+ case RISCV_IOMMU_DC_IOHGATP_MODE_SV32X4:
+ if (!(s->cap & RISCV_IOMMU_CAP_SV32X4)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ sc.levels = 2;
+ sc.ptidxbits = 10;
+ sc.ptesize = 4;
+ break;
+ default:
+ return MEMTX_ACCESS_ERROR;
+ }
+ } else {
+ /* 64bit mode for GXL == 0 */
+ switch (gatp_mode) {
+ case RISCV_IOMMU_DC_IOHGATP_MODE_SV39X4:
+ if (!(s->cap & RISCV_IOMMU_CAP_SV39X4)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ sc.levels = 3;
+ sc.ptidxbits = 9;
+ sc.ptesize = 8;
+ break;
+ case RISCV_IOMMU_DC_IOHGATP_MODE_SV48X4:
+ if (!(s->cap & RISCV_IOMMU_CAP_SV48X4)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ sc.levels = 4;
+ sc.ptidxbits = 9;
+ sc.ptesize = 8;
+ break;
+ case RISCV_IOMMU_DC_IOHGATP_MODE_SV57X4:
+ if (!(s->cap & RISCV_IOMMU_CAP_SV57X4)) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ sc.levels = 5;
+ sc.ptidxbits = 9;
+ sc.ptesize = 8;
+ break;
+ default:
+ return MEMTX_ACCESS_ERROR;
+ }
+ }
+
+ do {
+ const unsigned va_bits = (sc.step ? 0 : 2) + sc.ptidxbits;
+ const unsigned va_skip = TARGET_PAGE_BITS + sc.ptidxbits *
+ (sc.levels - 1 - sc.step);
+ const unsigned idx = (addr >> va_skip) & ((1 << va_bits) - 1);
+ const dma_addr_t pte_addr = base + idx * sc.ptesize;
+
+ /* Address range check before first level lookup */
+ if (!sc.step) {
+ const uint64_t va_mask = (1ULL << (va_skip + va_bits)) - 1;
+ if ((addr & va_mask) != addr) {
+ return MEMTX_ACCESS_ERROR;
+ }
+ }
+
+ /* Read page table entry */
+ if (sc.ptesize == 4) {
+ uint32_t pte32 = 0;
+ ret = ldl_le_dma(s->target_as, pte_addr, &pte32, attrs);
+ pte = pte32;
+ } else {
+ ret = ldq_le_dma(s->target_as, pte_addr, &pte, attrs);
+ }
+ if (ret != MEMTX_OK) {
+ return ret;
+ }
+
+ sc.step++;
+ hwaddr ppn = pte >> PTE_PPN_SHIFT;
+
+ if (!(pte & PTE_V)) {
+ return MEMTX_ACCESS_ERROR; /* Invalid PTE */
+ } else if (!(pte & (PTE_R | PTE_W | PTE_X))) {
+ base = PPN_PHYS(ppn); /* Inner PTE, continue walking */
+ } else if ((pte & (PTE_R | PTE_W | PTE_X)) == PTE_W) {
+ return MEMTX_ACCESS_ERROR; /* Reserved leaf PTE flags: PTE_W */
+ } else if ((pte & (PTE_R | PTE_W | PTE_X)) == (PTE_W | PTE_X)) {
+ return MEMTX_ACCESS_ERROR; /* Reserved leaf PTE flags: PTE_W + PTE_X */
+ } else if (ppn & ((1ULL << (va_skip - TARGET_PAGE_BITS)) - 1)) {
+ return MEMTX_ACCESS_ERROR; /* Misaligned PPN */
+ } else {
+ /* Leaf PTE, translation completed. */
+ base = PPN_PHYS(ppn) | (addr & ((1ULL << va_skip) - 1));
+ break;
+ }
+
+ if (sc.step == sc.levels) {
+ return MEMTX_ACCESS_ERROR; /* Can't find leaf PTE */
+ }
+ } while (1);
+
+out:
+ return dma_memory_read(s->target_as, base, buf, len, attrs);
+}
+
/*
* RISC-V IOMMU Device Context Loopkup - Device Directory Tree Walk
*
@@ -1038,7 +1180,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx)
*/
const int split = depth * 9 + 8;
addr |= ((ctx->process_id >> split) << 3) & ~TARGET_PAGE_MASK;
- if (dma_memory_read(s->target_as, addr, &de, sizeof(de),
+ if (pdt_memory_read(s, ctx, addr, &de, sizeof(de),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return RISCV_IOMMU_FQ_CAUSE_PDT_LOAD_FAULT;
}
@@ -1053,7 +1195,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx)
/* Leaf entry in PDT */
addr |= (ctx->process_id << 4) & ~TARGET_PAGE_MASK;
- if (dma_memory_read(s->target_as, addr, &dc.ta, sizeof(uint64_t) * 2,
+ if (pdt_memory_read(s, ctx, addr, &dc.ta, sizeof(uint64_t) * 2,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return RISCV_IOMMU_FQ_CAUSE_PDT_LOAD_FAULT;
}
diff --git a/hw/s390x/virtio-ccw.c b/hw/s390x/virtio-ccw.c
index d2f85b3..4cb1ced 100644
--- a/hw/s390x/virtio-ccw.c
+++ b/hw/s390x/virtio-ccw.c
@@ -1130,13 +1130,13 @@ static int virtio_ccw_load_queue(DeviceState *d, int n, QEMUFile *f)
static void virtio_ccw_save_config(DeviceState *d, QEMUFile *f)
{
VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
- vmstate_save_state(f, &vmstate_virtio_ccw_dev, dev, NULL);
+ vmstate_save_state(f, &vmstate_virtio_ccw_dev, dev, NULL, &error_fatal);
}
static int virtio_ccw_load_config(DeviceState *d, QEMUFile *f)
{
VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
- return vmstate_load_state(f, &vmstate_virtio_ccw_dev, dev, 1);
+ return vmstate_load_state(f, &vmstate_virtio_ccw_dev, dev, 1, &error_fatal);
}
static void virtio_ccw_pre_plugged(DeviceState *d, Error **errp)
diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c
index 20f70fb..f0a7dd2 100644
--- a/hw/scsi/spapr_vscsi.c
+++ b/hw/scsi/spapr_vscsi.c
@@ -630,7 +630,7 @@ static void vscsi_save_request(QEMUFile *f, SCSIRequest *sreq)
vscsi_req *req = sreq->hba_private;
assert(req->active);
- vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL);
+ vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL, &error_fatal);
trace_spapr_vscsi_save_request(req->qtag, req->cur_desc_num,
req->cur_desc_offset);
@@ -642,15 +642,17 @@ static void *vscsi_load_request(QEMUFile *f, SCSIRequest *sreq)
VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(bus->qbus.parent);
vscsi_req *req;
int rc;
+ Error *local_err = NULL;
assert(sreq->tag < VSCSI_REQ_LIMIT);
req = &s->reqs[sreq->tag];
assert(!req->active);
memset(req, 0, sizeof(*req));
- rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1);
+ rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1, &local_err);
if (rc) {
fprintf(stderr, "VSCSI: failed loading request tag#%u\n", sreq->tag);
+ error_report_err(local_err);
return NULL;
}
assert(req->active);
diff --git a/hw/vfio/container-legacy.c b/hw/vfio/container-legacy.c
index 34352dd..629ff23 100644
--- a/hw/vfio/container-legacy.c
+++ b/hw/vfio/container-legacy.c
@@ -972,7 +972,8 @@ static bool vfio_legacy_attach_device(const char *name, VFIODevice *vbasedev,
error_setg(&vbasedev->cpr.mdev_blocker,
"CPR does not support vfio mdev %s", vbasedev->name);
if (migrate_add_blocker_modes(&vbasedev->cpr.mdev_blocker, errp,
- MIG_MODE_CPR_TRANSFER, -1) < 0) {
+ MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC,
+ -1) < 0) {
goto hiod_unref_exit;
}
}
diff --git a/hw/vfio/cpr-iommufd.c b/hw/vfio/cpr-iommufd.c
index 1d70c87..8a4d65d 100644
--- a/hw/vfio/cpr-iommufd.c
+++ b/hw/vfio/cpr-iommufd.c
@@ -159,7 +159,8 @@ bool vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend *be, Error **errp)
if (!vfio_cpr_supported(be, cpr_blocker)) {
return migrate_add_blocker_modes(cpr_blocker, errp,
- MIG_MODE_CPR_TRANSFER, -1) == 0;
+ MIG_MODE_CPR_TRANSFER,
+ MIG_MODE_CPR_EXEC, -1) == 0;
}
vmstate_register(NULL, -1, &iommufd_cpr_vmstate, be);
diff --git a/hw/vfio/cpr-legacy.c b/hw/vfio/cpr-legacy.c
index 3a1d126..80af746 100644
--- a/hw/vfio/cpr-legacy.c
+++ b/hw/vfio/cpr-legacy.c
@@ -179,16 +179,17 @@ bool vfio_legacy_cpr_register_container(VFIOLegacyContainer *container,
if (!vfio_cpr_supported(container, cpr_blocker)) {
return migrate_add_blocker_modes(cpr_blocker, errp,
- MIG_MODE_CPR_TRANSFER, -1) == 0;
+ MIG_MODE_CPR_TRANSFER,
+ MIG_MODE_CPR_EXEC, -1) == 0;
}
vfio_cpr_add_kvm_notifier();
vmstate_register(NULL, -1, &vfio_container_vmstate, container);
- migration_add_notifier_mode(&container->cpr.transfer_notifier,
- vfio_cpr_fail_notifier,
- MIG_MODE_CPR_TRANSFER);
+ migration_add_notifier_modes(&container->cpr.transfer_notifier,
+ vfio_cpr_fail_notifier,
+ MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC, -1);
return true;
}
diff --git a/hw/vfio/cpr.c b/hw/vfio/cpr.c
index 2c71fc1..db462aa 100644
--- a/hw/vfio/cpr.c
+++ b/hw/vfio/cpr.c
@@ -195,9 +195,10 @@ static int vfio_cpr_kvm_close_notifier(NotifierWithReturn *notifier,
void vfio_cpr_add_kvm_notifier(void)
{
if (!kvm_close_notifier.notify) {
- migration_add_notifier_mode(&kvm_close_notifier,
- vfio_cpr_kvm_close_notifier,
- MIG_MODE_CPR_TRANSFER);
+ migration_add_notifier_modes(&kvm_close_notifier,
+ vfio_cpr_kvm_close_notifier,
+ MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC,
+ -1);
}
}
@@ -282,9 +283,9 @@ static int vfio_cpr_pci_notifier(NotifierWithReturn *notifier,
void vfio_cpr_pci_register_device(VFIOPCIDevice *vdev)
{
- migration_add_notifier_mode(&vdev->cpr.transfer_notifier,
- vfio_cpr_pci_notifier,
- MIG_MODE_CPR_TRANSFER);
+ migration_add_notifier_modes(&vdev->cpr.transfer_notifier,
+ vfio_cpr_pci_notifier,
+ MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC, -1);
}
void vfio_cpr_pci_unregister_device(VFIOPCIDevice *vdev)
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 5b022da..06b06af 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -2821,8 +2821,8 @@ static int vfio_pci_save_config(VFIODevice *vbasedev, QEMUFile *f, Error **errp)
{
VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
- return vmstate_save_state_with_err(f, &vmstate_vfio_pci_config, vdev, NULL,
- errp);
+ return vmstate_save_state(f, &vmstate_vfio_pci_config, vdev, NULL,
+ errp);
}
static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
@@ -2831,13 +2831,16 @@ static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
PCIDevice *pdev = PCI_DEVICE(vdev);
pcibus_t old_addr[PCI_NUM_REGIONS - 1];
int bar, ret;
+ Error *local_err = NULL;
for (bar = 0; bar < PCI_ROM_SLOT; bar++) {
old_addr[bar] = pdev->io_regions[bar].addr;
}
- ret = vmstate_load_state(f, &vmstate_vfio_pci_config, vdev, 1);
+ ret = vmstate_load_state(f, &vmstate_vfio_pci_config, vdev, 1,
+ &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
diff --git a/hw/virtio/virtio-mmio.c b/hw/virtio/virtio-mmio.c
index 532c671..fb58c36 100644
--- a/hw/virtio/virtio-mmio.c
+++ b/hw/virtio/virtio-mmio.c
@@ -34,6 +34,7 @@
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "trace.h"
+#include "qapi/error.h"
static bool virtio_mmio_ioeventfd_enabled(DeviceState *d)
{
@@ -612,14 +613,14 @@ static void virtio_mmio_save_extra_state(DeviceState *opaque, QEMUFile *f)
{
VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
- vmstate_save_state(f, &vmstate_virtio_mmio, proxy, NULL);
+ vmstate_save_state(f, &vmstate_virtio_mmio, proxy, NULL, &error_fatal);
}
static int virtio_mmio_load_extra_state(DeviceState *opaque, QEMUFile *f)
{
VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
- return vmstate_load_state(f, &vmstate_virtio_mmio, proxy, 1);
+ return vmstate_load_state(f, &vmstate_virtio_mmio, proxy, 1, &error_fatal);
}
static bool virtio_mmio_has_extra_state(DeviceState *opaque)
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 767216d..d2595fb 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -154,14 +154,14 @@ static void virtio_pci_save_extra_state(DeviceState *d, QEMUFile *f)
{
VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
- vmstate_save_state(f, &vmstate_virtio_pci, proxy, NULL);
+ vmstate_save_state(f, &vmstate_virtio_pci, proxy, NULL, &error_fatal);
}
static int virtio_pci_load_extra_state(DeviceState *d, QEMUFile *f)
{
VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
- return vmstate_load_state(f, &vmstate_virtio_pci, proxy, 1);
+ return vmstate_load_state(f, &vmstate_virtio_pci, proxy, 1, &error_fatal);
}
static void virtio_pci_save_queue(DeviceState *d, int n, QEMUFile *f)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index 9a81ad9..0a68f1b 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -2992,6 +2992,7 @@ int virtio_save(VirtIODevice *vdev, QEMUFile *f)
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
uint32_t guest_features_lo = (vdev->guest_features & 0xffffffff);
int i;
+ Error *local_err = NULL;
if (k->save_config) {
k->save_config(qbus->parent, f);
@@ -3035,14 +3036,15 @@ int virtio_save(VirtIODevice *vdev, QEMUFile *f)
}
if (vdc->vmsd) {
- int ret = vmstate_save_state(f, vdc->vmsd, vdev, NULL);
+ int ret = vmstate_save_state(f, vdc->vmsd, vdev, NULL, &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
}
/* Subsections */
- return vmstate_save_state(f, &vmstate_virtio, vdev, NULL);
+ return vmstate_save_state(f, &vmstate_virtio, vdev, NULL, &error_fatal);
}
/* A wrapper for use as a VMState .put function */
@@ -3235,6 +3237,7 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
+ Error *local_err = NULL;
/*
* We poison the endianness to ensure it does not get used before
@@ -3327,15 +3330,17 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
}
if (vdc->vmsd) {
- ret = vmstate_load_state(f, vdc->vmsd, vdev, version_id);
+ ret = vmstate_load_state(f, vdc->vmsd, vdev, version_id, &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
}
/* Subsections */
- ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1);
+ ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1, &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h
index 2f62ce2..2e9fe11 100644
--- a/include/crypto/tlssession.h
+++ b/include/crypto/tlssession.h
@@ -110,6 +110,7 @@
typedef struct QCryptoTLSSession QCryptoTLSSession;
#define QCRYPTO_TLS_SESSION_ERR_BLOCK -2
+#define QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION -3
/**
* qcrypto_tls_session_new:
@@ -259,7 +260,6 @@ ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
* @sess: the TLS session object
* @buf: to fill with plain text received
* @len: the length of @buf
- * @gracefulTermination: treat premature termination as graceful EOF
* @errp: pointer to hold returned error object
*
* Receive up to @len bytes of data from the remote peer
@@ -267,22 +267,18 @@ ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
* qcrypto_tls_session_set_callbacks(), decrypt it and
* store it in @buf.
*
- * If @gracefulTermination is true, then a premature termination
- * of the TLS session will be treated as indicating EOF, as
- * opposed to an error.
- *
* It is an error to call this before
* qcrypto_tls_session_handshake() returns
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
*
* Returns: the number of bytes received,
* or QCRYPTO_TLS_SESSION_ERR_BLOCK if the receive would block,
- * or -1 on error.
+ * or QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION if a premature termination
+ * is detected, or -1 on error.
*/
ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
char *buf,
size_t len,
- bool gracefulTermination,
Error **errp);
/**
diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h
index f373781..b96ac49 100644
--- a/include/exec/cpu-common.h
+++ b/include/exec/cpu-common.h
@@ -123,13 +123,13 @@ size_t qemu_ram_pagesize_largest(void);
void cpu_address_space_init(CPUState *cpu, int asidx,
const char *prefix, MemoryRegion *mr);
/**
- * cpu_address_space_destroy:
- * @cpu: CPU for which address space needs to be destroyed
- * @asidx: integer index of this address space
+ * cpu_destroy_address_spaces:
+ * @cpu: CPU for which address spaces need to be destroyed
*
- * Note that with KVM only one address space is supported.
+ * Destroy all address spaces associated with this CPU; this
+ * is called as part of unrealizing the CPU.
*/
-void cpu_address_space_destroy(CPUState *cpu, int asidx);
+void cpu_destroy_address_spaces(CPUState *cpu);
void cpu_physical_memory_rw(hwaddr addr, void *buf,
hwaddr len, bool is_write);
diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h
index a16c005..bd7182c 100644
--- a/include/exec/gdbstub.h
+++ b/include/exec/gdbstub.h
@@ -55,9 +55,6 @@ void gdb_unregister_coprocessor_all(CPUState *cpu);
* system emulation you can use a full chardev spec for your gdbserver
* port.
*
- * The error handle should be either &error_fatal (for start-up) or
- * &error_warn (for QMP/HMP initiated sessions).
- *
* Returns true when server successfully started.
*/
bool gdbserver_start(const char *port_or_device, Error **errp);
diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h
index c9f40c2..0fcbc92 100644
--- a/include/hw/core/cpu.h
+++ b/include/hw/core/cpu.h
@@ -515,7 +515,6 @@ struct CPUState {
QSIMPLEQ_HEAD(, qemu_work_item) work_list;
struct CPUAddressSpace *cpu_ases;
- int cpu_ases_count;
int num_ases;
AddressSpace *as;
MemoryRegion *memory;
diff --git a/include/hw/intc/riscv_aclint.h b/include/hw/intc/riscv_aclint.h
index 693415e..4b7406e 100644
--- a/include/hw/intc/riscv_aclint.h
+++ b/include/hw/intc/riscv_aclint.h
@@ -80,4 +80,8 @@ enum {
RISCV_ACLINT_SWI_SIZE = 0x4000
};
+#define VMSTATE_TIMER_PTR_VARRAY(_f, _s, _f_n) \
+VMSTATE_VARRAY_OF_POINTER_UINT32(_f, _s, _f_n, 0, vmstate_info_timer, \
+ QEMUTimer *)
+
#endif
diff --git a/include/migration/colo.h b/include/migration/colo.h
index 43222ef..d4fe422 100644
--- a/include/migration/colo.h
+++ b/include/migration/colo.h
@@ -25,7 +25,7 @@ void migrate_start_colo_process(MigrationState *s);
bool migration_in_colo_state(void);
/* loadvm */
-int migration_incoming_enable_colo(void);
+int migration_incoming_enable_colo(Error **errp);
void migration_incoming_disable_colo(void);
bool migration_incoming_colo_enabled(void);
bool migration_incoming_in_colo_state(void);
diff --git a/include/migration/cpr.h b/include/migration/cpr.h
index 3fc19a7..a412d66 100644
--- a/include/migration/cpr.h
+++ b/include/migration/cpr.h
@@ -34,6 +34,9 @@ void cpr_resave_fd(const char *name, int id, int fd);
int cpr_open_fd(const char *path, int flags, const char *name, int id,
Error **errp);
+typedef bool (*cpr_walk_fd_cb)(int fd);
+bool cpr_walk_fd(cpr_walk_fd_cb cb);
+
MigMode cpr_get_incoming_mode(void);
void cpr_set_incoming_mode(MigMode mode);
bool cpr_is_incoming(void);
@@ -50,4 +53,11 @@ int cpr_get_fd_param(const char *name, const char *fdname, int index,
QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp);
QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp);
+void cpr_exec_init(void);
+QEMUFile *cpr_exec_output(Error **errp);
+QEMUFile *cpr_exec_input(Error **errp);
+void cpr_exec_persist_state(QEMUFile *f);
+bool cpr_exec_has_state(void);
+void cpr_exec_unpersist_state(void);
+void cpr_exec_unpreserve_fds(void);
#endif
diff --git a/include/migration/misc.h b/include/migration/misc.h
index a261f99..592b930 100644
--- a/include/migration/misc.h
+++ b/include/migration/misc.h
@@ -95,7 +95,19 @@ void migration_add_notifier(NotifierWithReturn *notify,
void migration_add_notifier_mode(NotifierWithReturn *notify,
MigrationNotifyFunc func, MigMode mode);
+/*
+ * Same as migration_add_notifier, but applies to all @mode in the argument
+ * list. The list is terminated by -1 or MIG_MODE_ALL. For the latter,
+ * the notifier is added for all modes.
+ */
+void migration_add_notifier_modes(NotifierWithReturn *notify,
+ MigrationNotifyFunc func, MigMode mode, ...);
+
+/*
+ * Remove a notifier from all modes.
+ */
void migration_remove_notifier(NotifierWithReturn *notify);
+
void migration_file_set_error(int ret, Error *err);
/* True if incoming migration entered POSTCOPY_INCOMING_DISCARD */
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 1ff7bd9..63ccaee 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -163,6 +163,7 @@ typedef enum {
MIG_PRI_IOMMU, /* Must happen before PCI devices */
MIG_PRI_PCI_BUS, /* Must happen before IOMMU */
MIG_PRI_VIRTIO_MEM, /* Must happen before IOMMU */
+ MIG_PRI_APIC, /* Must happen before PCI devices */
MIG_PRI_GICV3_ITS, /* Must happen before PCI devices */
MIG_PRI_GICV3, /* Must happen before the ITS */
MIG_PRI_MAX,
@@ -200,14 +201,28 @@ struct VMStateDescription {
* exclusive. For this reason, also early_setup VMSDs are migrated in a
* QEMU_VM_SECTION_FULL section, while save_setup() data is migrated in
* a QEMU_VM_SECTION_START section.
+ *
+ * There are duplicate impls of the post/pre save/load hooks.
+ * New impls should preferentally use 'errp' variants of these
+ * methods and existing impls incrementally converted.
+ * The variants without 'errp' are intended to be removed
+ * once all usage is converted.
+ *
+ * For the errp variants,
+ * Returns: 0 on success,
+ * <0 on error where -value is an error number from errno.h
*/
+
bool early_setup;
int version_id;
int minimum_version_id;
MigrationPriority priority;
int (*pre_load)(void *opaque);
+ int (*pre_load_errp)(void *opaque, Error **errp);
int (*post_load)(void *opaque, int version_id);
+ int (*post_load_errp)(void *opaque, int version_id, Error **errp);
int (*pre_save)(void *opaque);
+ int (*pre_save_errp)(void *opaque, Error **errp);
int (*post_save)(void *opaque);
bool (*needed)(void *opaque);
bool (*dev_unplug_pending)(void *opaque);
@@ -522,6 +537,16 @@ extern const VMStateInfo vmstate_info_qlist;
.offset = vmstate_offset_array(_s, _f, _type*, _n), \
}
+#define VMSTATE_VARRAY_OF_POINTER_UINT32(_field, _state, _field_num, _version, _info, _type) { \
+ .name = (stringify(_field)), \
+ .version_id = (_version), \
+ .num_offset = vmstate_offset_value(_state, _field_num, uint32_t), \
+ .info = &(_info), \
+ .size = sizeof(_type), \
+ .flags = VMS_VARRAY_UINT32 | VMS_ARRAY_OF_POINTER | VMS_POINTER, \
+ .offset = vmstate_offset_pointer(_state, _field, _type), \
+}
+
#define VMSTATE_STRUCT_SUB_ARRAY(_field, _state, _start, _num, _version, _vmsd, _type) { \
.name = (stringify(_field)), \
.version_id = (_version), \
@@ -1196,10 +1221,8 @@ extern const VMStateInfo vmstate_info_qlist;
}
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, int version_id);
+ void *opaque, int version_id, Error **errp);
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, JSONWriter *vmdesc);
-int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc, Error **errp);
int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
diff --git a/include/qapi/error.h b/include/qapi/error.h
index 41e3816..b16c630 100644
--- a/include/qapi/error.h
+++ b/include/qapi/error.h
@@ -534,12 +534,6 @@ static inline void error_propagator_cleanup(ErrorPropagator *prop)
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(ErrorPropagator, error_propagator_cleanup);
/*
- * Special error destination to warn on error.
- * See error_setg() and error_propagate() for details.
- */
-extern Error *error_warn;
-
-/*
* Special error destination to abort on error.
* See error_setg() and error_propagate() for details.
*/
diff --git a/include/qemu/log-for-trace.h b/include/qemu/log-for-trace.h
index d47c9cd..f3a8791 100644
--- a/include/qemu/log-for-trace.h
+++ b/include/qemu/log-for-trace.h
@@ -19,9 +19,9 @@
#define QEMU_LOG_FOR_TRACE_H
/* Private global variable, don't use */
-extern int qemu_loglevel;
+extern unsigned qemu_loglevel;
-#define LOG_TRACE (1 << 15)
+#define LOG_TRACE (1u << 15)
/* Returns true if a bit is set in the current loglevel mask */
static inline bool qemu_loglevel_mask(int mask)
diff --git a/include/qemu/log.h b/include/qemu/log.h
index aae7298..7effba4 100644
--- a/include/qemu/log.h
+++ b/include/qemu/log.h
@@ -14,30 +14,30 @@ bool qemu_log_enabled(void);
/* Returns true if qemu_log() will write somewhere other than stderr. */
bool qemu_log_separate(void);
-#define CPU_LOG_TB_OUT_ASM (1 << 0)
-#define CPU_LOG_TB_IN_ASM (1 << 1)
-#define CPU_LOG_TB_OP (1 << 2)
-#define CPU_LOG_TB_OP_OPT (1 << 3)
-#define CPU_LOG_INT (1 << 4)
-#define CPU_LOG_EXEC (1 << 5)
-#define CPU_LOG_PCALL (1 << 6)
-#define CPU_LOG_TB_CPU (1 << 8)
-#define CPU_LOG_RESET (1 << 9)
-#define LOG_UNIMP (1 << 10)
-#define LOG_GUEST_ERROR (1 << 11)
-#define CPU_LOG_MMU (1 << 12)
-#define CPU_LOG_TB_NOCHAIN (1 << 13)
-#define CPU_LOG_PAGE (1 << 14)
+#define CPU_LOG_TB_OUT_ASM (1u << 0)
+#define CPU_LOG_TB_IN_ASM (1u << 1)
+#define CPU_LOG_TB_OP (1u << 2)
+#define CPU_LOG_TB_OP_OPT (1u << 3)
+#define CPU_LOG_INT (1u << 4)
+#define CPU_LOG_EXEC (1u << 5)
+#define CPU_LOG_PCALL (1u << 6)
+#define CPU_LOG_TB_CPU (1u << 8)
+#define CPU_LOG_RESET (1u << 9)
+#define LOG_UNIMP (1u << 10)
+#define LOG_GUEST_ERROR (1u << 11)
+#define CPU_LOG_MMU (1u << 12)
+#define CPU_LOG_TB_NOCHAIN (1u << 13)
+#define CPU_LOG_PAGE (1u << 14)
/* LOG_TRACE (1 << 15) is defined in log-for-trace.h */
-#define CPU_LOG_TB_OP_IND (1 << 16)
-#define CPU_LOG_TB_FPU (1 << 17)
-#define CPU_LOG_PLUGIN (1 << 18)
+#define CPU_LOG_TB_OP_IND (1u << 16)
+#define CPU_LOG_TB_FPU (1u << 17)
+#define CPU_LOG_PLUGIN (1u << 18)
/* LOG_STRACE is used for user-mode strace logging. */
-#define LOG_STRACE (1 << 19)
-#define LOG_PER_THREAD (1 << 20)
-#define CPU_LOG_TB_VPU (1 << 21)
-#define LOG_TB_OP_PLUGIN (1 << 22)
-#define LOG_INVALID_MEM (1 << 23)
+#define LOG_STRACE (1u << 19)
+#define LOG_PER_THREAD (1u << 20)
+#define CPU_LOG_TB_VPU (1u << 21)
+#define LOG_TB_OP_PLUGIN (1u << 22)
+#define LOG_INVALID_MEM (1u << 23)
/* Lock/unlock output. */
diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
index 1b38cb7..cf8d7cf 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
@@ -561,7 +561,7 @@ int madvise(char *, size_t, int);
#if defined(__linux__) && \
(defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) \
- || defined(__powerpc64__))
+ || defined(__powerpc64__) || defined(__riscv))
/* Use 2 MiB alignment so transparent hugepages can be used by KVM.
Valgrind does not support alignments larger than 1 MiB,
therefore we need special code which handles running on Valgrind. */
@@ -689,6 +689,15 @@ ssize_t qemu_write_full(int fd, const void *buf, size_t count)
void qemu_set_cloexec(int fd);
bool qemu_set_blocking(int fd, bool block, Error **errp);
+/*
+ * Clear FD_CLOEXEC for a descriptor.
+ *
+ * The caller must guarantee that no other fork+exec's occur before the
+ * exec that is intended to inherit this descriptor, eg by suspending CPUs
+ * and blocking monitor commands.
+ */
+void qemu_clear_cloexec(int fd);
+
/* Return a dynamically allocated directory path that is appropriate for storing
* local state.
*
diff --git a/include/system/memory.h b/include/system/memory.h
index aa85fc2..08daf0f 100644
--- a/include/system/memory.h
+++ b/include/system/memory.h
@@ -2727,15 +2727,33 @@ void address_space_init(AddressSpace *as, MemoryRegion *root, const char *name);
/**
* address_space_destroy: destroy an address space
*
- * Releases all resources associated with an address space. After an address space
- * is destroyed, its root memory region (given by address_space_init()) may be destroyed
- * as well.
+ * Releases all resources associated with an address space. After an
+ * address space is destroyed, the reference the AddressSpace had to
+ * its root memory region is dropped, which may result in the
+ * destruction of that memory region as well.
+ *
+ * Note that destruction of the AddressSpace is done via RCU;
+ * it is therefore not valid to free the memory the AddressSpace
+ * struct is in until after that RCU callback has completed.
+ * If you want to g_free() the AddressSpace after destruction you
+ * can do that with address_space_destroy_free().
*
* @as: address space to be destroyed
*/
void address_space_destroy(AddressSpace *as);
/**
+ * address_space_destroy_free: destroy an address space and free it
+ *
+ * This does the same thing as address_space_destroy(), and then also
+ * frees (via g_free()) the AddressSpace itself once the destruction
+ * is complete.
+ *
+ * @as: address space to be destroyed
+ */
+void address_space_destroy_free(AddressSpace *as);
+
+/**
* address_space_remove_listeners: unregister all listeners of an address space
*
* Removes all callbacks previously registered with memory_listener_register()
diff --git a/include/system/os-win32.h b/include/system/os-win32.h
index 3aa6cee..22d72ba 100644
--- a/include/system/os-win32.h
+++ b/include/system/os-win32.h
@@ -168,11 +168,14 @@ static inline void qemu_funlockfile(FILE *f)
#endif
}
-/* Helper for WSAEventSelect, to report errors */
+/* Helpers for WSAEventSelect() */
bool qemu_socket_select(int sockfd, WSAEVENT hEventObject,
long lNetworkEvents, Error **errp);
+void qemu_socket_select_nofail(int sockfd, WSAEVENT hEventObject,
+ long lNetworkEvents);
bool qemu_socket_unselect(int sockfd, Error **errp);
+void qemu_socket_unselect_nofail(int sockfd);
/* We wrap all the sockets functions so that we can set errno based on
* WSAGetLastError(), and use file-descriptors instead of SOCKET.
diff --git a/io/channel-socket.c b/io/channel-socket.c
index e53d9ac..712b793 100644
--- a/io/channel-socket.c
+++ b/io/channel-socket.c
@@ -454,7 +454,7 @@ static void qio_channel_socket_finalize(Object *obj)
}
}
#ifdef WIN32
- qemu_socket_unselect(ioc->fd, NULL);
+ qemu_socket_unselect_nofail(ioc->fd);
#endif
close(ioc->fd);
ioc->fd = -1;
@@ -929,7 +929,7 @@ qio_channel_socket_close(QIOChannel *ioc,
if (sioc->fd != -1) {
#ifdef WIN32
- qemu_socket_unselect(sioc->fd, NULL);
+ qemu_socket_unselect_nofail(sioc->fd);
#endif
if (qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_LISTEN)) {
socket_listen_cleanup(sioc->fd, errp);
diff --git a/io/channel-tls.c b/io/channel-tls.c
index 7135896..1fbed4b 100644
--- a/io/channel-tls.c
+++ b/io/channel-tls.c
@@ -346,6 +346,19 @@ static void qio_channel_tls_finalize(Object *obj)
qcrypto_tls_session_free(ioc->session);
}
+static bool
+qio_channel_tls_allow_premature_termination(QIOChannelTLS *tioc, int flags)
+{
+ if (flags & QIO_CHANNEL_READ_FLAG_RELAXED_EOF) {
+ return true;
+ }
+
+ if (qatomic_read(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ) {
+ return true;
+ }
+
+ return false;
+}
static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
const struct iovec *iov,
@@ -364,8 +377,6 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
tioc->session,
iov[i].iov_base,
iov[i].iov_len,
- flags & QIO_CHANNEL_READ_FLAG_RELAXED_EOF ||
- qatomic_load_acquire(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ,
errp);
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
if (got) {
@@ -373,6 +384,12 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
} else {
return QIO_CHANNEL_ERR_BLOCK;
}
+ } else if (ret == QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION) {
+ if (qio_channel_tls_allow_premature_termination(tioc, flags)) {
+ ret = 0;
+ } else {
+ return -1;
+ }
} else if (ret < 0) {
return -1;
}
diff --git a/io/channel-watch.c b/io/channel-watch.c
index 64b486e..018648b 100644
--- a/io/channel-watch.c
+++ b/io/channel-watch.c
@@ -281,9 +281,9 @@ GSource *qio_channel_create_socket_watch(QIOChannel *ioc,
GSource *source;
QIOChannelSocketSource *ssource;
- qemu_socket_select(sockfd, ioc->event,
- FD_READ | FD_ACCEPT | FD_CLOSE |
- FD_CONNECT | FD_WRITE | FD_OOB, NULL);
+ qemu_socket_select_nofail(sockfd, ioc->event,
+ FD_READ | FD_ACCEPT | FD_CLOSE |
+ FD_CONNECT | FD_WRITE | FD_OOB);
source = g_source_new(&qio_channel_socket_source_funcs,
sizeof(QIOChannelSocketSource));
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 1a5f2a0..d78b202 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -9023,6 +9023,29 @@ static int do_getdents64(abi_long dirfd, abi_long arg2, abi_long count)
#define RISCV_HWPROBE_EXT_ZTSO (1ULL << 33)
#define RISCV_HWPROBE_EXT_ZACAS (1ULL << 34)
#define RISCV_HWPROBE_EXT_ZICOND (1ULL << 35)
+#define RISCV_HWPROBE_EXT_ZIHINTPAUSE (1ULL << 36)
+#define RISCV_HWPROBE_EXT_ZVE32X (1ULL << 37)
+#define RISCV_HWPROBE_EXT_ZVE32F (1ULL << 38)
+#define RISCV_HWPROBE_EXT_ZVE64X (1ULL << 39)
+#define RISCV_HWPROBE_EXT_ZVE64F (1ULL << 40)
+#define RISCV_HWPROBE_EXT_ZVE64D (1ULL << 41)
+#define RISCV_HWPROBE_EXT_ZIMOP (1ULL << 42)
+#define RISCV_HWPROBE_EXT_ZCA (1ULL << 43)
+#define RISCV_HWPROBE_EXT_ZCB (1ULL << 44)
+#define RISCV_HWPROBE_EXT_ZCD (1ULL << 45)
+#define RISCV_HWPROBE_EXT_ZCF (1ULL << 46)
+#define RISCV_HWPROBE_EXT_ZCMOP (1ULL << 47)
+#define RISCV_HWPROBE_EXT_ZAWRS (1ULL << 48)
+#define RISCV_HWPROBE_EXT_SUPM (1ULL << 49)
+#define RISCV_HWPROBE_EXT_ZICNTR (1ULL << 50)
+#define RISCV_HWPROBE_EXT_ZIHPM (1ULL << 51)
+#define RISCV_HWPROBE_EXT_ZFBFMIN (1ULL << 52)
+#define RISCV_HWPROBE_EXT_ZVFBFMIN (1ULL << 53)
+#define RISCV_HWPROBE_EXT_ZVFBFWMA (1ULL << 54)
+#define RISCV_HWPROBE_EXT_ZICBOM (1ULL << 55)
+#define RISCV_HWPROBE_EXT_ZAAMO (1ULL << 56)
+#define RISCV_HWPROBE_EXT_ZALRSC (1ULL << 57)
+#define RISCV_HWPROBE_EXT_ZABHA (1ULL << 58)
#define RISCV_HWPROBE_KEY_CPUPERF_0 5
#define RISCV_HWPROBE_MISALIGNED_UNKNOWN (0 << 0)
@@ -9033,6 +9056,22 @@ static int do_getdents64(abi_long dirfd, abi_long arg2, abi_long count)
#define RISCV_HWPROBE_MISALIGNED_MASK (7 << 0)
#define RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE 6
+#define RISCV_HWPROBE_KEY_HIGHEST_VIRT_ADDRESS 7
+#define RISCV_HWPROBE_KEY_TIME_CSR_FREQ 8
+#define RISCV_HWPROBE_KEY_MISALIGNED_SCALAR_PERF 9
+#define RISCV_HWPROBE_MISALIGNED_SCALAR_UNKNOWN 0
+#define RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED 1
+#define RISCV_HWPROBE_MISALIGNED_SCALAR_SLOW 2
+#define RISCV_HWPROBE_MISALIGNED_SCALAR_FAST 3
+#define RISCV_HWPROBE_MISALIGNED_SCALAR_UNSUPPORTED 4
+#define RISCV_HWPROBE_KEY_MISALIGNED_VECTOR_PERF 10
+#define RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN 0
+#define RISCV_HWPROBE_MISALIGNED_VECTOR_SLOW 2
+#define RISCV_HWPROBE_MISALIGNED_VECTOR_FAST 3
+#define RISCV_HWPROBE_MISALIGNED_VECTOR_UNSUPPORTED 4
+#define RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0 11
+#define RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE 12
+#define RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0 13
struct riscv_hwprobe {
abi_llong key;
@@ -9141,6 +9180,52 @@ static void risc_hwprobe_fill_pairs(CPURISCVState *env,
RISCV_HWPROBE_EXT_ZACAS : 0;
value |= cfg->ext_zicond ?
RISCV_HWPROBE_EXT_ZICOND : 0;
+ value |= cfg->ext_zihintpause ?
+ RISCV_HWPROBE_EXT_ZIHINTPAUSE : 0;
+ value |= cfg->ext_zve32x ?
+ RISCV_HWPROBE_EXT_ZVE32X : 0;
+ value |= cfg->ext_zve32f ?
+ RISCV_HWPROBE_EXT_ZVE32F : 0;
+ value |= cfg->ext_zve64x ?
+ RISCV_HWPROBE_EXT_ZVE64X : 0;
+ value |= cfg->ext_zve64f ?
+ RISCV_HWPROBE_EXT_ZVE64F : 0;
+ value |= cfg->ext_zve64d ?
+ RISCV_HWPROBE_EXT_ZVE64D : 0;
+ value |= cfg->ext_zimop ?
+ RISCV_HWPROBE_EXT_ZIMOP : 0;
+ value |= cfg->ext_zca ?
+ RISCV_HWPROBE_EXT_ZCA : 0;
+ value |= cfg->ext_zcb ?
+ RISCV_HWPROBE_EXT_ZCB : 0;
+ value |= cfg->ext_zcd ?
+ RISCV_HWPROBE_EXT_ZCD : 0;
+ value |= cfg->ext_zcf ?
+ RISCV_HWPROBE_EXT_ZCF : 0;
+ value |= cfg->ext_zcmop ?
+ RISCV_HWPROBE_EXT_ZCMOP : 0;
+ value |= cfg->ext_zawrs ?
+ RISCV_HWPROBE_EXT_ZAWRS : 0;
+ value |= cfg->ext_supm ?
+ RISCV_HWPROBE_EXT_SUPM : 0;
+ value |= cfg->ext_zicntr ?
+ RISCV_HWPROBE_EXT_ZICNTR : 0;
+ value |= cfg->ext_zihpm ?
+ RISCV_HWPROBE_EXT_ZIHPM : 0;
+ value |= cfg->ext_zfbfmin ?
+ RISCV_HWPROBE_EXT_ZFBFMIN : 0;
+ value |= cfg->ext_zvfbfmin ?
+ RISCV_HWPROBE_EXT_ZVFBFMIN : 0;
+ value |= cfg->ext_zvfbfwma ?
+ RISCV_HWPROBE_EXT_ZVFBFWMA : 0;
+ value |= cfg->ext_zicbom ?
+ RISCV_HWPROBE_EXT_ZICBOM : 0;
+ value |= cfg->ext_zaamo ?
+ RISCV_HWPROBE_EXT_ZAAMO : 0;
+ value |= cfg->ext_zalrsc ?
+ RISCV_HWPROBE_EXT_ZALRSC : 0;
+ value |= cfg->ext_zabha ?
+ RISCV_HWPROBE_EXT_ZABHA : 0;
__put_user(value, &pair->value);
break;
case RISCV_HWPROBE_KEY_CPUPERF_0:
@@ -9150,6 +9235,10 @@ static void risc_hwprobe_fill_pairs(CPURISCVState *env,
value = cfg->ext_zicboz ? cfg->cboz_blocksize : 0;
__put_user(value, &pair->value);
break;
+ case RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE:
+ value = cfg->ext_zicbom ? cfg->cbom_blocksize : 0;
+ __put_user(value, &pair->value);
+ break;
default:
__put_user(-1, &pair->key);
break;
diff --git a/migration/colo.c b/migration/colo.c
index cf4d71d..db783f6 100644
--- a/migration/colo.c
+++ b/migration/colo.c
@@ -686,11 +686,10 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis,
bql_lock();
cpu_synchronize_all_states();
- ret = qemu_loadvm_state_main(mis->from_src_file, mis);
+ ret = qemu_loadvm_state_main(mis->from_src_file, mis, errp);
bql_unlock();
if (ret < 0) {
- error_setg(errp, "Load VM's live state (ram) error");
return;
}
@@ -729,9 +728,8 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis,
bql_lock();
vmstate_loading = true;
colo_flush_ram_cache();
- ret = qemu_load_device_state(fb);
+ ret = qemu_load_device_state(fb, errp);
if (ret < 0) {
- error_setg(errp, "COLO: load device state failed");
vmstate_loading = false;
bql_unlock();
return;
@@ -849,10 +847,6 @@ static void *colo_process_incoming_thread(void *opaque)
failover_init_state();
mis->to_src_file = qemu_file_get_return_path(mis->from_src_file);
- if (!mis->to_src_file) {
- error_report("COLO incoming thread: Open QEMUFile to_src_file failed");
- goto out;
- }
/*
* Note: the communication between Primary side and Secondary side
* should be sequential, we set the fd to unblocked in migration incoming
diff --git a/migration/cpr-exec.c b/migration/cpr-exec.c
new file mode 100644
index 0000000..d57714b
--- /dev/null
+++ b/migration/cpr-exec.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2021-2025 Oracle and/or its affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "qemu/memfd.h"
+#include "qapi/error.h"
+#include "qapi/type-helpers.h"
+#include "io/channel-file.h"
+#include "io/channel-socket.h"
+#include "block/block-global-state.h"
+#include "qemu/main-loop.h"
+#include "migration/cpr.h"
+#include "migration/qemu-file.h"
+#include "migration/migration.h"
+#include "migration/misc.h"
+#include "migration/vmstate.h"
+#include "system/runstate.h"
+#include "trace.h"
+
+#define CPR_EXEC_STATE_NAME "QEMU_CPR_EXEC_STATE"
+
+static QEMUFile *qemu_file_new_fd_input(int fd, const char *name)
+{
+ g_autoptr(QIOChannelFile) fioc = qio_channel_file_new_fd(fd);
+ QIOChannel *ioc = QIO_CHANNEL(fioc);
+ qio_channel_set_name(ioc, name);
+ return qemu_file_new_input(ioc);
+}
+
+static QEMUFile *qemu_file_new_fd_output(int fd, const char *name)
+{
+ g_autoptr(QIOChannelFile) fioc = qio_channel_file_new_fd(fd);
+ QIOChannel *ioc = QIO_CHANNEL(fioc);
+ qio_channel_set_name(ioc, name);
+ return qemu_file_new_output(ioc);
+}
+
+void cpr_exec_persist_state(QEMUFile *f)
+{
+ QIOChannelFile *fioc = QIO_CHANNEL_FILE(qemu_file_get_ioc(f));
+ int mfd = dup(fioc->fd);
+ char val[16];
+
+ /* Remember mfd in environment for post-exec load */
+ qemu_clear_cloexec(mfd);
+ snprintf(val, sizeof(val), "%d", mfd);
+ g_setenv(CPR_EXEC_STATE_NAME, val, 1);
+}
+
+static int cpr_exec_find_state(void)
+{
+ const char *val = g_getenv(CPR_EXEC_STATE_NAME);
+ int mfd;
+
+ assert(val);
+ g_unsetenv(CPR_EXEC_STATE_NAME);
+ assert(!qemu_strtoi(val, NULL, 10, &mfd));
+ return mfd;
+}
+
+bool cpr_exec_has_state(void)
+{
+ return g_getenv(CPR_EXEC_STATE_NAME) != NULL;
+}
+
+void cpr_exec_unpersist_state(void)
+{
+ int mfd;
+ const char *val = g_getenv(CPR_EXEC_STATE_NAME);
+
+ g_unsetenv(CPR_EXEC_STATE_NAME);
+ assert(val);
+ assert(!qemu_strtoi(val, NULL, 10, &mfd));
+ close(mfd);
+}
+
+QEMUFile *cpr_exec_output(Error **errp)
+{
+ int mfd;
+
+#ifdef CONFIG_LINUX
+ mfd = qemu_memfd_create(CPR_EXEC_STATE_NAME, 0, false, 0, 0, errp);
+#else
+ mfd = -1;
+#endif
+
+ if (mfd < 0) {
+ return NULL;
+ }
+
+ return qemu_file_new_fd_output(mfd, CPR_EXEC_STATE_NAME);
+}
+
+QEMUFile *cpr_exec_input(Error **errp)
+{
+ int mfd = cpr_exec_find_state();
+
+ lseek(mfd, 0, SEEK_SET);
+ return qemu_file_new_fd_input(mfd, CPR_EXEC_STATE_NAME);
+}
+
+static bool preserve_fd(int fd)
+{
+ qemu_clear_cloexec(fd);
+ return true;
+}
+
+static bool unpreserve_fd(int fd)
+{
+ qemu_set_cloexec(fd);
+ return true;
+}
+
+static void cpr_exec_preserve_fds(void)
+{
+ cpr_walk_fd(preserve_fd);
+}
+
+void cpr_exec_unpreserve_fds(void)
+{
+ cpr_walk_fd(unpreserve_fd);
+}
+
+static void cpr_exec_cb(void *opaque)
+{
+ MigrationState *s = migrate_get_current();
+ char **argv = strv_from_str_list(s->parameters.cpr_exec_command);
+ Error *err = NULL;
+
+ /*
+ * Clear the close-on-exec flag for all preserved fd's. We cannot do so
+ * earlier because they should not persist across miscellaneous fork and
+ * exec calls that are performed during normal operation.
+ */
+ cpr_exec_preserve_fds();
+
+ trace_cpr_exec();
+ execvp(argv[0], argv);
+
+ /*
+ * exec should only fail if argv[0] is bogus, or has a permissions problem,
+ * or the system is very short on resources.
+ */
+ g_strfreev(argv);
+ cpr_exec_unpreserve_fds();
+
+ error_setg_errno(&err, errno, "execvp %s failed", argv[0]);
+ error_report_err(error_copy(err));
+ migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED);
+ migrate_set_error(s, err);
+
+ /* Note, we can go from state COMPLETED to FAILED */
+ migration_call_notifiers(s, MIG_EVENT_PRECOPY_FAILED, NULL);
+
+ err = NULL;
+ if (!migration_block_activate(&err)) {
+ /* error was already reported */
+ error_free(err);
+ return;
+ }
+
+ if (runstate_is_live(s->vm_old_state)) {
+ vm_start();
+ }
+}
+
+static int cpr_exec_notifier(NotifierWithReturn *notifier, MigrationEvent *e,
+ Error **errp)
+{
+ MigrationState *s = migrate_get_current();
+
+ if (e->type == MIG_EVENT_PRECOPY_DONE) {
+ QEMUBH *cpr_exec_bh = qemu_bh_new(cpr_exec_cb, NULL);
+ assert(s->state == MIGRATION_STATUS_COMPLETED);
+ qemu_bh_schedule(cpr_exec_bh);
+ qemu_notify_event();
+ } else if (e->type == MIG_EVENT_PRECOPY_FAILED) {
+ cpr_exec_unpersist_state();
+ }
+ return 0;
+}
+
+void cpr_exec_init(void)
+{
+ static NotifierWithReturn exec_notifier;
+
+ migration_add_notifier_mode(&exec_notifier, cpr_exec_notifier,
+ MIG_MODE_CPR_EXEC);
+}
diff --git a/migration/cpr.c b/migration/cpr.c
index 42ad0b0..22dbac7 100644
--- a/migration/cpr.c
+++ b/migration/cpr.c
@@ -6,7 +6,9 @@
*/
#include "qemu/osdep.h"
+#include "qemu/error-report.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
#include "hw/vfio/vfio-device.h"
#include "migration/cpr.h"
#include "migration/misc.h"
@@ -100,10 +102,10 @@ void cpr_resave_fd(const char *name, int id, int fd)
if (old_fd < 0) {
cpr_save_fd(name, id, fd);
} else if (old_fd != fd) {
- error_setg(&error_fatal,
- "internal error: cpr fd '%s' id %d value %d "
- "already saved with a different value %d",
- name, id, fd, old_fd);
+ error_report("internal error: cpr fd '%s' id %d value %d "
+ "already saved with a different value %d",
+ name, id, fd, old_fd);
+ g_assert_not_reached();
}
}
@@ -121,6 +123,19 @@ int cpr_open_fd(const char *path, int flags, const char *name, int id,
return fd;
}
+bool cpr_walk_fd(cpr_walk_fd_cb cb)
+{
+ CprFd *elem;
+
+ QLIST_FOREACH(elem, &cpr_state.fds, next) {
+ g_assert(elem->fd >= 0);
+ if (!cb(elem->fd)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/*************************************************************************/
static const VMStateDescription vmstate_cpr_state = {
.name = CPR_STATE,
@@ -172,6 +187,8 @@ int cpr_state_save(MigrationChannel *channel, Error **errp)
if (mode == MIG_MODE_CPR_TRANSFER) {
g_assert(channel);
f = cpr_transfer_output(channel, errp);
+ } else if (mode == MIG_MODE_CPR_EXEC) {
+ f = cpr_exec_output(errp);
} else {
return 0;
}
@@ -182,13 +199,16 @@ int cpr_state_save(MigrationChannel *channel, Error **errp)
qemu_put_be32(f, QEMU_CPR_FILE_MAGIC);
qemu_put_be32(f, QEMU_CPR_FILE_VERSION);
- ret = vmstate_save_state(f, &vmstate_cpr_state, &cpr_state, 0);
+ ret = vmstate_save_state(f, &vmstate_cpr_state, &cpr_state, 0, errp);
if (ret) {
- error_setg(errp, "vmstate_save_state error %d", ret);
qemu_fclose(f);
return ret;
}
+ if (migrate_mode() == MIG_MODE_CPR_EXEC) {
+ cpr_exec_persist_state(f);
+ }
+
/*
* Close the socket only partially so we can later detect when the other
* end closes by getting a HUP event.
@@ -207,7 +227,13 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
QEMUFile *f;
MigMode mode = 0;
- if (channel) {
+ if (cpr_exec_has_state()) {
+ mode = MIG_MODE_CPR_EXEC;
+ f = cpr_exec_input(errp);
+ if (channel) {
+ warn_report("ignoring cpr channel for migration mode cpr-exec");
+ }
+ } else if (channel) {
mode = MIG_MODE_CPR_TRANSFER;
cpr_set_incoming_mode(mode);
f = cpr_transfer_input(channel, errp);
@@ -219,6 +245,7 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
}
trace_cpr_state_load(MigMode_str(mode));
+ cpr_set_incoming_mode(mode);
v = qemu_get_be32(f);
if (v != QEMU_CPR_FILE_MAGIC) {
@@ -233,13 +260,17 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
return -ENOTSUP;
}
- ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1);
+ ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1, errp);
if (ret) {
- error_setg(errp, "vmstate_load_state error %d", ret);
qemu_fclose(f);
return ret;
}
+ if (migrate_mode() == MIG_MODE_CPR_EXEC) {
+ /* Set cloexec to prevent fd leaks from fork until the next cpr-exec */
+ cpr_exec_unpreserve_fds();
+ }
+
/*
* Let the caller decide when to close the socket (and generate a HUP event
* for the sending side).
@@ -260,7 +291,7 @@ void cpr_state_close(void)
bool cpr_incoming_needed(void *opaque)
{
MigMode mode = migrate_mode();
- return mode == MIG_MODE_CPR_TRANSFER;
+ return mode == MIG_MODE_CPR_TRANSFER || mode == MIG_MODE_CPR_EXEC;
}
/*
diff --git a/migration/meson.build b/migration/meson.build
index 0f71544..16909d5 100644
--- a/migration/meson.build
+++ b/migration/meson.build
@@ -16,6 +16,7 @@ system_ss.add(files(
'channel-block.c',
'cpr.c',
'cpr-transfer.c',
+ 'cpr-exec.c',
'cpu-throttle.c',
'dirtyrate.c',
'exec.c',
diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c
index 0fc21f0..847d18f 100644
--- a/migration/migration-hmp-cmds.c
+++ b/migration/migration-hmp-cmds.c
@@ -306,6 +306,18 @@ void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict)
qapi_free_MigrationCapabilityStatusList(caps);
}
+static void monitor_print_cpr_exec_command(Monitor *mon, strList *args)
+{
+ monitor_printf(mon, "%s:",
+ MigrationParameter_str(MIGRATION_PARAMETER_CPR_EXEC_COMMAND));
+
+ while (args) {
+ monitor_printf(mon, " %s", args->value);
+ args = args->next;
+ }
+ monitor_printf(mon, "\n");
+}
+
void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
{
MigrationParameters *params;
@@ -353,6 +365,10 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
monitor_printf(mon, "%s: '%s'\n",
MigrationParameter_str(MIGRATION_PARAMETER_TLS_HOSTNAME),
params->tls_hostname);
+ assert(params->tls_authz);
+ monitor_printf(mon, "%s: '%s'\n",
+ MigrationParameter_str(MIGRATION_PARAMETER_TLS_AUTHZ),
+ params->tls_authz);
assert(params->has_max_bandwidth);
monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
MigrationParameter_str(MIGRATION_PARAMETER_MAX_BANDWIDTH),
@@ -361,6 +377,10 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
MigrationParameter_str(MIGRATION_PARAMETER_AVAIL_SWITCHOVER_BANDWIDTH),
params->avail_switchover_bandwidth);
+ assert(params->has_max_postcopy_bandwidth);
+ monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
+ MigrationParameter_str(MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH),
+ params->max_postcopy_bandwidth);
assert(params->has_downtime_limit);
monitor_printf(mon, "%s: %" PRIu64 " ms\n",
MigrationParameter_str(MIGRATION_PARAMETER_DOWNTIME_LIMIT),
@@ -383,12 +403,6 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
monitor_printf(mon, "%s: %" PRIu64 " bytes\n",
MigrationParameter_str(MIGRATION_PARAMETER_XBZRLE_CACHE_SIZE),
params->xbzrle_cache_size);
- monitor_printf(mon, "%s: %" PRIu64 "\n",
- MigrationParameter_str(MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH),
- params->max_postcopy_bandwidth);
- monitor_printf(mon, "%s: '%s'\n",
- MigrationParameter_str(MIGRATION_PARAMETER_TLS_AUTHZ),
- params->tls_authz);
if (params->has_block_bitmap_mapping) {
const BitmapMigrationNodeAliasList *bmnal;
@@ -435,6 +449,9 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
MIGRATION_PARAMETER_DIRECT_IO),
params->direct_io ? "on" : "off");
}
+
+ assert(params->has_cpr_exec_command);
+ monitor_print_cpr_exec_command(mon, params->cpr_exec_command);
}
qapi_free_MigrationParameters(params);
@@ -716,6 +733,21 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict)
p->has_direct_io = true;
visit_type_bool(v, param, &p->direct_io, &err);
break;
+ case MIGRATION_PARAMETER_CPR_EXEC_COMMAND: {
+ g_autofree char **strv = NULL;
+ g_autoptr(GError) gerr = NULL;
+ strList **tail = &p->cpr_exec_command;
+
+ if (!g_shell_parse_argv(valuestr, NULL, &strv, &gerr)) {
+ error_setg(&err, "%s", gerr->message);
+ break;
+ }
+ for (int i = 0; strv[i]; i++) {
+ QAPI_LIST_APPEND(tail, strv[i]);
+ }
+ p->has_cpr_exec_command = true;
+ break;
+ }
default:
g_assert_not_reached();
}
diff --git a/migration/migration.c b/migration/migration.c
index e1ac4d7..a63b46b 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -74,11 +74,7 @@
#define INMIGRATE_DEFAULT_EXIT_ON_ERROR true
-static NotifierWithReturnList migration_state_notifiers[] = {
- NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_NORMAL),
- NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_CPR_REBOOT),
- NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_CPR_TRANSFER),
-};
+static GSList *migration_state_notifiers[MIG_MODE__MAX];
/* Messages sent on the return path from destination to source */
enum mig_rp_message_type {
@@ -337,6 +333,7 @@ void migration_object_init(void)
ram_mig_init();
dirty_bitmap_mig_init();
+ cpr_exec_init();
/* Initialize cpu throttle timers */
cpu_throttle_init();
@@ -623,22 +620,22 @@ void migration_incoming_disable_colo(void)
migration_colo_enabled = false;
}
-int migration_incoming_enable_colo(void)
+int migration_incoming_enable_colo(Error **errp)
{
#ifndef CONFIG_REPLICATION
- error_report("ENABLE_COLO command come in migration stream, but the "
- "replication module is not built in");
+ error_setg(errp, "ENABLE_COLO command come in migration stream, but the "
+ "replication module is not built in");
return -ENOTSUP;
#endif
if (!migrate_colo()) {
- error_report("ENABLE_COLO command come in migration stream, but x-colo "
- "capability is not set");
+ error_setg(errp, "ENABLE_COLO command come in migration stream"
+ ", but x-colo capability is not set");
return -EINVAL;
}
if (ram_block_discard_disable(true)) {
- error_report("COLO: cannot disable RAM discard");
+ error_setg(errp, "COLO: cannot disable RAM discard");
return -EBUSY;
}
migration_colo_enabled = true;
@@ -881,7 +878,7 @@ process_incoming_migration_co(void *opaque)
MIGRATION_STATUS_ACTIVE);
mis->loadvm_co = qemu_coroutine_self();
- ret = qemu_loadvm_state(mis->from_src_file);
+ ret = qemu_loadvm_state(mis->from_src_file, &local_err);
mis->loadvm_co = NULL;
trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
@@ -908,7 +905,8 @@ process_incoming_migration_co(void *opaque)
}
if (ret < 0) {
- error_setg(&local_err, "load of migration failed: %s", strerror(-ret));
+ error_prepend(&local_err, "load of migration failed: %s: ",
+ strerror(-ret));
goto fail;
}
@@ -935,6 +933,15 @@ fail:
}
exit(EXIT_FAILURE);
+ } else {
+ /*
+ * Report the error here in case that QEMU abruptly exits
+ * when postcopy is enabled.
+ */
+ WITH_QEMU_LOCK_GUARD(&s->error_mutex) {
+ error_report_err(s->error);
+ s->error = NULL;
+ }
}
out:
/* Pairs with the refcount taken in qmp_migrate_incoming() */
@@ -1665,23 +1672,51 @@ void migration_cancel(void)
}
}
+static int get_modes(MigMode mode, va_list ap);
+
+static void add_notifiers(NotifierWithReturn *notify, int modes)
+{
+ for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
+ if (modes & BIT(mode)) {
+ migration_state_notifiers[mode] =
+ g_slist_prepend(migration_state_notifiers[mode], notify);
+ }
+ }
+}
+
+void migration_add_notifier_modes(NotifierWithReturn *notify,
+ MigrationNotifyFunc func, MigMode mode, ...)
+{
+ int modes;
+ va_list ap;
+
+ va_start(ap, mode);
+ modes = get_modes(mode, ap);
+ va_end(ap);
+
+ notify->notify = (NotifierWithReturnFunc)func;
+ add_notifiers(notify, modes);
+}
+
void migration_add_notifier_mode(NotifierWithReturn *notify,
MigrationNotifyFunc func, MigMode mode)
{
- notify->notify = (NotifierWithReturnFunc)func;
- notifier_with_return_list_add(&migration_state_notifiers[mode], notify);
+ migration_add_notifier_modes(notify, func, mode, -1);
}
void migration_add_notifier(NotifierWithReturn *notify,
MigrationNotifyFunc func)
{
- migration_add_notifier_mode(notify, func, MIG_MODE_NORMAL);
+ migration_add_notifier_modes(notify, func, MIG_MODE_NORMAL, -1);
}
void migration_remove_notifier(NotifierWithReturn *notify)
{
if (notify->notify) {
- notifier_with_return_remove(notify);
+ for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
+ migration_blockers[mode] =
+ g_slist_remove(migration_state_notifiers[mode], notify);
+ }
notify->notify = NULL;
}
}
@@ -1691,18 +1726,29 @@ int migration_call_notifiers(MigrationState *s, MigrationEventType type,
{
MigMode mode = s->parameters.mode;
MigrationEvent e;
+ NotifierWithReturn *notifier;
+ GSList *elem, *next;
int ret;
e.type = type;
- ret = notifier_with_return_list_notify(&migration_state_notifiers[mode],
- &e, errp);
- assert(!ret || type == MIG_EVENT_PRECOPY_SETUP);
- return ret;
+
+ for (elem = migration_state_notifiers[mode]; elem; elem = next) {
+ next = elem->next;
+ notifier = (NotifierWithReturn *)elem->data;
+ ret = notifier->notify(notifier, &e, errp);
+ if (ret) {
+ assert(type == MIG_EVENT_PRECOPY_SETUP);
+ return ret;
+ }
+ }
+
+ return 0;
}
bool migration_has_failed(MigrationState *s)
{
- return (s->state == MIGRATION_STATUS_CANCELLED ||
+ return (s->state == MIGRATION_STATUS_CANCELLING ||
+ s->state == MIGRATION_STATUS_CANCELLED ||
s->state == MIGRATION_STATUS_FAILED);
}
@@ -1762,7 +1808,8 @@ bool migrate_mode_is_cpr(MigrationState *s)
{
MigMode mode = s->parameters.mode;
return mode == MIG_MODE_CPR_REBOOT ||
- mode == MIG_MODE_CPR_TRANSFER;
+ mode == MIG_MODE_CPR_TRANSFER ||
+ mode == MIG_MODE_CPR_EXEC;
}
int migrate_init(MigrationState *s, Error **errp)
@@ -2111,6 +2158,12 @@ static bool migrate_prepare(MigrationState *s, bool resume, Error **errp)
return false;
}
+ if (migrate_mode() == MIG_MODE_CPR_EXEC &&
+ !s->parameters.has_cpr_exec_command) {
+ error_setg(errp, "cpr-exec mode requires setting cpr-exec-command");
+ return false;
+ }
+
if (migration_is_blocked(errp)) {
return false;
}
@@ -2646,12 +2699,9 @@ out:
return NULL;
}
-static int open_return_path_on_source(MigrationState *ms)
+static void open_return_path_on_source(MigrationState *ms)
{
ms->rp_state.from_dst_file = qemu_file_get_return_path(ms->to_dst_file);
- if (!ms->rp_state.from_dst_file) {
- return -1;
- }
trace_open_return_path_on_source();
@@ -2660,8 +2710,6 @@ static int open_return_path_on_source(MigrationState *ms)
ms->rp_state.rp_thread_created = true;
trace_open_return_path_on_source_continue();
-
- return 0;
}
/* Return true if error detected, or false otherwise */
@@ -2872,8 +2920,9 @@ static int postcopy_start(MigrationState *ms, Error **errp)
fail_closefb:
qemu_fclose(fb);
fail:
- migrate_set_state(&ms->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
- MIGRATION_STATUS_FAILED);
+ if (ms->state != MIGRATION_STATUS_CANCELLING) {
+ migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILED);
+ }
migration_block_activate(NULL);
migration_call_notifiers(ms, MIG_EVENT_PRECOPY_FAILED, NULL);
bql_unlock();
@@ -4012,10 +4061,7 @@ void migration_connect(MigrationState *s, Error *error_in)
* QEMU uses the return path.
*/
if (migrate_postcopy_ram() || migrate_return_path()) {
- if (open_return_path_on_source(s)) {
- error_setg(&local_err, "Unable to open return-path for postcopy");
- goto fail;
- }
+ open_return_path_on_source(s);
}
/*
diff --git a/migration/multifd.c b/migration/multifd.c
index b255778..98873ce 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -439,6 +439,39 @@ static void multifd_send_set_error(Error *err)
}
}
+/*
+ * Gracefully shutdown IOChannels. Only needed for successful migrations on
+ * top of TLS channels. Otherwise it is same to qio_channel_shutdown().
+ *
+ * A successful migration also guarantees multifd sender threads are
+ * properly flushed and halted. It is only safe to send BYE in the
+ * migration thread here when we know there's no other thread writting to
+ * the channel, because GnuTLS doesn't support concurrent writers.
+ */
+static void migration_ioc_shutdown_gracefully(QIOChannel *ioc)
+{
+ g_autoptr(Error) local_err = NULL;
+
+ if (!migration_has_failed(migrate_get_current()) &&
+ object_dynamic_cast((Object *)ioc, TYPE_QIO_CHANNEL_TLS)) {
+
+ /*
+ * The destination expects the TLS session to always be properly
+ * terminated. This helps to detect a premature termination in the
+ * middle of the stream. Note that older QEMUs always break the
+ * connection on the source and the destination always sees
+ * GNUTLS_E_PREMATURE_TERMINATION.
+ */
+ migration_tls_channel_end(ioc, &local_err);
+ if (local_err) {
+ warn_report("Failed to gracefully terminate TLS connection: %s",
+ error_get_pretty(local_err));
+ }
+ }
+
+ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+}
+
static void multifd_send_terminate_threads(void)
{
int i;
@@ -460,7 +493,7 @@ static void multifd_send_terminate_threads(void)
qemu_sem_post(&p->sem);
if (p->c) {
- qio_channel_shutdown(p->c, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+ migration_ioc_shutdown_gracefully(p->c);
}
}
@@ -547,36 +580,6 @@ void multifd_send_shutdown(void)
return;
}
- for (i = 0; i < migrate_multifd_channels(); i++) {
- MultiFDSendParams *p = &multifd_send_state->params[i];
-
- /* thread_created implies the TLS handshake has succeeded */
- if (p->tls_thread_created && p->thread_created) {
- Error *local_err = NULL;
- /*
- * The destination expects the TLS session to always be
- * properly terminated. This helps to detect a premature
- * termination in the middle of the stream. Note that
- * older QEMUs always break the connection on the source
- * and the destination always sees
- * GNUTLS_E_PREMATURE_TERMINATION.
- */
- migration_tls_channel_end(p->c, &local_err);
-
- /*
- * The above can return an error in case the migration has
- * already failed. If the migration succeeded, errors are
- * not expected but there's no need to kill the source.
- */
- if (local_err && !migration_has_failed(migrate_get_current())) {
- warn_report(
- "multifd_send_%d: Failed to terminate TLS connection: %s",
- p->id, error_get_pretty(local_err));
- break;
- }
- }
- }
-
multifd_send_terminate_threads();
for (i = 0; i < migrate_multifd_channels(); i++) {
diff --git a/migration/options.c b/migration/options.c
index 4e923a2..5183112 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -959,6 +959,9 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
params->zero_page_detection = s->parameters.zero_page_detection;
params->has_direct_io = true;
params->direct_io = s->parameters.direct_io;
+ params->has_cpr_exec_command = true;
+ params->cpr_exec_command = QAPI_CLONE(strList,
+ s->parameters.cpr_exec_command);
return params;
}
@@ -993,6 +996,7 @@ void migrate_params_init(MigrationParameters *params)
params->has_mode = true;
params->has_zero_page_detection = true;
params->has_direct_io = true;
+ params->has_cpr_exec_command = true;
}
/*
@@ -1297,6 +1301,10 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
if (params->has_direct_io) {
dest->direct_io = params->direct_io;
}
+
+ if (params->has_cpr_exec_command) {
+ dest->cpr_exec_command = params->cpr_exec_command;
+ }
}
static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
@@ -1429,6 +1437,12 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
if (params->has_direct_io) {
s->parameters.direct_io = params->direct_io;
}
+
+ if (params->has_cpr_exec_command) {
+ qapi_free_strList(s->parameters.cpr_exec_command);
+ s->parameters.cpr_exec_command =
+ QAPI_CLONE(strList, params->cpr_exec_command);
+ }
}
void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 0172172..5471efb 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -681,6 +681,7 @@ out:
*/
static int init_range(RAMBlock *rb, void *opaque)
{
+ Error **errp = opaque;
const char *block_name = qemu_ram_get_idstr(rb);
void *host_addr = qemu_ram_get_host_addr(rb);
ram_addr_t offset = qemu_ram_get_offset(rb);
@@ -701,6 +702,8 @@ static int init_range(RAMBlock *rb, void *opaque)
* (Precopy will just overwrite this data, so doesn't need the discard)
*/
if (ram_discard_range(block_name, 0, length)) {
+ error_setg(errp, "failed to discard RAM block %s len=%zu",
+ block_name, length);
return -1;
}
@@ -749,9 +752,9 @@ static int cleanup_range(RAMBlock *rb, void *opaque)
* postcopy later; must be called prior to any precopy.
* called from arch_init's similarly named ram_postcopy_incoming_init
*/
-int postcopy_ram_incoming_init(MigrationIncomingState *mis)
+int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp)
{
- if (foreach_not_ignored_block(init_range, NULL)) {
+ if (foreach_not_ignored_block(init_range, errp)) {
return -1;
}
@@ -1703,7 +1706,7 @@ bool postcopy_ram_supported_by_host(MigrationIncomingState *mis, Error **errp)
return false;
}
-int postcopy_ram_incoming_init(MigrationIncomingState *mis)
+int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp)
{
error_report("postcopy_ram_incoming_init: No OS support");
return -1;
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index 3852141..ca19433 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -30,7 +30,7 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis);
* postcopy later; must be called prior to any precopy.
* called from ram.c's similarly named ram_postcopy_incoming_init
*/
-int postcopy_ram_incoming_init(MigrationIncomingState *mis);
+int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp);
/*
* At the end of a migration where postcopy_ram_incoming_init was called.
diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index 0f4280d..2d4ce17 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -125,7 +125,6 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
/*
* Result: QEMUFile* for a 'return path' for comms in the opposite direction
- * NULL if not available
*/
QEMUFile *qemu_file_get_return_path(QEMUFile *f)
{
@@ -349,17 +348,13 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f)
} else {
qio_channel_wait(f->ioc, G_IO_IN);
}
- } else if (len < 0) {
- len = -EIO;
}
} while (len == QIO_CHANNEL_ERR_BLOCK);
if (len > 0) {
f->buf_size += len;
- } else if (len == 0) {
- qemu_file_set_error_obj(f, -EIO, local_error);
} else {
- qemu_file_set_error_obj(f, len, local_error);
+ qemu_file_set_error_obj(f, -EIO, local_error);
}
for (int i = 0; i < nfd; i++) {
diff --git a/migration/ram.c b/migration/ram.c
index 7208bc1..9aac896 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -228,6 +228,7 @@ bool migrate_ram_is_ignored(RAMBlock *block)
MigMode mode = migrate_mode();
return !qemu_ram_is_migratable(block) ||
mode == MIG_MODE_CPR_TRANSFER ||
+ mode == MIG_MODE_CPR_EXEC ||
(migrate_ignore_shared() && qemu_ram_is_shared(block)
&& qemu_ram_is_named_file(block));
}
@@ -3575,8 +3576,10 @@ static void colo_init_ram_state(void)
* colo cache: this is for secondary VM, we cache the whole
* memory of the secondary VM, it is need to hold the global lock
* to call this helper.
+ *
+ * Returns zero to indicate success or -1 on error.
*/
-int colo_init_ram_cache(void)
+int colo_init_ram_cache(Error **errp)
{
RAMBlock *block;
@@ -3585,16 +3588,16 @@ int colo_init_ram_cache(void)
block->colo_cache = qemu_anon_ram_alloc(block->used_length,
NULL, false, false);
if (!block->colo_cache) {
- error_report("%s: Can't alloc memory for COLO cache of block %s,"
- "size 0x" RAM_ADDR_FMT, __func__, block->idstr,
- block->used_length);
+ error_setg(errp, "Can't alloc memory for COLO cache of "
+ "block %s, size 0x" RAM_ADDR_FMT,
+ block->idstr, block->used_length);
RAMBLOCK_FOREACH_NOT_IGNORED(block) {
if (block->colo_cache) {
qemu_anon_ram_free(block->colo_cache, block->used_length);
block->colo_cache = NULL;
}
}
- return -errno;
+ return -1;
}
if (!machine_dump_guest_core(current_machine)) {
qemu_madvise(block->colo_cache, block->used_length,
@@ -3716,9 +3719,9 @@ static int ram_load_cleanup(void *opaque)
* postcopy-ram. postcopy-ram's similarly names
* postcopy_ram_incoming_init does the work.
*/
-int ram_postcopy_incoming_init(MigrationIncomingState *mis)
+int ram_postcopy_incoming_init(MigrationIncomingState *mis, Error **errp)
{
- return postcopy_ram_incoming_init(mis);
+ return postcopy_ram_incoming_init(mis, errp);
}
/**
diff --git a/migration/ram.h b/migration/ram.h
index 921c39a..24cd0bf 100644
--- a/migration/ram.h
+++ b/migration/ram.h
@@ -86,7 +86,7 @@ void ram_postcopy_migrated_memory_release(MigrationState *ms);
void ram_postcopy_send_discard_bitmap(MigrationState *ms);
/* For incoming postcopy discard */
int ram_discard_range(const char *block_name, uint64_t start, size_t length);
-int ram_postcopy_incoming_init(MigrationIncomingState *mis);
+int ram_postcopy_incoming_init(MigrationIncomingState *mis, Error **errp);
int ram_load_postcopy(QEMUFile *f, int channel);
void ram_handle_zero(void *host, uint64_t size);
@@ -109,7 +109,7 @@ void ramblock_set_file_bmap_atomic(RAMBlock *block, ram_addr_t offset,
bool set);
/* ram cache */
-int colo_init_ram_cache(void);
+int colo_init_ram_cache(Error **errp);
void colo_flush_ram_cache(void);
void colo_release_ram_cache(void);
void colo_incoming_start_dirty_log(void);
diff --git a/migration/savevm.c b/migration/savevm.c
index abe0547..7b35ec4 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -963,13 +963,20 @@ void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
}
}
-static int vmstate_load(QEMUFile *f, SaveStateEntry *se)
+static int vmstate_load(QEMUFile *f, SaveStateEntry *se, Error **errp)
{
+ int ret;
trace_vmstate_load(se->idstr, se->vmsd ? se->vmsd->name : "(old)");
if (!se->vmsd) { /* Old style */
- return se->ops->load_state(f, se->opaque, se->load_version_id);
+ ret = se->ops->load_state(f, se->opaque, se->load_version_id);
+ if (ret < 0) {
+ error_setg(errp, "Failed to load vmstate version_id: %d, ret: %d",
+ se->load_version_id, ret);
+ }
+ return ret;
}
- return vmstate_load_state(f, se->vmsd, se->opaque, se->load_version_id);
+ return vmstate_load_state(f, se->vmsd, se->opaque, se->load_version_id,
+ errp);
}
static void vmstate_save_old_style(QEMUFile *f, SaveStateEntry *se,
@@ -1049,8 +1056,8 @@ static int vmstate_save(QEMUFile *f, SaveStateEntry *se, JSONWriter *vmdesc,
if (!se->vmsd) {
vmstate_save_old_style(f, se, vmdesc);
} else {
- ret = vmstate_save_state_with_err(f, se->vmsd, se->opaque, vmdesc,
- errp);
+ ret = vmstate_save_state(f, se->vmsd, se->opaque, vmdesc,
+ errp);
if (ret) {
return ret;
}
@@ -1278,6 +1285,7 @@ void qemu_savevm_state_header(QEMUFile *f)
{
MigrationState *s = migrate_get_current();
JSONWriter *vmdesc = s->vmdesc;
+ Error *local_err = NULL;
trace_savevm_state_header();
qemu_put_be32(f, QEMU_VM_FILE_MAGIC);
@@ -1296,7 +1304,11 @@ void qemu_savevm_state_header(QEMUFile *f)
json_writer_start_object(vmdesc, "configuration");
}
- vmstate_save_state(f, &vmstate_configuration, &savevm_state, vmdesc);
+ vmstate_save_state(f, &vmstate_configuration, &savevm_state,
+ vmdesc, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ }
if (vmdesc) {
json_writer_end_object(vmdesc);
@@ -1905,39 +1917,39 @@ enum LoadVMExitCodes {
* quickly.
*/
static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
- uint16_t len)
+ uint16_t len, Error **errp)
{
PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_ADVISE);
uint64_t remote_pagesize_summary, local_pagesize_summary, remote_tps;
size_t page_size = qemu_target_page_size();
- Error *local_err = NULL;
trace_loadvm_postcopy_handle_advise();
if (ps != POSTCOPY_INCOMING_NONE) {
- error_report("CMD_POSTCOPY_ADVISE in wrong postcopy state (%d)", ps);
+ error_setg(errp, "CMD_POSTCOPY_ADVISE in wrong postcopy state (%d)",
+ ps);
return -1;
}
switch (len) {
case 0:
if (migrate_postcopy_ram()) {
- error_report("RAM postcopy is enabled but have 0 byte advise");
+ error_setg(errp, "RAM postcopy is enabled but have 0 byte advise");
return -EINVAL;
}
return 0;
case 8 + 8:
if (!migrate_postcopy_ram()) {
- error_report("RAM postcopy is disabled but have 16 byte advise");
+ error_setg(errp,
+ "RAM postcopy is disabled but have 16 byte advise");
return -EINVAL;
}
break;
default:
- error_report("CMD_POSTCOPY_ADVISE invalid length (%d)", len);
+ error_setg(errp, "CMD_POSTCOPY_ADVISE invalid length (%d)", len);
return -EINVAL;
}
- if (!postcopy_ram_supported_by_host(mis, &local_err)) {
- error_report_err(local_err);
+ if (!postcopy_ram_supported_by_host(mis, errp)) {
postcopy_state_set(POSTCOPY_INCOMING_NONE);
return -1;
}
@@ -1960,9 +1972,10 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
* also fails when passed to an older qemu that doesn't
* do huge pages.
*/
- error_report("Postcopy needs matching RAM page sizes (s=%" PRIx64
- " d=%" PRIx64 ")",
- remote_pagesize_summary, local_pagesize_summary);
+ error_setg(errp,
+ "Postcopy needs matching RAM page sizes "
+ "(s=%" PRIx64 " d=%" PRIx64 ")",
+ remote_pagesize_summary, local_pagesize_summary);
return -1;
}
@@ -1972,17 +1985,18 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
* Again, some differences could be dealt with, but for now keep it
* simple.
*/
- error_report("Postcopy needs matching target page sizes (s=%d d=%zd)",
- (int)remote_tps, page_size);
+ error_setg(errp,
+ "Postcopy needs matching target page sizes (s=%d d=%zd)",
+ (int)remote_tps, page_size);
return -1;
}
- if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_ADVISE, &local_err)) {
- error_report_err(local_err);
+ if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_ADVISE, errp)) {
return -1;
}
- if (ram_postcopy_incoming_init(mis)) {
+ if (ram_postcopy_incoming_init(mis, errp) < 0) {
+ error_prepend(errp, "Postcopy RAM incoming init failed: ");
return -1;
}
@@ -1995,7 +2009,7 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
* There can be 0..many of these messages, each encoding multiple pages.
*/
static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
- uint16_t len)
+ uint16_t len, Error **errp)
{
int tmp;
char ramid[256];
@@ -2008,6 +2022,7 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
/* 1st discard */
tmp = postcopy_ram_prepare_discard(mis);
if (tmp) {
+ error_setg(errp, "Failed to prepare for RAM discard: %d", tmp);
return tmp;
}
break;
@@ -2017,8 +2032,9 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
break;
default:
- error_report("CMD_POSTCOPY_RAM_DISCARD in wrong postcopy state (%d)",
- ps);
+ error_setg(errp,
+ "CMD_POSTCOPY_RAM_DISCARD in wrong postcopy state (%d)",
+ ps);
return -1;
}
/* We're expecting a
@@ -2027,29 +2043,30 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
* then at least 1 16 byte chunk
*/
if (len < (1 + 1 + 1 + 1 + 2 * 8)) {
- error_report("CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
+ error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
return -1;
}
tmp = qemu_get_byte(mis->from_src_file);
if (tmp != postcopy_ram_discard_version) {
- error_report("CMD_POSTCOPY_RAM_DISCARD invalid version (%d)", tmp);
+ error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid version (%d)", tmp);
return -1;
}
if (!qemu_get_counted_string(mis->from_src_file, ramid)) {
- error_report("CMD_POSTCOPY_RAM_DISCARD Failed to read RAMBlock ID");
+ error_setg(errp,
+ "CMD_POSTCOPY_RAM_DISCARD Failed to read RAMBlock ID");
return -1;
}
tmp = qemu_get_byte(mis->from_src_file);
if (tmp != 0) {
- error_report("CMD_POSTCOPY_RAM_DISCARD missing nil (%d)", tmp);
+ error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD missing nil (%d)", tmp);
return -1;
}
len -= 3 + strlen(ramid);
if (len % 16) {
- error_report("CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
+ error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
return -1;
}
trace_loadvm_postcopy_ram_handle_discard_header(ramid, len);
@@ -2061,6 +2078,7 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
len -= 16;
int ret = ram_discard_range(ramid, start_addr, block_length);
if (ret) {
+ error_setg(errp, "Failed to discard RAM range %s: %d", ramid, ret);
return ret;
}
}
@@ -2082,6 +2100,7 @@ static void *postcopy_ram_listen_thread(void *opaque)
QEMUFile *f = mis->from_src_file;
int load_res;
MigrationState *migr = migrate_get_current();
+ Error *local_err = NULL;
object_ref(OBJECT(migr));
@@ -2098,7 +2117,7 @@ static void *postcopy_ram_listen_thread(void *opaque)
qemu_file_set_blocking(f, true, &error_fatal);
/* TODO: sanity check that only postcopiable data will be loaded here */
- load_res = qemu_loadvm_state_main(f, mis);
+ load_res = qemu_loadvm_state_main(f, mis, &local_err);
/*
* This is tricky, but, mis->from_src_file can change after it
@@ -2124,7 +2143,10 @@ static void *postcopy_ram_listen_thread(void *opaque)
__func__, load_res);
load_res = 0; /* prevent further exit() */
} else {
- error_report("%s: loadvm failed: %d", __func__, load_res);
+ error_prepend(&local_err,
+ "loadvm failed during postcopy: %d: ", load_res);
+ migrate_set_error(migr, local_err);
+ error_report_err(local_err);
migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
MIGRATION_STATUS_FAILED);
}
@@ -2172,15 +2194,16 @@ static void *postcopy_ram_listen_thread(void *opaque)
}
/* After this message we must be able to immediately receive postcopy data */
-static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
+static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis,
+ Error **errp)
{
PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
- Error *local_err = NULL;
trace_loadvm_postcopy_handle_listen("enter");
if (ps != POSTCOPY_INCOMING_ADVISE && ps != POSTCOPY_INCOMING_DISCARD) {
- error_report("CMD_POSTCOPY_LISTEN in wrong postcopy state (%d)", ps);
+ error_setg(errp,
+ "CMD_POSTCOPY_LISTEN in wrong postcopy state (%d)", ps);
return -1;
}
if (ps == POSTCOPY_INCOMING_ADVISE) {
@@ -2203,14 +2226,14 @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
if (migrate_postcopy_ram()) {
if (postcopy_ram_incoming_setup(mis)) {
postcopy_ram_incoming_cleanup(mis);
+ error_setg(errp, "Failed to setup incoming postcopy RAM blocks");
return -1;
}
}
trace_loadvm_postcopy_handle_listen("after uffd");
- if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, &local_err)) {
- error_report_err(local_err);
+ if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, errp)) {
return -1;
}
@@ -2263,13 +2286,13 @@ static void loadvm_postcopy_handle_run_bh(void *opaque)
}
/* After all discards we can start running and asking for pages */
-static int loadvm_postcopy_handle_run(MigrationIncomingState *mis)
+static int loadvm_postcopy_handle_run(MigrationIncomingState *mis, Error **errp)
{
PostcopyState ps = postcopy_state_get();
trace_loadvm_postcopy_handle_run();
if (ps != POSTCOPY_INCOMING_LISTENING) {
- error_report("CMD_POSTCOPY_RUN in wrong postcopy state (%d)", ps);
+ error_setg(errp, "CMD_POSTCOPY_RUN in wrong postcopy state (%d)", ps);
return -1;
}
@@ -2327,12 +2350,12 @@ static void migrate_send_rp_req_pages_pending(MigrationIncomingState *mis)
}
}
-static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
+static void loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
{
if (mis->state != MIGRATION_STATUS_POSTCOPY_RECOVER) {
- error_report("%s: illegal resume received", __func__);
+ warn_report("%s: illegal resume received", __func__);
/* Don't fail the load, only for this. */
- return 0;
+ return;
}
/*
@@ -2384,8 +2407,6 @@ static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
/* Kick the fast ram load thread too */
qemu_sem_post(&mis->postcopy_pause_sem_fast_load);
}
-
- return 0;
}
/**
@@ -2398,7 +2419,7 @@ static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
* Returns: Negative values on error
*
*/
-static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
+static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis, Error **errp)
{
int ret;
size_t length;
@@ -2408,7 +2429,7 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
trace_loadvm_handle_cmd_packaged(length);
if (length > MAX_VM_CMD_PACKAGED_SIZE) {
- error_report("Unreasonably large packaged state: %zu", length);
+ error_setg(errp, "Unreasonably large packaged state: %zu", length);
return -1;
}
@@ -2419,8 +2440,8 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
length);
if (ret != length) {
object_unref(OBJECT(bioc));
- error_report("CMD_PACKAGED: Buffer receive fail ret=%d length=%zu",
- ret, length);
+ error_setg(errp, "CMD_PACKAGED: Buffer receive fail ret=%d length=%zu",
+ ret, length);
return (ret < 0) ? ret : -EAGAIN;
}
bioc->usage += length;
@@ -2449,7 +2470,7 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
qemu_coroutine_yield();
} while (1);
- ret = qemu_loadvm_state_main(packf, mis);
+ ret = qemu_loadvm_state_main(packf, mis, errp);
trace_loadvm_handle_cmd_packaged_main(ret);
qemu_fclose(packf);
object_unref(OBJECT(bioc));
@@ -2464,32 +2485,35 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
* len (1 byte) + ramblock_name (<255 bytes)
*/
static int loadvm_handle_recv_bitmap(MigrationIncomingState *mis,
- uint16_t len)
+ uint16_t len, Error **errp)
{
QEMUFile *file = mis->from_src_file;
RAMBlock *rb;
char block_name[256];
size_t cnt;
+ int ret;
cnt = qemu_get_counted_string(file, block_name);
if (!cnt) {
- error_report("%s: failed to read block name", __func__);
+ error_setg(errp, "failed to read block name");
return -EINVAL;
}
/* Validate before using the data */
- if (qemu_file_get_error(file)) {
- return qemu_file_get_error(file);
+ ret = qemu_file_get_error(file);
+ if (ret < 0) {
+ error_setg(errp, "loadvm failed: stream error: %d", ret);
+ return ret;
}
if (len != cnt + 1) {
- error_report("%s: invalid payload length (%d)", __func__, len);
+ error_setg(errp, "invalid payload length (%d)", len);
return -EINVAL;
}
rb = qemu_ram_block_by_name(block_name);
if (!rb) {
- error_report("%s: block '%s' not found", __func__, block_name);
+ error_setg(errp, "block '%s' not found", block_name);
return -EINVAL;
}
@@ -2500,20 +2524,26 @@ static int loadvm_handle_recv_bitmap(MigrationIncomingState *mis,
return 0;
}
-static int loadvm_process_enable_colo(MigrationIncomingState *mis)
+static int loadvm_process_enable_colo(MigrationIncomingState *mis,
+ Error **errp)
{
- int ret = migration_incoming_enable_colo();
+ ERRP_GUARD();
+ int ret;
- if (!ret) {
- ret = colo_init_ram_cache();
- if (ret) {
- migration_incoming_disable_colo();
- }
+ ret = migration_incoming_enable_colo(errp);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = colo_init_ram_cache(errp);
+ if (ret) {
+ error_prepend(errp, "failed to init colo RAM cache: %d: ", ret);
+ migration_incoming_disable_colo();
}
return ret;
}
-static int loadvm_postcopy_handle_switchover_start(void)
+static int loadvm_postcopy_handle_switchover_start(Error **errp)
{
SaveStateEntry *se;
@@ -2526,6 +2556,7 @@ static int loadvm_postcopy_handle_switchover_start(void)
ret = se->ops->switchover_start(se->opaque);
if (ret < 0) {
+ error_setg(errp, "Switchover start failed: %d", ret);
return ret;
}
}
@@ -2539,32 +2570,37 @@ static int loadvm_postcopy_handle_switchover_start(void)
* LOADVM_QUIT All good, but exit the loop
* <0 Error
*/
-static int loadvm_process_command(QEMUFile *f)
+static int loadvm_process_command(QEMUFile *f, Error **errp)
{
MigrationIncomingState *mis = migration_incoming_get_current();
uint16_t cmd;
uint16_t len;
uint32_t tmp32;
+ int ret;
cmd = qemu_get_be16(f);
len = qemu_get_be16(f);
/* Check validity before continue processing of cmds */
- if (qemu_file_get_error(f)) {
- return qemu_file_get_error(f);
+ ret = qemu_file_get_error(f);
+ if (ret) {
+ error_setg(errp,
+ "Failed to load VM process command: stream error: %d",
+ ret);
+ return ret;
}
if (cmd >= MIG_CMD_MAX || cmd == MIG_CMD_INVALID) {
- error_report("MIG_CMD 0x%x unknown (len 0x%x)", cmd, len);
+ error_setg(errp, "MIG_CMD 0x%x unknown (len 0x%x)", cmd, len);
return -EINVAL;
}
trace_loadvm_process_command(mig_cmd_args[cmd].name, len);
if (mig_cmd_args[cmd].len != -1 && mig_cmd_args[cmd].len != len) {
- error_report("%s received with bad length - expecting %zu, got %d",
- mig_cmd_args[cmd].name,
- (size_t)mig_cmd_args[cmd].len, len);
+ error_setg(errp, "%s received with bad length - expecting %zu, got %d",
+ mig_cmd_args[cmd].name,
+ (size_t)mig_cmd_args[cmd].len, len);
return -ERANGE;
}
@@ -2576,10 +2612,6 @@ static int loadvm_process_command(QEMUFile *f)
return 0;
}
mis->to_src_file = qemu_file_get_return_path(f);
- if (!mis->to_src_file) {
- error_report("CMD_OPEN_RETURN_PATH failed");
- return -1;
- }
/*
* Switchover ack is enabled but no device uses it, so send an ACK to
@@ -2587,11 +2619,10 @@ static int loadvm_process_command(QEMUFile *f)
* been created.
*/
if (migrate_switchover_ack() && !mis->switchover_ack_pending_num) {
- int ret = migrate_send_rp_switchover_ack(mis);
+ ret = migrate_send_rp_switchover_ack(mis);
if (ret) {
- error_report(
- "Could not send switchover ack RP MSG, err %d (%s)", ret,
- strerror(-ret));
+ error_setg_errno(errp, -ret,
+ "Could not send switchover ack RP MSG");
return ret;
}
}
@@ -2601,39 +2632,40 @@ static int loadvm_process_command(QEMUFile *f)
tmp32 = qemu_get_be32(f);
trace_loadvm_process_command_ping(tmp32);
if (!mis->to_src_file) {
- error_report("CMD_PING (0x%x) received with no return path",
- tmp32);
+ error_setg(errp, "CMD_PING (0x%x) received with no return path",
+ tmp32);
return -1;
}
migrate_send_rp_pong(mis, tmp32);
break;
case MIG_CMD_PACKAGED:
- return loadvm_handle_cmd_packaged(mis);
+ return loadvm_handle_cmd_packaged(mis, errp);
case MIG_CMD_POSTCOPY_ADVISE:
- return loadvm_postcopy_handle_advise(mis, len);
+ return loadvm_postcopy_handle_advise(mis, len, errp);
case MIG_CMD_POSTCOPY_LISTEN:
- return loadvm_postcopy_handle_listen(mis);
+ return loadvm_postcopy_handle_listen(mis, errp);
case MIG_CMD_POSTCOPY_RUN:
- return loadvm_postcopy_handle_run(mis);
+ return loadvm_postcopy_handle_run(mis, errp);
case MIG_CMD_POSTCOPY_RAM_DISCARD:
- return loadvm_postcopy_ram_handle_discard(mis, len);
+ return loadvm_postcopy_ram_handle_discard(mis, len, errp);
case MIG_CMD_POSTCOPY_RESUME:
- return loadvm_postcopy_handle_resume(mis);
+ loadvm_postcopy_handle_resume(mis);
+ return 0;
case MIG_CMD_RECV_BITMAP:
- return loadvm_handle_recv_bitmap(mis, len);
+ return loadvm_handle_recv_bitmap(mis, len, errp);
case MIG_CMD_ENABLE_COLO:
- return loadvm_process_enable_colo(mis);
+ return loadvm_process_enable_colo(mis, errp);
case MIG_CMD_SWITCHOVER_START:
- return loadvm_postcopy_handle_switchover_start();
+ return loadvm_postcopy_handle_switchover_start(errp);
}
return 0;
@@ -2683,8 +2715,9 @@ static bool check_section_footer(QEMUFile *f, SaveStateEntry *se)
}
static int
-qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
+qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type, Error **errp)
{
+ ERRP_GUARD();
bool trace_downtime = (type == QEMU_VM_SECTION_FULL);
uint32_t instance_id, version_id, section_id;
int64_t start_ts, end_ts;
@@ -2695,8 +2728,8 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
/* Read section start */
section_id = qemu_get_be32(f);
if (!qemu_get_counted_string(f, idstr)) {
- error_report("Unable to read ID string for section %u",
- section_id);
+ error_setg(errp, "Unable to read ID string for section %u",
+ section_id);
return -EINVAL;
}
instance_id = qemu_get_be32(f);
@@ -2704,8 +2737,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
ret = qemu_file_get_error(f);
if (ret) {
- error_report("%s: Failed to read instance/version ID: %d",
- __func__, ret);
+ error_setg(errp, "Failed to read instance/version ID: %d", ret);
return ret;
}
@@ -2714,17 +2746,17 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
/* Find savevm section */
se = find_se(idstr, instance_id);
if (se == NULL) {
- error_report("Unknown savevm section or instance '%s' %"PRIu32". "
- "Make sure that your current VM setup matches your "
- "saved VM setup, including any hotplugged devices",
- idstr, instance_id);
+ error_setg(errp, "Unknown section or instance '%s' %"PRIu32". "
+ "Make sure that your current VM setup matches your "
+ "saved VM setup, including any hotplugged devices",
+ idstr, instance_id);
return -EINVAL;
}
/* Validate version */
if (version_id > se->version_id) {
- error_report("savevm: unsupported version %d for '%s' v%d",
- version_id, idstr, se->version_id);
+ error_setg(errp, "unsupported version %d for '%s' v%d",
+ version_id, idstr, se->version_id);
return -EINVAL;
}
se->load_version_id = version_id;
@@ -2732,7 +2764,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
/* Validate if it is a device's state */
if (xen_enabled() && se->is_ram) {
- error_report("loadvm: %s RAM loading not allowed on Xen", idstr);
+ error_setg(errp, "loadvm: %s RAM loading not allowed on Xen", idstr);
return -EINVAL;
}
@@ -2740,10 +2772,11 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
start_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
}
- ret = vmstate_load(f, se);
+ ret = vmstate_load(f, se, errp);
if (ret < 0) {
- error_report("error while loading state for instance 0x%"PRIx32" of"
- " device '%s'", instance_id, idstr);
+ error_prepend(errp,
+ "error while loading state for instance 0x%"PRIx32" of"
+ " device '%s': ", instance_id, idstr);
return ret;
}
@@ -2754,6 +2787,8 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
}
if (!check_section_footer(f, se)) {
+ error_setg(errp, "Section footer error, section_id: %d",
+ section_id);
return -EINVAL;
}
@@ -2761,7 +2796,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
}
static int
-qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
+qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type, Error **errp)
{
bool trace_downtime = (type == QEMU_VM_SECTION_END);
int64_t start_ts, end_ts;
@@ -2773,8 +2808,7 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
ret = qemu_file_get_error(f);
if (ret) {
- error_report("%s: Failed to read section ID: %d",
- __func__, ret);
+ error_setg(errp, "Failed to read section ID: %d", ret);
return ret;
}
@@ -2785,7 +2819,7 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
}
}
if (se == NULL) {
- error_report("Unknown savevm section %d", section_id);
+ error_setg(errp, "Unknown section %d", section_id);
return -EINVAL;
}
@@ -2793,10 +2827,8 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
start_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
}
- ret = vmstate_load(f, se);
+ ret = vmstate_load(f, se, errp);
if (ret < 0) {
- error_report("error while loading state section id %d(%s)",
- section_id, se->idstr);
return ret;
}
@@ -2807,40 +2839,50 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
}
if (!check_section_footer(f, se)) {
+ error_setg(errp, "Section footer error, section_id: %d",
+ section_id);
return -EINVAL;
}
return 0;
}
-static int qemu_loadvm_state_header(QEMUFile *f)
+static int qemu_loadvm_state_header(QEMUFile *f, Error **errp)
{
unsigned int v;
int ret;
v = qemu_get_be32(f);
if (v != QEMU_VM_FILE_MAGIC) {
- error_report("Not a migration stream");
+ error_setg(errp, "Not a migration stream, magic: %x != %x",
+ v, QEMU_VM_FILE_MAGIC);
return -EINVAL;
}
v = qemu_get_be32(f);
if (v == QEMU_VM_FILE_VERSION_COMPAT) {
- error_report("SaveVM v2 format is obsolete and don't work anymore");
+ error_setg(errp,
+ "SaveVM v2 format is obsolete and no longer supported");
+
return -ENOTSUP;
}
if (v != QEMU_VM_FILE_VERSION) {
- error_report("Unsupported migration stream version");
+ error_setg(errp, "Unsupported migration stream version, "
+ "file version %x != %x",
+ v, QEMU_VM_FILE_VERSION);
return -ENOTSUP;
}
if (migrate_get_current()->send_configuration) {
- if (qemu_get_byte(f) != QEMU_VM_CONFIGURATION) {
- error_report("Configuration section missing");
+ v = qemu_get_byte(f);
+ if (v != QEMU_VM_CONFIGURATION) {
+ error_setg(errp, "Configuration section missing, %x != %x",
+ v, QEMU_VM_CONFIGURATION);
return -EINVAL;
}
- ret = vmstate_load_state(f, &vmstate_configuration, &savevm_state, 0);
+ ret = vmstate_load_state(f, &vmstate_configuration, &savevm_state, 0,
+ errp);
if (ret) {
return ret;
}
@@ -3028,8 +3070,10 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
return true;
}
-int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis)
+int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
+ Error **errp)
{
+ ERRP_GUARD();
uint8_t section_type;
int ret = 0;
@@ -3037,8 +3081,11 @@ retry:
while (true) {
section_type = qemu_get_byte(f);
- ret = qemu_file_get_error_obj_any(f, mis->postcopy_qemufile_dst, NULL);
+ ret = qemu_file_get_error_obj_any(f, mis->postcopy_qemufile_dst, errp);
if (ret) {
+ error_prepend(errp,
+ "Failed to load section ID: stream error: %d: ",
+ ret);
break;
}
@@ -3046,20 +3093,20 @@ retry:
switch (section_type) {
case QEMU_VM_SECTION_START:
case QEMU_VM_SECTION_FULL:
- ret = qemu_loadvm_section_start_full(f, section_type);
+ ret = qemu_loadvm_section_start_full(f, section_type, errp);
if (ret < 0) {
goto out;
}
break;
case QEMU_VM_SECTION_PART:
case QEMU_VM_SECTION_END:
- ret = qemu_loadvm_section_part_end(f, section_type);
+ ret = qemu_loadvm_section_part_end(f, section_type, errp);
if (ret < 0) {
goto out;
}
break;
case QEMU_VM_COMMAND:
- ret = loadvm_process_command(f);
+ ret = loadvm_process_command(f, errp);
trace_qemu_loadvm_state_section_command(ret);
if ((ret < 0) || (ret == LOADVM_QUIT)) {
goto out;
@@ -3069,7 +3116,7 @@ retry:
/* This is the end of migration */
goto out;
default:
- error_report("Unknown savevm section type %d", section_type);
+ error_setg(errp, "Unknown section type %d", section_type);
ret = -EINVAL;
goto out;
}
@@ -3097,33 +3144,31 @@ out:
migrate_postcopy_ram() && postcopy_pause_incoming(mis)) {
/* Reset f to point to the newly created channel */
f = mis->from_src_file;
+ error_free_or_abort(errp);
goto retry;
}
}
return ret;
}
-int qemu_loadvm_state(QEMUFile *f)
+int qemu_loadvm_state(QEMUFile *f, Error **errp)
{
MigrationState *s = migrate_get_current();
MigrationIncomingState *mis = migration_incoming_get_current();
- Error *local_err = NULL;
int ret;
- if (qemu_savevm_state_blocked(&local_err)) {
- error_report_err(local_err);
+ if (qemu_savevm_state_blocked(errp)) {
return -EINVAL;
}
qemu_loadvm_thread_pool_create(mis);
- ret = qemu_loadvm_state_header(f);
+ ret = qemu_loadvm_state_header(f, errp);
if (ret) {
return ret;
}
- if (qemu_loadvm_state_setup(f, &local_err) != 0) {
- error_report_err(local_err);
+ if (qemu_loadvm_state_setup(f, errp) != 0) {
return -EINVAL;
}
@@ -3133,7 +3178,7 @@ int qemu_loadvm_state(QEMUFile *f)
cpu_synchronize_all_pre_loadvm();
- ret = qemu_loadvm_state_main(f, mis);
+ ret = qemu_loadvm_state_main(f, mis, errp);
qemu_event_set(&mis->main_thread_load_event);
trace_qemu_loadvm_state_post_main(ret);
@@ -3151,8 +3196,15 @@ int qemu_loadvm_state(QEMUFile *f)
if (migrate_has_error(migrate_get_current()) ||
!qemu_loadvm_thread_pool_wait(s, mis)) {
ret = -EINVAL;
+ error_setg(errp,
+ "Error while loading vmstate");
} else {
ret = qemu_file_get_error(f);
+ if (ret < 0) {
+ error_setg(errp,
+ "Error while loading vmstate: stream error: %d",
+ ret);
+ }
}
}
/*
@@ -3201,15 +3253,14 @@ int qemu_loadvm_state(QEMUFile *f)
return ret;
}
-int qemu_load_device_state(QEMUFile *f)
+int qemu_load_device_state(QEMUFile *f, Error **errp)
{
MigrationIncomingState *mis = migration_incoming_get_current();
int ret;
/* Load QEMU_VM_SECTION_FULL section */
- ret = qemu_loadvm_state_main(f, mis);
+ ret = qemu_loadvm_state_main(f, mis, errp);
if (ret < 0) {
- error_report("Failed to load device state: %d", ret);
return ret;
}
@@ -3417,6 +3468,7 @@ void qmp_xen_save_devices_state(const char *filename, bool has_live, bool live,
void qmp_xen_load_devices_state(const char *filename, Error **errp)
{
+ ERRP_GUARD();
QEMUFile *f;
QIOChannelFile *ioc;
int ret;
@@ -3438,10 +3490,10 @@ void qmp_xen_load_devices_state(const char *filename, Error **errp)
f = qemu_file_new_input(QIO_CHANNEL(ioc));
object_unref(OBJECT(ioc));
- ret = qemu_loadvm_state(f);
+ ret = qemu_loadvm_state(f, errp);
qemu_fclose(f);
if (ret < 0) {
- error_setg(errp, "loading Xen device state failed");
+ error_prepend(errp, "loading Xen device state failed: ");
}
migration_incoming_state_destroy();
}
@@ -3512,13 +3564,12 @@ bool load_snapshot(const char *name, const char *vmstate,
ret = -EINVAL;
goto err_drain;
}
- ret = qemu_loadvm_state(f);
+ ret = qemu_loadvm_state(f, errp);
migration_incoming_state_destroy();
bdrv_drain_all_end();
if (ret < 0) {
- error_setg(errp, "Error %d while loading VM state", ret);
return false;
}
diff --git a/migration/savevm.h b/migration/savevm.h
index 2d5e9c7..c337e3e 100644
--- a/migration/savevm.h
+++ b/migration/savevm.h
@@ -64,10 +64,11 @@ void qemu_savevm_send_colo_enable(QEMUFile *f);
void qemu_savevm_live_state(QEMUFile *f);
int qemu_save_device_state(QEMUFile *f);
-int qemu_loadvm_state(QEMUFile *f);
+int qemu_loadvm_state(QEMUFile *f, Error **errp);
void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
-int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis);
-int qemu_load_device_state(QEMUFile *f);
+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_complete_precopy_non_iterable(QEMUFile *f,
bool in_postcopy);
diff --git a/migration/trace-events b/migration/trace-events
index 706db97..e8edd1f 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -354,6 +354,7 @@ cpr_state_save(const char *mode) "%s mode"
cpr_state_load(const char *mode) "%s mode"
cpr_transfer_input(const char *path) "%s"
cpr_transfer_output(const char *path) "%s"
+cpr_exec(void) ""
# block-dirty-bitmap.c
send_bitmap_header_enter(void) ""
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index 741a588..4b01dc1 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -19,6 +19,7 @@
#include "qemu/error-report.h"
#include "qemu/queue.h"
#include "trace.h"
+#include "qapi/error.h"
/* bool */
@@ -321,6 +322,10 @@ static int get_fd(QEMUFile *f, void *pv, size_t size,
const VMStateField *field)
{
int32_t *v = pv;
+ if (migrate_mode() == MIG_MODE_CPR_EXEC) {
+ qemu_get_sbe32s(f, v);
+ return 0;
+ }
*v = qemu_file_get_fd(f);
return 0;
}
@@ -329,6 +334,10 @@ static int put_fd(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, JSONWriter *vmdesc)
{
int32_t *v = pv;
+ if (migrate_mode() == MIG_MODE_CPR_EXEC) {
+ qemu_put_sbe32s(f, v);
+ return 0;
+ }
return qemu_file_put_fd(f, *v);
}
@@ -543,13 +552,17 @@ static int get_tmp(QEMUFile *f, void *pv, size_t size,
const VMStateField *field)
{
int ret;
+ Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
int version_id = field->version_id;
void *tmp = g_malloc(size);
/* Writes the parent field which is at the start of the tmp */
*(void **)tmp = pv;
- ret = vmstate_load_state(f, vmsd, tmp, version_id);
+ ret = vmstate_load_state(f, vmsd, tmp, version_id, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
g_free(tmp);
return ret;
}
@@ -560,10 +573,14 @@ static int put_tmp(QEMUFile *f, void *pv, size_t size,
const VMStateDescription *vmsd = field->vmsd;
void *tmp = g_malloc(size);
int ret;
+ Error *local_err = NULL;
/* Writes the parent field which is at the start of the tmp */
*(void **)tmp = pv;
- ret = vmstate_save_state(f, vmsd, tmp, vmdesc);
+ ret = vmstate_save_state(f, vmsd, tmp, vmdesc, &local_err);
+ if (ret) {
+ error_report_err(local_err);
+ }
g_free(tmp);
return ret;
@@ -626,6 +643,7 @@ static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
const VMStateField *field)
{
int ret = 0;
+ Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
/* size of a QTAILQ element */
size_t size = field->size;
@@ -649,8 +667,9 @@ static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
while (qemu_get_byte(f)) {
elm = g_malloc(size);
- ret = vmstate_load_state(f, vmsd, elm, version_id);
+ ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
QTAILQ_RAW_INSERT_TAIL(pv, elm, entry_offset);
@@ -669,13 +688,15 @@ static int put_qtailq(QEMUFile *f, void *pv, size_t unused_size,
size_t entry_offset = field->start;
void *elm;
int ret;
+ Error *local_err = NULL;
trace_put_qtailq(vmsd->name, vmsd->version_id);
QTAILQ_RAW_FOREACH(elm, pv, entry_offset) {
qemu_put_byte(f, true);
- ret = vmstate_save_state(f, vmsd, elm, vmdesc);
+ ret = vmstate_save_state(f, vmsd, elm, vmdesc, &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
}
@@ -704,6 +725,7 @@ static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data)
struct put_gtree_data *capsule = (struct put_gtree_data *)data;
QEMUFile *f = capsule->f;
int ret;
+ Error *local_err = NULL;
qemu_put_byte(f, true);
@@ -711,16 +733,20 @@ static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data)
if (!capsule->key_vmsd) {
qemu_put_be64(f, (uint64_t)(uintptr_t)(key)); /* direct key */
} else {
- ret = vmstate_save_state(f, capsule->key_vmsd, key, capsule->vmdesc);
+ ret = vmstate_save_state(f, capsule->key_vmsd, key, capsule->vmdesc,
+ &local_err);
if (ret) {
+ error_report_err(local_err);
capsule->ret = ret;
return true;
}
}
/* put the data */
- ret = vmstate_save_state(f, capsule->val_vmsd, value, capsule->vmdesc);
+ ret = vmstate_save_state(f, capsule->val_vmsd, value, capsule->vmdesc,
+ &local_err);
if (ret) {
+ error_report_err(local_err);
capsule->ret = ret;
return true;
}
@@ -772,6 +798,7 @@ static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
GTree *tree = *pval;
void *key, *val;
int ret = 0;
+ Error *local_err = NULL;
/* in case of direct key, the key vmsd can be {}, ie. check fields */
if (!direct_key && version_id > key_vmsd->version_id) {
@@ -803,18 +830,16 @@ static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
key = (void *)(uintptr_t)qemu_get_be64(f);
} else {
key = g_malloc0(key_size);
- ret = vmstate_load_state(f, key_vmsd, key, version_id);
+ ret = vmstate_load_state(f, key_vmsd, key, version_id, &local_err);
if (ret) {
- error_report("%s : failed to load %s (%d)",
- field->name, key_vmsd->name, ret);
+ error_report_err(local_err);
goto key_error;
}
}
val = g_malloc0(val_size);
- ret = vmstate_load_state(f, val_vmsd, val, version_id);
+ ret = vmstate_load_state(f, val_vmsd, val, version_id, &local_err);
if (ret) {
- error_report("%s : failed to load %s (%d)",
- field->name, val_vmsd->name, ret);
+ error_report_err(local_err);
goto val_error;
}
g_tree_insert(tree, key, val);
@@ -851,14 +876,14 @@ static int put_qlist(QEMUFile *f, void *pv, size_t unused_size,
size_t entry_offset = field->start;
void *elm;
int ret;
+ Error *local_err = NULL;
trace_put_qlist(field->name, vmsd->name, vmsd->version_id);
QLIST_RAW_FOREACH(elm, pv, entry_offset) {
qemu_put_byte(f, true);
- ret = vmstate_save_state(f, vmsd, elm, vmdesc);
+ ret = vmstate_save_state(f, vmsd, elm, vmdesc, &local_err);
if (ret) {
- error_report("%s: failed to save %s (%d)", field->name,
- vmsd->name, ret);
+ error_report_err(local_err);
return ret;
}
}
@@ -872,6 +897,7 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
const VMStateField *field)
{
int ret = 0;
+ Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
/* size of a QLIST element */
size_t size = field->size;
@@ -892,10 +918,9 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
while (qemu_get_byte(f)) {
elm = g_malloc(size);
- ret = vmstate_load_state(f, vmsd, elm, version_id);
+ ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
if (ret) {
- error_report("%s: failed to load %s (%d)", field->name,
- vmsd->name, ret);
+ error_report_err(local_err);
g_free(elm);
return ret;
}
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 5feaa32..81eadde 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -25,7 +25,7 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
Error **errp);
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque);
+ void *opaque, Error **errp);
/* Whether this field should exist for either save or load the VM? */
static bool
@@ -132,29 +132,43 @@ static void vmstate_handle_alloc(void *ptr, const VMStateField *field,
}
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, int version_id)
+ void *opaque, int version_id, Error **errp)
{
+ ERRP_GUARD();
const VMStateField *field = vmsd->fields;
int ret = 0;
trace_vmstate_load_state(vmsd->name, version_id);
if (version_id > vmsd->version_id) {
- error_report("%s: incoming version_id %d is too new "
- "for local version_id %d",
- vmsd->name, version_id, vmsd->version_id);
+ error_setg(errp, "%s: incoming version_id %d is too new "
+ "for local version_id %d",
+ vmsd->name, version_id, vmsd->version_id);
trace_vmstate_load_state_end(vmsd->name, "too new", -EINVAL);
return -EINVAL;
}
if (version_id < vmsd->minimum_version_id) {
- error_report("%s: incoming version_id %d is too old "
- "for local minimum version_id %d",
- vmsd->name, version_id, vmsd->minimum_version_id);
+ error_setg(errp, "%s: incoming version_id %d is too old "
+ "for local minimum version_id %d",
+ vmsd->name, version_id, vmsd->minimum_version_id);
trace_vmstate_load_state_end(vmsd->name, "too old", -EINVAL);
return -EINVAL;
}
- if (vmsd->pre_load) {
+ if (vmsd->pre_load_errp) {
+ ret = vmsd->pre_load_errp(opaque, errp);
+ if (ret < 0) {
+ error_prepend(errp, "pre load hook failed for: '%s', "
+ "version_id: %d, minimum version_id: %d, "
+ "ret: %d: ", vmsd->name, vmsd->version_id,
+ vmsd->minimum_version_id, ret);
+ return ret;
+ }
+ } else if (vmsd->pre_load) {
ret = vmsd->pre_load(opaque);
if (ret) {
+ error_setg(errp, "pre load hook failed for: '%s', "
+ "version_id: %d, minimum version_id: %d, ret: %d",
+ vmsd->name, vmsd->version_id, vmsd->minimum_version_id,
+ ret);
return ret;
}
}
@@ -192,13 +206,21 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
if (inner_field->flags & VMS_STRUCT) {
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
- inner_field->vmsd->version_id);
+ inner_field->vmsd->version_id,
+ errp);
} else if (inner_field->flags & VMS_VSTRUCT) {
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
- inner_field->struct_version_id);
+ inner_field->struct_version_id,
+ errp);
} else {
ret = inner_field->info->get(f, curr_elem, size,
inner_field);
+ if (ret < 0) {
+ error_setg(errp,
+ "Failed to load element of type %s for %s: "
+ "%d", inner_field->info->name,
+ inner_field->name, ret);
+ }
}
/* If we used a fake temp field.. free it now */
@@ -208,30 +230,47 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
if (ret >= 0) {
ret = qemu_file_get_error(f);
+ if (ret < 0) {
+ error_setg(errp,
+ "Failed to load %s state: stream error: %d",
+ vmsd->name, ret);
+ }
}
if (ret < 0) {
qemu_file_set_error(f, ret);
- error_report("Failed to load %s:%s", vmsd->name,
- field->name);
trace_vmstate_load_field_error(field->name, ret);
return ret;
}
}
} else if (field->flags & VMS_MUST_EXIST) {
- error_report("Input validation failed: %s/%s",
- vmsd->name, field->name);
+ error_setg(errp, "Input validation failed: %s/%s version_id: %d",
+ vmsd->name, field->name, vmsd->version_id);
return -1;
}
field++;
}
assert(field->flags == VMS_END);
- ret = vmstate_subsection_load(f, vmsd, opaque);
+ ret = vmstate_subsection_load(f, vmsd, opaque, errp);
if (ret != 0) {
qemu_file_set_error(f, ret);
return ret;
}
- if (vmsd->post_load) {
+ if (vmsd->post_load_errp) {
+ ret = vmsd->post_load_errp(opaque, version_id, errp);
+ if (ret < 0) {
+ error_prepend(errp, "post load hook failed for: %s, version_id: "
+ "%d, minimum_version: %d, ret: %d: ", vmsd->name,
+ vmsd->version_id, vmsd->minimum_version_id, ret);
+ }
+ } else if (vmsd->post_load) {
ret = vmsd->post_load(opaque, version_id);
+ if (ret < 0) {
+ error_setg(errp,
+ "post load hook failed for: %s, version_id: %d, "
+ "minimum_version: %d, ret: %d",
+ vmsd->name, vmsd->version_id, vmsd->minimum_version_id,
+ ret);
+ }
}
trace_vmstate_load_state_end(vmsd->name, "end", ret);
return ret;
@@ -384,12 +423,6 @@ bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque)
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, JSONWriter *vmdesc_id)
-{
- return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id, NULL);
-}
-
-int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc_id, Error **errp)
{
return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id, errp);
@@ -398,12 +431,20 @@ int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc, int version_id, Error **errp)
{
+ ERRP_GUARD();
int ret = 0;
const VMStateField *field = vmsd->fields;
trace_vmstate_save_state_top(vmsd->name);
- if (vmsd->pre_save) {
+ if (vmsd->pre_save_errp) {
+ ret = vmsd->pre_save_errp(opaque, errp);
+ trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
+ if (ret < 0) {
+ error_prepend(errp, "pre-save for %s failed, ret: %d: ",
+ vmsd->name, ret);
+ }
+ } else if (vmsd->pre_save) {
ret = vmsd->pre_save(opaque);
trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
if (ret) {
@@ -490,7 +531,7 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
if (inner_field->flags & VMS_STRUCT) {
ret = vmstate_save_state(f, inner_field->vmsd,
- curr_elem, vmdesc_loop);
+ curr_elem, vmdesc_loop, errp);
} else if (inner_field->flags & VMS_VSTRUCT) {
ret = vmstate_save_state_v(f, inner_field->vmsd,
curr_elem, vmdesc_loop,
@@ -566,8 +607,9 @@ vmstate_get_subsection(const VMStateDescription * const *sub,
}
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque)
+ void *opaque, Error **errp)
{
+ ERRP_GUARD();
trace_vmstate_subsection_load(vmsd->name);
while (qemu_peek_byte(f, 0) == QEMU_VM_SUBSECTION) {
@@ -598,6 +640,8 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
sub_vmsd = vmstate_get_subsection(vmsd->subsections, idstr);
if (sub_vmsd == NULL) {
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(lookup)");
+ error_setg(errp, "VM subsection '%s' in '%s' does not exist",
+ idstr, vmsd->name);
return -ENOENT;
}
qemu_file_skip(f, 1); /* subsection */
@@ -605,9 +649,12 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
qemu_file_skip(f, len); /* idstr */
version_id = qemu_get_be32(f);
- ret = vmstate_load_state(f, sub_vmsd, opaque, version_id);
+ ret = vmstate_load_state(f, sub_vmsd, opaque, version_id, errp);
if (ret) {
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(child)");
+ error_prepend(errp,
+ "Loading VM subsection '%s' in '%s' failed: %d: ",
+ idstr, vmsd->name, ret);
return ret;
}
}
@@ -646,7 +693,7 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
qemu_put_byte(f, len);
qemu_put_buffer(f, (uint8_t *)vmsdsub->name, len);
qemu_put_be32(f, vmsdsub->version_id);
- ret = vmstate_save_state_with_err(f, vmsdsub, opaque, vmdesc, errp);
+ ret = vmstate_save_state(f, vmsdsub, opaque, vmdesc, errp);
if (ret) {
return ret;
}
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index 74a0f56..33a88ce 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -280,14 +280,15 @@ void hmp_log(Monitor *mon, const QDict *qdict)
void hmp_gdbserver(Monitor *mon, const QDict *qdict)
{
+ Error *err = NULL;
const char *device = qdict_get_try_str(qdict, "device");
+
if (!device) {
device = "tcp::" DEFAULT_GDBSTUB_PORT;
}
- if (!gdbserver_start(device, &error_warn)) {
- monitor_printf(mon, "Could not open gdbserver on device '%s'\n",
- device);
+ if (!gdbserver_start(device, &err)) {
+ error_report_err(err);
} else if (strcmp(device, "none") == 0) {
monitor_printf(mon, "Disabled gdbserver\n");
} else {
diff --git a/net/slirp.c b/net/slirp.c
index 9657e86..0a1c2a5 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -258,11 +258,13 @@ static void net_slirp_register_poll_sock(slirp_os_socket fd, void *opaque)
{
#ifdef WIN32
AioContext *ctxt = qemu_get_aio_context();
+ g_autofree char *msg = NULL;
if (WSAEventSelect(fd, event_notifier_get_handle(&ctxt->notifier),
FD_READ | FD_ACCEPT | FD_CLOSE |
FD_CONNECT | FD_WRITE | FD_OOB) != 0) {
- error_setg_win32(&error_warn, WSAGetLastError(), "failed to WSAEventSelect()");
+ msg = g_win32_error_message(WSAGetLastError());
+ warn_report("failed to WSAEventSelect(): %s", msg);
}
#endif
}
@@ -270,8 +272,11 @@ static void net_slirp_register_poll_sock(slirp_os_socket fd, void *opaque)
static void net_slirp_unregister_poll_sock(slirp_os_socket fd, void *opaque)
{
#ifdef WIN32
+ g_autofree char *msg = NULL;
+
if (WSAEventSelect(fd, NULL, 0) != 0) {
- error_setg_win32(&error_warn, WSAGetLastError(), "failed to WSAEventSelect()");
+ msg = g_win32_error_message(WSAGetLastError());
+ warn_report("failed to WSAEventSelect(): %s", msg);
}
#endif
}
diff --git a/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin b/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin
index b2e7400..02be3a7 100644
--- a/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin
+++ b/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin
Binary files differ
diff --git a/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin b/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin
index 018b473..cce35c6 100644
--- a/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin
+++ b/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin
Binary files differ
diff --git a/qapi/migration.json b/qapi/migration.json
index 2387c21..be0f3fc 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -694,9 +694,32 @@
# until you issue the `migrate-incoming` command.
#
# (since 10.0)
+#
+# @cpr-exec: The migrate command stops the VM, saves state to the
+# migration channel, directly exec's a new version of QEMU on the
+# same host, replacing the original process while retaining its
+# PID, and loads state from the channel. Guest RAM is preserved
+# in place. Devices and their pinned pages are also preserved for
+# VFIO and IOMMUFD.
+#
+# Old QEMU starts new QEMU by exec'ing the command specified by
+# the @cpr-exec-command parameter. The command may be a direct
+# invocation of new QEMU, or may be a wrapper that exec's the new
+# QEMU binary.
+#
+# Because old QEMU terminates when new QEMU starts, one cannot
+# stream data between the two, so the channel must be a type,
+# such as a file, that accepts all data before old QEMU exits.
+# Otherwise, old QEMU may quietly block writing to the channel.
+#
+# Memory-backend objects must have the share=on attribute, but
+# memory-backend-epc is not supported. The VM must be started
+# with the '-machine aux-ram-share=on' option.
+#
+# (since 10.2)
##
{ 'enum': 'MigMode',
- 'data': [ 'normal', 'cpr-reboot', 'cpr-transfer' ] }
+ 'data': [ 'normal', 'cpr-reboot', 'cpr-transfer', 'cpr-exec' ] }
##
# @ZeroPageDetection:
@@ -924,6 +947,10 @@
# only has effect if the @mapped-ram capability is enabled.
# (Since 9.1)
#
+# @cpr-exec-command: Command to start the new QEMU process when @mode
+# is @cpr-exec. The first list element is the program's filename,
+# the remainder its arguments. (Since 10.2)
+#
# Features:
#
# @unstable: Members @x-checkpoint-delay and
@@ -950,7 +977,8 @@
'vcpu-dirty-limit',
'mode',
'zero-page-detection',
- 'direct-io'] }
+ 'direct-io',
+ 'cpr-exec-command'] }
##
# @MigrateSetParameters:
@@ -1105,6 +1133,10 @@
# only has effect if the @mapped-ram capability is enabled.
# (Since 9.1)
#
+# @cpr-exec-command: Command to start the new QEMU process when @mode
+# is @cpr-exec. The first list element is the program's filename,
+# the remainder its arguments. (Since 10.2)
+#
# Features:
#
# @unstable: Members @x-checkpoint-delay and
@@ -1146,7 +1178,8 @@
'*vcpu-dirty-limit': 'uint64',
'*mode': 'MigMode',
'*zero-page-detection': 'ZeroPageDetection',
- '*direct-io': 'bool' } }
+ '*direct-io': 'bool',
+ '*cpr-exec-command': [ 'str' ]} }
##
# @migrate-set-parameters:
@@ -1315,6 +1348,10 @@
# only has effect if the @mapped-ram capability is enabled.
# (Since 9.1)
#
+# @cpr-exec-command: Command to start the new QEMU process when @mode
+# is @cpr-exec. The first list element is the program's filename,
+# the remainder its arguments. (Since 10.2)
+#
# Features:
#
# @unstable: Members @x-checkpoint-delay and
@@ -1353,7 +1390,8 @@
'*vcpu-dirty-limit': 'uint64',
'*mode': 'MigMode',
'*zero-page-detection': 'ZeroPageDetection',
- '*direct-io': 'bool' } }
+ '*direct-io': 'bool',
+ '*cpr-exec-command': [ 'str' ]} }
##
# @query-migrate-parameters:
diff --git a/roms/opensbi b/roms/opensbi
-Subproject 43cace6c3671e5172d0df0a8963e552bb04b7b2
+Subproject a32a91069119e7a5aa31e6bc51d5e00860be3d8
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 8315f98..444ef51 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -159,6 +159,7 @@ dependencies = [
"migration",
"qom",
"system",
+ "trace",
"util",
]
@@ -259,6 +260,13 @@ dependencies = [
]
[[package]]
+name = "trace"
+version = "0.1.0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index d8183c6..f372d7d 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -11,6 +11,7 @@ members = [
"hw/core",
"hw/char/pl011",
"hw/timer/hpet",
+ "trace",
"util",
"tests",
]
diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml
index b2418ab..dc41d0e 100644
--- a/rust/hw/char/pl011/Cargo.toml
+++ b/rust/hw/char/pl011/Cargo.toml
@@ -24,6 +24,7 @@ qom = { path = "../../../qom" }
chardev = { path = "../../../chardev" }
system = { path = "../../../system" }
hwcore = { path = "../../../hw/core" }
+trace = { path = "../../../trace" }
[lints]
workspace = true
diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build
index a33f329..07b3da1 100644
--- a/rust/hw/char/pl011/meson.build
+++ b/rust/hw/char/pl011/meson.build
@@ -40,6 +40,7 @@ _libpl011_rs = static_library(
chardev_rs,
system_rs,
hwcore_rs,
+ trace_rs
],
)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 1b4587d..8889d6e 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -21,6 +21,8 @@ use util::{log::Log, log_mask_ln};
use crate::registers::{self, Interrupt, RegisterOffset};
+::trace::include_trace!("hw_char");
+
// TODO: You must disable the UART before any of the control registers are
// reprogrammed. When the UART is disabled in the middle of transmission or
// reception, it completes the current character before stopping
@@ -208,13 +210,7 @@ impl PL011Registers {
(update, result)
}
- pub(self) fn write(
- &mut self,
- offset: RegisterOffset,
- value: u32,
- char_backend: &CharBackend,
- ) -> bool {
- // eprintln!("write offset {offset} value {value}");
+ pub(self) fn write(&mut self, offset: RegisterOffset, value: u32, device: &PL011State) -> bool {
use RegisterOffset::*;
match offset {
DR => return self.write_data_register(value),
@@ -229,9 +225,11 @@ impl PL011Registers {
}
IBRD => {
self.ibrd = value;
+ device.trace_baudrate_change(self.ibrd, self.fbrd);
}
FBRD => {
self.fbrd = value;
+ device.trace_baudrate_change(self.ibrd, self.fbrd);
}
LCR_H => {
let new_val: registers::LineControl = value.into();
@@ -242,7 +240,7 @@ impl PL011Registers {
}
let update = (self.line_control.send_break() != new_val.send_break()) && {
let break_enable = new_val.send_break();
- let _ = char_backend.send_break(break_enable);
+ let _ = device.char_backend.send_break(break_enable);
self.loopback_break(break_enable)
};
self.line_control = new_val;
@@ -279,12 +277,13 @@ impl PL011Registers {
}
fn read_data_register(&mut self, update: &mut bool) -> u32 {
+ let depth = self.fifo_depth();
self.flags.set_receive_fifo_full(false);
let c = self.read_fifo[self.read_pos];
if self.read_count > 0 {
self.read_count -= 1;
- self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
+ self.read_pos = (self.read_pos + 1) & (depth - 1);
}
if self.read_count == 0 {
self.flags.set_receive_fifo_empty(true);
@@ -292,6 +291,7 @@ impl PL011Registers {
if self.read_count + 1 == self.read_trigger {
self.int_level &= !Interrupt::RX;
}
+ trace::trace_pl011_read_fifo(self.read_count, depth);
self.receive_status_error_clear.set_from_data(c);
*update = true;
u32::from(c)
@@ -447,7 +447,9 @@ impl PL011Registers {
self.read_fifo[slot] = value;
self.read_count += 1;
self.flags.set_receive_fifo_empty(false);
+ trace::trace_pl011_fifo_rx_put(value.into(), self.read_count, depth);
if self.read_count == depth {
+ trace::trace_pl011_fifo_rx_full();
self.flags.set_receive_fifo_full(true);
}
@@ -516,8 +518,21 @@ impl PL011State {
uninit_field_mut!(*this, clock).write(clock);
}
- const fn clock_update(&self, _event: ClockEvent) {
- /* pl011_trace_baudrate_change(s); */
+ pub fn trace_baudrate_change(&self, ibrd: u32, fbrd: u32) {
+ let divider = 4.0 / f64::from(ibrd * (FBRD_MASK + 1) + fbrd);
+ let hz = self.clock.hz();
+ let rate = if ibrd == 0 {
+ 0
+ } else {
+ ((hz as f64) * divider) as u32
+ };
+ trace::trace_pl011_baudrate_change(rate, hz, ibrd, fbrd);
+ }
+
+ fn clock_update(&self, _event: ClockEvent) {
+ let regs = self.regs.borrow();
+ let (ibrd, fbrd) = (regs.ibrd, regs.fbrd);
+ self.trace_baudrate_change(ibrd, fbrd)
}
pub fn clock_needed(&self) -> bool {
@@ -543,6 +558,7 @@ impl PL011State {
}
Ok(field) => {
let (update_irq, result) = self.regs.borrow_mut().read(field);
+ trace::trace_pl011_read(offset, result, c"");
if update_irq {
self.update();
self.char_backend.accept_input();
@@ -557,6 +573,7 @@ impl PL011State {
if let Ok(field) = RegisterOffset::try_from(offset) {
// qemu_chr_fe_write_all() calls into the can_receive
// callback, so handle writes before entering PL011Registers.
+ trace::trace_pl011_write(offset, value as u32, c"");
if field == RegisterOffset::DR {
// ??? Check if transmitter is enabled.
let ch: [u8; 1] = [value as u8];
@@ -565,10 +582,7 @@ impl PL011State {
let _ = self.char_backend.write_all(&ch);
}
- update_irq = self
- .regs
- .borrow_mut()
- .write(field, value as u32, &self.char_backend);
+ update_irq = self.regs.borrow_mut().write(field, value as u32, self);
} else {
log_mask_ln!(
Log::GuestError,
@@ -582,11 +596,19 @@ impl PL011State {
fn can_receive(&self) -> u32 {
let regs = self.regs.borrow();
- // trace_pl011_can_receive(s->lcr, s->read_count, r);
- regs.fifo_depth() - regs.read_count
+ let fifo_available = regs.fifo_depth() - regs.read_count;
+ trace::trace_pl011_can_receive(
+ regs.line_control.into(),
+ regs.read_count,
+ regs.fifo_depth(),
+ fifo_available,
+ );
+ fifo_available
}
fn receive(&self, buf: &[u8]) {
+ trace::trace_pl011_receive(buf.len());
+
let mut regs = self.regs.borrow_mut();
if regs.loopback_enabled() {
// In loopback mode, the RX input signal is internally disconnected
@@ -635,6 +657,7 @@ impl PL011State {
fn update(&self) {
let regs = self.regs.borrow();
let flags = regs.int_level & regs.int_enabled;
+ trace::trace_pl011_irq_state(flags != 0);
for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
irq.set(flags.any_set(i));
}
diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs
index a4493db..c3097a2 100644
--- a/rust/hw/core/src/qdev.rs
+++ b/rust/hw/core/src/qdev.rs
@@ -411,6 +411,39 @@ where
impl<R: ObjectDeref> DeviceMethods for R where R::Target: IsA<DeviceState> {}
+impl Clock {
+ pub const PERIOD_1SEC: u64 = bindings::CLOCK_PERIOD_1SEC;
+
+ pub const fn period_from_ns(ns: u64) -> u64 {
+ ns * Self::PERIOD_1SEC / 1_000_000_000
+ }
+
+ pub const fn period_from_hz(hz: u64) -> u64 {
+ if hz == 0 {
+ 0
+ } else {
+ Self::PERIOD_1SEC / hz
+ }
+ }
+
+ pub const fn period_to_hz(period: u64) -> u64 {
+ if period == 0 {
+ 0
+ } else {
+ Self::PERIOD_1SEC / period
+ }
+ }
+
+ pub const fn period(&self) -> u64 {
+ // SAFETY: Clock is returned by init_clock_in with zero value for period
+ unsafe { &*self.0.as_ptr() }.period
+ }
+
+ pub const fn hz(&self) -> u64 {
+ Self::period_to_hz(self.period())
+ }
+}
+
unsafe impl ObjectType for Clock {
type Class = ObjectClass;
const TYPE_NAME: &'static CStr =
diff --git a/rust/meson.build b/rust/meson.build
index b3ac3a7..695d5a6 100644
--- a/rust/meson.build
+++ b/rust/meson.build
@@ -34,7 +34,7 @@ subdir('system')
subdir('chardev')
subdir('hw/core')
subdir('tests')
-
+subdir('trace')
subdir('hw')
cargo = find_program('cargo', required: false)
diff --git a/rust/trace/Cargo.toml b/rust/trace/Cargo.toml
new file mode 100644
index 0000000..fc81bce
--- /dev/null
+++ b/rust/trace/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "trace"
+version = "0.1.0"
+authors = ["Tanish Desai <tanishdesai37@gmail.com>"]
+description = "QEMU tracing infrastructure support"
+resolver = "2"
+publish = false
+
+edition.workspace = true
+homepage.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+libc = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/rust/trace/meson.build b/rust/trace/meson.build
new file mode 100644
index 0000000..adca57e
--- /dev/null
+++ b/rust/trace/meson.build
@@ -0,0 +1,19 @@
+rust = import('rust')
+
+lib_rs = configure_file(
+ input: 'src/lib.rs',
+ output: 'lib.rs',
+ configuration: {
+ 'MESON_BUILD_ROOT': meson.project_build_root(),
+ })
+
+_trace_rs = static_library(
+ 'trace', # Library name,
+ lib_rs,
+ trace_rs_targets, # List of generated `.rs` custom targets
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ dependencies: [libc_rs],
+ rust_abi: 'rust',
+)
+
+trace_rs = declare_dependency(link_with: _trace_rs)
diff --git a/rust/trace/src/lib.rs b/rust/trace/src/lib.rs
new file mode 100644
index 0000000..e03bce4
--- /dev/null
+++ b/rust/trace/src/lib.rs
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! This crate provides macros that aid in using QEMU's tracepoint
+//! functionality.
+
+#[doc(hidden)]
+/// Re-exported item to avoid adding libc as a dependency everywhere.
+pub use libc::{syslog, LOG_INFO};
+
+#[macro_export]
+/// Define the trace-points from the named directory (which should have slashes
+/// replaced by underscore characters) as functions in a module called `trace`.
+///
+/// ```ignore
+/// ::trace::include_trace!("hw_char");
+/// // ...
+/// trace::trace_pl011_read_fifo_rx_full();
+/// ```
+macro_rules! include_trace {
+ ($name:literal) => {
+ #[allow(
+ clippy::ptr_as_ptr,
+ clippy::cast_lossless,
+ clippy::used_underscore_binding
+ )]
+ mod trace {
+ #[cfg(not(MESON))]
+ include!(concat!(
+ env!("MESON_BUILD_ROOT"),
+ "/trace/trace-",
+ $name,
+ ".rs"
+ ));
+
+ #[cfg(MESON)]
+ include!(concat!("@MESON_BUILD_ROOT@/trace/trace-", $name, ".rs"));
+ }
+ };
+}
diff --git a/rust/util/src/log.rs b/rust/util/src/log.rs
index af9a3e9..0a4bc42 100644
--- a/rust/util/src/log.rs
+++ b/rust/util/src/log.rs
@@ -142,7 +142,7 @@ macro_rules! log_mask_ln {
let _: $crate::log::Log = $mask;
if unsafe {
- ($crate::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0
+ ($crate::bindings::qemu_loglevel & ($mask as std::os::raw::c_uint)) != 0
} {
_ = $crate::log::LogGuard::log_fmt(
format_args!("{}\n", format_args!($fmt $($args)*)));
diff --git a/scripts/analyse-locks-simpletrace.py b/scripts/analyse-locks-simpletrace.py
index d650dd7..bd04cd4 100755
--- a/scripts/analyse-locks-simpletrace.py
+++ b/scripts/analyse-locks-simpletrace.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
#
# Analyse lock events and compute statistics
#
diff --git a/scripts/modinfo-collect.py b/scripts/modinfo-collect.py
index 48bd92b..6ebaea9 100644
--- a/scripts/modinfo-collect.py
+++ b/scripts/modinfo-collect.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
import os
import sys
diff --git a/scripts/modinfo-generate.py b/scripts/modinfo-generate.py
index b1538fc..aaf2354 100644
--- a/scripts/modinfo-generate.py
+++ b/scripts/modinfo-generate.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
import os
import sys
diff --git a/scripts/oss-fuzz/minimize_qtest_trace.py b/scripts/oss-fuzz/minimize_qtest_trace.py
index d1f3990..414a6d9 100755
--- a/scripts/oss-fuzz/minimize_qtest_trace.py
+++ b/scripts/oss-fuzz/minimize_qtest_trace.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
This takes a crashing qtest trace and tries to remove superfluous operations
diff --git a/scripts/oss-fuzz/output_reproducer.py b/scripts/oss-fuzz/output_reproducer.py
index e8ef76b..0df96cf 100755
--- a/scripts/oss-fuzz/output_reproducer.py
+++ b/scripts/oss-fuzz/output_reproducer.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
Convert plain qtest traces to C or Bash reproducers
diff --git a/scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py b/scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py
index b154a25..8af0d5d 100755
--- a/scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py
+++ b/scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
Use this to convert qtest log info from a generic fuzzer input into a qtest
diff --git a/scripts/probe-gdb-support.py b/scripts/probe-gdb-support.py
index 6bcadce..43c7030 100644
--- a/scripts/probe-gdb-support.py
+++ b/scripts/probe-gdb-support.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# coding: utf-8
#
# Probe gdb for supported architectures.
#
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index e35e4dd..f73bc55 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright (c) 2017-2019 Red Hat Inc.
#
# Authors:
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index cae0a08..f40b247 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright IBM, Corp. 2011
# Copyright (c) 2013-2021 Red Hat Inc.
#
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
index d3c56d45..0c9b8db 100644
--- a/scripts/qapi/gen.py
+++ b/scripts/qapi/gen.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# QAPI code generation
#
# Copyright (c) 2015-2019 Red Hat Inc.
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 2529edf..9fbf80a 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# QAPI schema parser
#
# Copyright IBM, Corp. 2011
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 3abddea..8d88b40 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# QAPI schema internal representation
#
# Copyright (c) 2015-2019 Red Hat Inc.
diff --git a/scripts/qemu-plugin-symbols.py b/scripts/qemu-plugin-symbols.py
index e285ebb..6964497 100755
--- a/scripts/qemu-plugin-symbols.py
+++ b/scripts/qemu-plugin-symbols.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
#
# Extract QEMU Plugin API symbols from a header file
#
diff --git a/scripts/qemugdb/tcg.py b/scripts/qemugdb/tcg.py
index 16c03c0..22529c7 100644
--- a/scripts/qemugdb/tcg.py
+++ b/scripts/qemugdb/tcg.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# GDB debugging support, TCG status
#
# Copyright 2016 Linaro Ltd
diff --git a/scripts/qemugdb/timers.py b/scripts/qemugdb/timers.py
index 46537b2..5714f92 100644
--- a/scripts/qemugdb/timers.py
+++ b/scripts/qemugdb/timers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# GDB debugging support
#
# Copyright 2017 Linaro Ltd
diff --git a/scripts/replay-dump.py b/scripts/replay-dump.py
index 4ce7ff5..0976365 100755
--- a/scripts/replay-dump.py
+++ b/scripts/replay-dump.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
#
# Dump the contents of a recorded execution stream
#
diff --git a/scripts/tracetool.py b/scripts/tracetool.py
index 5de9ce9..0fdc9cb 100755
--- a/scripts/tracetool.py
+++ b/scripts/tracetool.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
Command-line wrapper for the tracetool machinery.
diff --git a/scripts/tracetool/__init__.py b/scripts/tracetool/__init__.py
index 1d5238a..74062d2 100644
--- a/scripts/tracetool/__init__.py
+++ b/scripts/tracetool/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Machinery for generating tracing-related intermediate files.
@@ -15,7 +15,6 @@ __email__ = "stefanha@redhat.com"
import os
import re
import sys
-import weakref
from pathlib import PurePath
import tracetool.backend
@@ -31,6 +30,49 @@ def error(*lines):
error_write(*lines)
sys.exit(1)
+FMT_TOKEN = re.compile(r'''(?:
+ " ( (?: [^"\\] | \\[\\"abfnrt] | # a string literal
+ \\x[0-9a-fA-F][0-9a-fA-F]) *? ) "
+ | ( PRI [duixX] (?:8|16|32|64|PTR|MAX) ) # a PRIxxx macro
+ | \s+ # spaces (ignored)
+ )''', re.X)
+
+PRI_SIZE_MAP = {
+ '8': 'hh',
+ '16': 'h',
+ '32': '',
+ '64': 'll',
+ 'PTR': 't',
+ 'MAX': 'j',
+}
+
+def expand_format_string(c_fmt, prefix=""):
+ def pri_macro_to_fmt(pri_macro):
+ assert pri_macro.startswith("PRI")
+ fmt_type = pri_macro[3] # 'd', 'i', 'u', or 'x'
+ fmt_size = pri_macro[4:] # '8', '16', '32', '64', 'PTR', 'MAX'
+
+ size = PRI_SIZE_MAP.get(fmt_size, None)
+ if size is None:
+ raise Exception(f"unknown macro {pri_macro}")
+ return size + fmt_type
+
+ result = prefix
+ pos = 0
+ while pos < len(c_fmt):
+ m = FMT_TOKEN.match(c_fmt, pos)
+ if not m:
+ print("No match at position", pos, ":", repr(c_fmt[pos:]), file=sys.stderr)
+ raise Exception("syntax error in trace file")
+ if m[1]:
+ substr = m[1]
+ elif m[2]:
+ substr = pri_macro_to_fmt(m[2])
+ else:
+ substr = ""
+ result += substr
+ pos = m.end()
+ return result
out_lineno = 1
out_filename = '<none>'
@@ -90,6 +132,49 @@ ALLOWED_TYPES = [
"ptrdiff_t",
]
+C_TYPE_KEYWORDS = {"char", "int", "void", "short", "long", "signed", "unsigned"}
+
+C_TO_RUST_TYPE_MAP = {
+ "int": "std::ffi::c_int",
+ "long": "std::ffi::c_long",
+ "long long": "std::ffi::c_longlong",
+ "short": "std::ffi::c_short",
+ "char": "std::ffi::c_char",
+ "bool": "bool",
+ "unsigned": "std::ffi::c_uint",
+ # multiple keywords, keep them sorted
+ "long unsigned": "std::ffi::c_long",
+ "long long unsigned": "std::ffi::c_ulonglong",
+ "short unsigned": "std::ffi::c_ushort",
+ "char unsigned": "u8",
+ "int8_t": "i8",
+ "uint8_t": "u8",
+ "int16_t": "i16",
+ "uint16_t": "u16",
+ "int32_t": "i32",
+ "uint32_t": "u32",
+ "int64_t": "i64",
+ "uint64_t": "u64",
+ "void": "()",
+ "size_t": "usize",
+ "ssize_t": "isize",
+ "uintptr_t": "usize",
+ "ptrdiff_t": "isize",
+}
+
+# Rust requires manual casting of <32-bit types when passing them to
+# variable-argument functions.
+RUST_VARARGS_SMALL_TYPES = {
+ "std::ffi::c_short",
+ "std::ffi::c_ushort",
+ "std::ffi::c_char",
+ "i8",
+ "u8",
+ "i16",
+ "u16",
+ "bool",
+}
+
def validate_type(name):
bits = name.split(" ")
for bit in bits:
@@ -105,6 +190,38 @@ def validate_type(name):
"other complex pointer types should be "
"declared as 'void *'" % name)
+def c_type_to_rust(name):
+ ptr = False
+ const = False
+ name = name.rstrip()
+ if name[-1] == '*':
+ name = name[:-1].rstrip()
+ ptr = True
+ if name[-1] == '*':
+ # pointers to pointers are the same as void*
+ name = "void"
+
+ bits = name.split()
+ if "const" in bits:
+ const = True
+ bits.remove("const")
+ if bits[0] in C_TYPE_KEYWORDS:
+ if "signed" in bits:
+ bits.remove("signed")
+ if len(bits) > 1 and "int" in bits:
+ bits.remove("int")
+ bits.sort()
+ name = ' '.join(bits)
+ else:
+ if len(bits) > 1:
+ raise ValueError("Invalid type '%s'." % name)
+ name = bits[0]
+
+ ty = C_TO_RUST_TYPE_MAP[name.strip()]
+ if ptr:
+ ty = f'*{"const" if const else "mut"} {ty}'
+ return ty
+
class Arguments:
"""Event arguments description."""
@@ -122,10 +239,6 @@ class Arguments:
else:
self._args.append(arg)
- def copy(self):
- """Create a new copy."""
- return Arguments(list(self._args))
-
@staticmethod
def build(arg_str):
"""Build and Arguments instance from an argument string.
@@ -197,6 +310,43 @@ class Arguments:
"""List of argument names casted to their type."""
return ["(%s)%s" % (type_, name) for type_, name in self._args]
+ def rust_decl_extern(self):
+ """Return a Rust argument list for an extern "C" function"""
+ return ", ".join((f"_{name}: {c_type_to_rust(type_)}"
+ for type_, name in self._args))
+
+ def rust_decl(self):
+ """Return a Rust argument list for a tracepoint function"""
+ def decl_type(type_):
+ if type_ == "const char *":
+ return "&std::ffi::CStr"
+ return c_type_to_rust(type_)
+
+ return ", ".join((f"_{name}: {decl_type(type_)}"
+ for type_, name in self._args))
+
+ def rust_call_extern(self):
+ """Return a Rust argument list for a call to an extern "C" function"""
+ def rust_cast(name, type_):
+ if type_ == "const char *":
+ return f"_{name}.as_ptr()"
+ return f"_{name}"
+
+ return ", ".join((rust_cast(name, type_) for type_, name in self._args))
+
+ def rust_call_varargs(self):
+ """Return a Rust argument list for a call to a C varargs function"""
+ def rust_cast(name, type_):
+ if type_ == "const char *":
+ return f"_{name}.as_ptr()"
+
+ type_ = c_type_to_rust(type_)
+ if type_ in RUST_VARARGS_SMALL_TYPES:
+ return f"_{name} as std::ffi::c_int"
+ return f"_{name} /* as {type_} */"
+
+ return ", ".join((rust_cast(name, type_) for type_, name in self._args))
+
class Event(object):
"""Event description.
@@ -222,13 +372,12 @@ class Event(object):
r"(?P<name>\w+)"
r"\((?P<args>[^)]*)\)"
r"\s*"
- r"(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?"
+ r"(?P<fmt>\".+)?"
r"\s*")
_VALID_PROPS = set(["disable"])
- def __init__(self, name, props, fmt, args, lineno, filename, orig=None,
- event_trans=None, event_exec=None):
+ def __init__(self, name, props, fmt, args, lineno, filename):
"""
Parameters
----------
@@ -236,20 +385,14 @@ class Event(object):
Event name.
props : list of str
Property names.
- fmt : str, list of str
- Event printing format string(s).
+ fmt : str
+ Event printing format string.
args : Arguments
Event arguments.
lineno : int
The line number in the input file.
filename : str
The path to the input file.
- orig : Event or None
- Original Event before transformation/generation.
- event_trans : Event or None
- Generated translation-time event ("tcg" property).
- event_exec : Event or None
- Generated execution-time event ("tcg" property).
"""
self.name = name
@@ -258,29 +401,16 @@ class Event(object):
self.args = args
self.lineno = int(lineno)
self.filename = str(filename)
- self.event_trans = event_trans
- self.event_exec = event_exec
if len(args) > 10:
raise ValueError("Event '%s' has more than maximum permitted "
"argument count" % name)
- if orig is None:
- self.original = weakref.ref(self)
- else:
- self.original = orig
-
unknown_props = set(self.properties) - self._VALID_PROPS
if len(unknown_props) > 0:
raise ValueError("Unknown properties: %s"
% ", ".join(unknown_props))
- assert isinstance(self.fmt, str) or len(self.fmt) == 2
- def copy(self):
- """Create a new copy."""
- return Event(self.name, list(self.properties), self.fmt,
- self.args.copy(), self.lineno, self.filename,
- self, self.event_trans, self.event_exec)
@staticmethod
def build(line_str, lineno, filename):
@@ -302,8 +432,7 @@ class Event(object):
name = groups["name"]
props = groups["props"].split()
fmt = groups["fmt"]
- fmt_trans = groups["fmt_trans"]
- if fmt.find("%m") != -1 or fmt_trans.find("%m") != -1:
+ if fmt.find("%m") != -1:
raise ValueError("Event format '%m' is forbidden, pass the error "
"as an explicit trace argument")
if fmt.endswith(r'\n"'):
@@ -312,29 +441,22 @@ class Event(object):
if '\\n' in fmt:
raise ValueError("Event format must not use new line character")
- if len(fmt_trans) > 0:
- fmt = [fmt_trans, fmt]
args = Arguments.build(groups["args"])
return Event(name, props, fmt, args, lineno, posix_relpath(filename))
def __repr__(self):
"""Evaluable string representation for this object."""
- if isinstance(self.fmt, str):
- fmt = self.fmt
- else:
- fmt = "%s, %s" % (self.fmt[0], self.fmt[1])
return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
self.name,
self.args,
- fmt)
+ self.fmt)
# Star matching on PRI is dangerous as one might have multiple
# arguments with that format, hence the non-greedy version of it.
_FMT = re.compile(r"(%[\d\.]*\w+|%.*?PRI\S+)")
def formats(self):
"""List conversion specifiers in the argument print format string."""
- assert not isinstance(self.fmt, list)
return self._FMT.findall(self.fmt)
QEMU_TRACE = "trace_%(name)s"
diff --git a/scripts/tracetool/backend/__init__.py b/scripts/tracetool/backend/__init__.py
index 7bfcc86..9109a78 100644
--- a/scripts/tracetool/backend/__init__.py
+++ b/scripts/tracetool/backend/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Backend management.
@@ -19,11 +19,15 @@ All backends must generate their contents through the 'tracetool.out' routine.
Backend attributes
------------------
-========= ====================================================================
-Attribute Description
-========= ====================================================================
-PUBLIC If exists and is set to 'True', the backend is considered "public".
-========= ====================================================================
+=========================== ====================================================
+Attribute Description
+=========================== ====================================================
+PUBLIC If exists and is set to 'True', the backend is
+ considered "public".
+CHECK_TRACE_EVENT_GET_STATE If exists and is set to 'True', the backend-specific
+ code inside the tracepoint is emitted within an
+ ``if trace_event_get_state()`` conditional.
+=========================== ====================================================
Backend functions
@@ -94,29 +98,40 @@ def exists(name):
if name == "nop":
return True
name = name.replace("-", "_")
- return tracetool.try_import("tracetool.backend." + name)[1]
+ return tracetool.try_import("tracetool.backend." + name)[0]
class Wrapper:
def __init__(self, backends, format):
self._backends = [backend.replace("-", "_") for backend in backends]
self._format = format.replace("-", "_")
+ self.check_trace_event_get_state = False
for backend in self._backends:
assert exists(backend)
assert tracetool.format.exists(self._format)
+ for backend in self.backend_modules():
+ check_trace_event_get_state = getattr(backend, "CHECK_TRACE_EVENT_GET_STATE", False)
+ self.check_trace_event_get_state = self.check_trace_event_get_state or check_trace_event_get_state
- def _run_function(self, name, *args, **kwargs):
+ def backend_modules(self):
for backend in self._backends:
- func = tracetool.try_import("tracetool.backend." + backend,
- name % self._format, None)[1]
- if func is not None:
- func(*args, **kwargs)
+ module = tracetool.try_import("tracetool.backend." + backend)[1]
+ if module is not None:
+ yield module
+
+ def _run_function(self, name, *args, check_trace_event_get_state=None, **kwargs):
+ for backend in self.backend_modules():
+ func = getattr(backend, name % self._format, None)
+ if func is not None and \
+ (check_trace_event_get_state is None or
+ check_trace_event_get_state == getattr(backend, 'CHECK_TRACE_EVENT_GET_STATE', False)):
+ func(*args, **kwargs)
def generate_begin(self, events, group):
self._run_function("generate_%s_begin", events, group)
- def generate(self, event, group):
- self._run_function("generate_%s", event, group)
+ def generate(self, event, group, check_trace_event_get_state=None):
+ self._run_function("generate_%s", event, group, check_trace_event_get_state=check_trace_event_get_state)
def generate_backend_dstate(self, event, group):
self._run_function("generate_%s_backend_dstate", event, group)
diff --git a/scripts/tracetool/backend/dtrace.py b/scripts/tracetool/backend/dtrace.py
index e17edc9..b4af403 100644
--- a/scripts/tracetool/backend/dtrace.py
+++ b/scripts/tracetool/backend/dtrace.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
DTrace/SystemTAP backend.
diff --git a/scripts/tracetool/backend/ftrace.py b/scripts/tracetool/backend/ftrace.py
index 5fa30cc..e03698a 100644
--- a/scripts/tracetool/backend/ftrace.py
+++ b/scripts/tracetool/backend/ftrace.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Ftrace built-in backend.
@@ -12,10 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
-from tracetool import out
+from tracetool import out, expand_format_string
PUBLIC = True
+CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
@@ -28,22 +29,11 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
- out(' {',
- ' char ftrace_buf[MAX_TRACE_STRLEN];',
- ' int unused __attribute__ ((unused));',
- ' int trlen;',
- ' if (trace_event_get_state(%(event_id)s)) {',
- '#line %(event_lineno)d "%(event_filename)s"',
- ' trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,',
- ' "%(name)s " %(fmt)s "\\n" %(argnames)s);',
+ out('#line %(event_lineno)d "%(event_filename)s"',
+ ' ftrace_write("%(name)s " %(fmt)s "\\n" %(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
- ' trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);',
- ' unused = write(trace_marker_fd, ftrace_buf, trlen);',
- ' }',
- ' }',
name=event.name,
args=event.args,
- event_id="TRACE_" + event.name.upper(),
event_lineno=event.lineno,
event_filename=event.filename,
fmt=event.fmt.rstrip("\n"),
@@ -53,3 +43,9 @@ def generate_h(event, group):
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
event_id="TRACE_" + event.name.upper())
+
+def generate_rs(event, group):
+ out(' let format_string = c"%(fmt)s";',
+ ' unsafe {bindings::ftrace_write(format_string.as_ptr() as *const c_char, %(args)s);}',
+ fmt=expand_format_string(event.fmt),
+ args=event.args.rust_call_varargs())
diff --git a/scripts/tracetool/backend/log.py b/scripts/tracetool/backend/log.py
index eb50cee..9e3e504 100644
--- a/scripts/tracetool/backend/log.py
+++ b/scripts/tracetool/backend/log.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Stderr built-in backend.
@@ -12,10 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
-from tracetool import out
+from tracetool import out, expand_format_string
PUBLIC = True
+CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
@@ -28,14 +29,11 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
- cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
-
- out(' if (%(cond)s && qemu_loglevel_mask(LOG_TRACE)) {',
+ out(' if (qemu_loglevel_mask(LOG_TRACE)) {',
'#line %(event_lineno)d "%(event_filename)s"',
- ' qemu_log("%(name)s " %(fmt)s "\\n"%(argnames)s);',
+ ' qemu_log("%(name)s " %(fmt)s "\\n"%(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
- ' }',
- cond=cond,
+ ' }',
event_lineno=event.lineno,
event_filename=event.filename,
name=event.name,
@@ -46,3 +44,11 @@ def generate_h(event, group):
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
event_id="TRACE_" + event.name.upper())
+
+def generate_rs(event, group):
+ out(' let format_string = c"%(fmt)s\\n";',
+ ' if (unsafe { bindings::qemu_loglevel } & bindings::LOG_TRACE) != 0 {',
+ ' unsafe { bindings::qemu_log(format_string.as_ptr() as *const c_char, %(args)s);}',
+ ' }',
+ fmt=expand_format_string(event.fmt, event.name + " "),
+ args=event.args.rust_call_varargs())
diff --git a/scripts/tracetool/backend/simple.py b/scripts/tracetool/backend/simple.py
index 7c84c06..b131e4f 100644
--- a/scripts/tracetool/backend/simple.py
+++ b/scripts/tracetool/backend/simple.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Simple built-in backend.
@@ -16,6 +16,7 @@ from tracetool import out
PUBLIC = True
+CHECK_TRACE_EVENT_GET_STATE = True
def is_string(arg):
@@ -36,13 +37,8 @@ def generate_h_begin(events, group):
def generate_h(event, group):
- event_id = 'TRACE_' + event.name.upper()
- cond = "trace_event_get_state(%s)" % event_id
- out(' if (%(cond)s) {',
- ' _simple_%(api)s(%(args)s);',
- ' }',
+ out(' _simple_%(api)s(%(args)s);',
api=event.api(),
- cond=cond,
args=", ".join(event.args.names()))
@@ -102,3 +98,10 @@ def generate_c(event, group):
out(' trace_record_finish(&rec);',
'}',
'')
+
+def generate_rs(event, group):
+ out(' extern "C" { fn _simple_%(api)s(%(rust_args)s); }',
+ ' unsafe { _simple_%(api)s(%(args)s); }',
+ api=event.api(),
+ rust_args=event.args.rust_decl_extern(),
+ args=event.args.rust_call_extern())
diff --git a/scripts/tracetool/backend/syslog.py b/scripts/tracetool/backend/syslog.py
index 3f82e54..12b8265 100644
--- a/scripts/tracetool/backend/syslog.py
+++ b/scripts/tracetool/backend/syslog.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Syslog built-in backend.
@@ -12,10 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
__email__ = "stefanha@redhat.com"
-from tracetool import out
+from tracetool import out, expand_format_string
PUBLIC = True
+CHECK_TRACE_EVENT_GET_STATE = True
def generate_h_begin(events, group):
@@ -28,20 +29,20 @@ def generate_h(event, group):
if len(event.args) > 0:
argnames = ", " + argnames
- cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
-
- out(' if (%(cond)s) {',
- '#line %(event_lineno)d "%(event_filename)s"',
+ out('#line %(event_lineno)d "%(event_filename)s"',
' syslog(LOG_INFO, "%(name)s " %(fmt)s %(argnames)s);',
'#line %(out_next_lineno)d "%(out_filename)s"',
- ' }',
- cond=cond,
event_lineno=event.lineno,
event_filename=event.filename,
name=event.name,
fmt=event.fmt.rstrip("\n"),
argnames=argnames)
+def generate_rs(event, group):
+ out(' let format_string = c"%(fmt)s";',
+ ' unsafe {::trace::syslog(::trace::LOG_INFO, format_string.as_ptr() as *const c_char, %(args)s);}',
+ fmt=expand_format_string(event.fmt),
+ args=event.args.rust_call_varargs())
def generate_h_backend_dstate(event, group):
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
diff --git a/scripts/tracetool/backend/ust.py b/scripts/tracetool/backend/ust.py
index c857516..3aa9bb1 100644
--- a/scripts/tracetool/backend/ust.py
+++ b/scripts/tracetool/backend/ust.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
LTTng User Space Tracing backend.
diff --git a/scripts/tracetool/format/__init__.py b/scripts/tracetool/format/__init__.py
index 2dc46f3..7b9d1b5 100644
--- a/scripts/tracetool/format/__init__.py
+++ b/scripts/tracetool/format/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Format management.
@@ -70,7 +70,7 @@ def exists(name):
if len(name) == 0:
return False
name = name.replace("-", "_")
- return tracetool.try_import("tracetool.format." + name)[1]
+ return tracetool.try_import("tracetool.format." + name)[0]
def generate(events, format, backend, group):
diff --git a/scripts/tracetool/format/c.py b/scripts/tracetool/format/c.py
index e473fb6..50e0331 100644
--- a/scripts/tracetool/format/c.py
+++ b/scripts/tracetool/format/c.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.c
diff --git a/scripts/tracetool/format/d.py b/scripts/tracetool/format/d.py
index a5e096e..e9e33df 100644
--- a/scripts/tracetool/format/d.py
+++ b/scripts/tracetool/format/d.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.dtrace (DTrace only).
diff --git a/scripts/tracetool/format/h.py b/scripts/tracetool/format/h.py
index b42a826..dd58713 100644
--- a/scripts/tracetool/format/h.py
+++ b/scripts/tracetool/format/h.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-tracers.h
@@ -40,11 +40,6 @@ def generate(events, backend, group):
enabled = 0
else:
enabled = 1
- if "tcg-exec" in e.properties:
- # a single define for the two "sub-events"
- out('#define TRACE_%(name)s_ENABLED %(enabled)d',
- name=e.original.name.upper(),
- enabled=enabled)
out('#define TRACE_%s_ENABLED %d' % (e.name.upper(), enabled))
backend.generate_begin(events, group)
@@ -60,7 +55,6 @@ def generate(events, backend, group):
out(' false)')
- # tracer without checks
out('',
'static inline void %(api)s(%(args)s)',
'{',
@@ -68,11 +62,17 @@ def generate(events, backend, group):
args=e.args)
if "disable" not in e.properties:
- backend.generate(e, group)
-
+ backend.generate(e, group, check_trace_event_get_state=False)
+
+ if backend.check_trace_event_get_state:
+ event_id = 'TRACE_' + e.name.upper()
+ cond = "trace_event_get_state(%s)" % event_id
+ out(' if (%(cond)s) {',
+ cond=cond)
+ backend.generate(e, group, check_trace_event_get_state=True)
+ out(' }')
out('}')
-
backend.generate_end(events, group)
out('#endif /* TRACE_%s_GENERATED_TRACERS_H */' % group.upper())
diff --git a/scripts/tracetool/format/log_stap.py b/scripts/tracetool/format/log_stap.py
index 710d62b..259303a 100644
--- a/scripts/tracetool/format/log_stap.py
+++ b/scripts/tracetool/format/log_stap.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file that printfs log messages (DTrace with SystemTAP only).
@@ -18,8 +18,6 @@ from tracetool.backend.dtrace import binary, probeprefix
from tracetool.backend.simple import is_string
from tracetool.format.stap import stap_escape
-def global_var_name(name):
- return probeprefix().replace(".", "_") + "_" + name
STATE_SKIP = 0
STATE_LITERAL = 1
diff --git a/scripts/tracetool/format/rs.py b/scripts/tracetool/format/rs.py
new file mode 100644
index 0000000..32ac4e5
--- /dev/null
+++ b/scripts/tracetool/format/rs.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+"""
+trace-DIR.rs
+"""
+
+__author__ = "Tanish Desai <tanishdesai37@gmail.com>"
+__copyright__ = "Copyright 2025, Tanish Desai <tanishdesai37@gmail.com>"
+__license__ = "GPL version 2 or (at your option) any later version"
+
+__maintainer__ = "Stefan Hajnoczi"
+__email__ = "stefanha@redhat.com"
+
+
+from tracetool import out
+
+
+def generate(events, backend, group):
+ out('// SPDX-License-Identifier: GPL-2.0-or-later',
+ '// This file is @generated by tracetool, do not edit.',
+ '',
+ '#[allow(unused_imports)]',
+ 'use std::ffi::c_char;',
+ '#[allow(unused_imports)]',
+ 'use util::bindings;',
+ '',
+ '#[inline(always)]',
+ 'fn trace_event_state_is_enabled(dstate: u16) -> bool {',
+ ' (unsafe { trace_events_enabled_count }) != 0 && dstate != 0',
+ '}',
+ '',
+ 'extern "C" {',
+ ' static mut trace_events_enabled_count: u32;',
+ '}',)
+
+ out('extern "C" {')
+
+ for e in events:
+ out(' static mut %s: u16;' % e.api(e.QEMU_DSTATE))
+ out('}')
+
+ backend.generate_begin(events, group)
+
+ for e in events:
+ out('',
+ '#[inline(always)]',
+ '#[allow(dead_code)]',
+ 'pub fn %(api)s(%(args)s)',
+ '{',
+ api=e.api(e.QEMU_TRACE),
+ args=e.args.rust_decl())
+
+ if "disable" not in e.properties:
+ backend.generate(e, group, check_trace_event_get_state=False)
+ if backend.check_trace_event_get_state:
+ event_id = 'TRACE_' + e.name.upper()
+ out(' if trace_event_state_is_enabled(unsafe { _%(event_id)s_DSTATE}) {',
+ event_id = event_id,
+ api=e.api())
+ backend.generate(e, group, check_trace_event_get_state=True)
+ out(' }')
+ out('}')
+
+ backend.generate_end(events, group)
diff --git a/scripts/tracetool/format/simpletrace_stap.py b/scripts/tracetool/format/simpletrace_stap.py
index 7297113..c7bde97 100644
--- a/scripts/tracetool/format/simpletrace_stap.py
+++ b/scripts/tracetool/format/simpletrace_stap.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file that outputs simpletrace binary traces (DTrace with SystemTAP only).
@@ -17,8 +17,6 @@ from tracetool.backend.dtrace import probeprefix
from tracetool.backend.simple import is_string
from tracetool.format.stap import stap_escape
-def global_var_name(name):
- return probeprefix().replace(".", "_") + "_" + name
def generate(events, backend, group):
out('/* This file is autogenerated by tracetool, do not edit. */',
diff --git a/scripts/tracetool/format/stap.py b/scripts/tracetool/format/stap.py
index 4d77fbc..285c920 100644
--- a/scripts/tracetool/format/stap.py
+++ b/scripts/tracetool/format/stap.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
Generate .stp file (DTrace with SystemTAP only).
diff --git a/scripts/tracetool/format/ust_events_c.py b/scripts/tracetool/format/ust_events_c.py
index 569754a..074226b 100644
--- a/scripts/tracetool/format/ust_events_c.py
+++ b/scripts/tracetool/format/ust_events_c.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-ust.c
diff --git a/scripts/tracetool/format/ust_events_h.py b/scripts/tracetool/format/ust_events_h.py
index 2a31fef..cee7970 100644
--- a/scripts/tracetool/format/ust_events_h.py
+++ b/scripts/tracetool/format/ust_events_h.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
"""
trace/generated-ust-provider.h
diff --git a/stubs/cpu-destroy-address-spaces.c b/stubs/cpu-destroy-address-spaces.c
new file mode 100644
index 0000000..dc6813f
--- /dev/null
+++ b/stubs/cpu-destroy-address-spaces.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "exec/cpu-common.h"
+
+/*
+ * user-mode CPUs never create address spaces with
+ * cpu_address_space_init(), so the cleanup function doesn't
+ * need to do anything. We need this stub because cpu-common.c
+ * is built-once so it can't #ifndef CONFIG_USER around the
+ * call; the real function is in physmem.c which is system-only.
+ */
+void cpu_destroy_address_spaces(CPUState *cpu)
+{
+}
diff --git a/stubs/meson.build b/stubs/meson.build
index cef046e..5d57746 100644
--- a/stubs/meson.build
+++ b/stubs/meson.build
@@ -55,6 +55,7 @@ endif
if have_user
# Symbols that are used by hw/core.
stub_ss.add(files('cpu-synchronize-state.c'))
+ stub_ss.add(files('cpu-destroy-address-spaces.c'))
# Stubs for QAPI events. Those can always be included in the build, but
# they are not built at all for --disable-system builds.
diff --git a/system/memory.c b/system/memory.c
index cf8cad6..fe8b28a 100644
--- a/system/memory.c
+++ b/system/memory.c
@@ -3278,7 +3278,14 @@ static void do_address_space_destroy(AddressSpace *as)
memory_region_unref(as->root);
}
-void address_space_destroy(AddressSpace *as)
+static void do_address_space_destroy_free(AddressSpace *as)
+{
+ do_address_space_destroy(as);
+ g_free(as);
+}
+
+/* Detach address space from global view, notify all listeners */
+static void address_space_detach(AddressSpace *as)
{
MemoryRegion *root = as->root;
@@ -3293,9 +3300,20 @@ void address_space_destroy(AddressSpace *as)
* values to expire before freeing the data.
*/
as->root = root;
+}
+
+void address_space_destroy(AddressSpace *as)
+{
+ address_space_detach(as);
call_rcu(as, do_address_space_destroy, rcu);
}
+void address_space_destroy_free(AddressSpace *as)
+{
+ address_space_detach(as);
+ call_rcu(as, do_address_space_destroy_free, rcu);
+}
+
static const char *memory_region_type(MemoryRegion *mr)
{
if (mr->alias) {
diff --git a/system/physmem.c b/system/physmem.c
index ae8ecd5..dbb2a4e 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -795,7 +795,6 @@ void cpu_address_space_init(CPUState *cpu, int asidx,
if (!cpu->cpu_ases) {
cpu->cpu_ases = g_new0(CPUAddressSpace, cpu->num_ases);
- cpu->cpu_ases_count = cpu->num_ases;
}
newas = &cpu->cpu_ases[asidx];
@@ -809,30 +808,29 @@ void cpu_address_space_init(CPUState *cpu, int asidx,
}
}
-void cpu_address_space_destroy(CPUState *cpu, int asidx)
+void cpu_destroy_address_spaces(CPUState *cpu)
{
CPUAddressSpace *cpuas;
+ int asidx;
assert(cpu->cpu_ases);
- assert(asidx >= 0 && asidx < cpu->num_ases);
- cpuas = &cpu->cpu_ases[asidx];
- if (tcg_enabled()) {
- memory_listener_unregister(&cpuas->tcg_as_listener);
- }
+ /* convenience alias just points to some cpu_ases[n] */
+ cpu->as = NULL;
- address_space_destroy(cpuas->as);
- g_free_rcu(cpuas->as, rcu);
-
- if (asidx == 0) {
- /* reset the convenience alias for address space 0 */
- cpu->as = NULL;
+ for (asidx = 0; asidx < cpu->num_ases; asidx++) {
+ cpuas = &cpu->cpu_ases[asidx];
+ if (!cpuas->as) {
+ /* This index was never initialized; no deinit needed */
+ continue;
+ }
+ if (tcg_enabled()) {
+ memory_listener_unregister(&cpuas->tcg_as_listener);
+ }
+ g_clear_pointer(&cpuas->as, address_space_destroy_free);
}
- if (--cpu->cpu_ases_count == 0) {
- g_free(cpu->cpu_ases);
- cpu->cpu_ases = NULL;
- }
+ g_clear_pointer(&cpu->cpu_ases, g_free);
}
AddressSpace *cpu_get_address_space(CPUState *cpu, int asidx)
diff --git a/system/vl.c b/system/vl.c
index 00f3694..646239e 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -3837,6 +3837,8 @@ void qemu_init(int argc, char **argv)
}
qemu_init_displays();
accel_setup_post(current_machine);
- os_setup_post();
+ if (migrate_mode() != MIG_MODE_CPR_EXEC) {
+ os_setup_post();
+ }
resume_mux_open();
}
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index d055ddf..a877018 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -604,7 +604,7 @@ static void riscv_cpu_dump_state(CPUState *cs, FILE *f, int flags)
}
}
}
- if (riscv_has_ext(env, RVV) && (flags & CPU_DUMP_VPU)) {
+ if (riscv_cpu_cfg(env)->ext_zve32x && (flags & CPU_DUMP_VPU)) {
static const int dump_rvv_csrs[] = {
CSR_VSTART,
CSR_VXSAT,
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 4a862da..2c22664 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -592,6 +592,7 @@ static inline int riscv_has_ext(CPURISCVState *env, target_ulong ext)
extern const char * const riscv_int_regnames[];
extern const char * const riscv_int_regnamesh[];
extern const char * const riscv_fpr_regnames[];
+extern const char * const riscv_rvv_regnames[];
const char *riscv_cpu_get_trap_name(target_ulong cause, bool async);
int riscv_cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cs,
@@ -873,7 +874,7 @@ static inline void riscv_csr_write(CPURISCVState *env, int csrno,
static inline target_ulong riscv_csr_read(CPURISCVState *env, int csrno)
{
target_ulong val = 0;
- riscv_csrrw(env, csrno, &val, 0, 0, 0);
+ riscv_csrr(env, csrno, &val);
return val;
}
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index 3c8989f..5c91658 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -203,6 +203,8 @@ static RISCVException cfi_ss(CPURISCVState *env, int csrno)
#if !defined(CONFIG_USER_ONLY)
if (env->debugger) {
return RISCV_EXCP_NONE;
+ } else if (env->virt_enabled) {
+ return RISCV_EXCP_VIRT_INSTRUCTION_FAULT;
}
#endif
return RISCV_EXCP_ILLEGAL_INST;
@@ -2003,7 +2005,8 @@ static RISCVException write_mstatus(CPURISCVState *env, int csrno,
if (riscv_has_ext(env, RVF)) {
mask |= MSTATUS_FS;
}
- if (riscv_has_ext(env, RVV)) {
+
+ if (riscv_cpu_cfg(env)->ext_zve32x) {
mask |= MSTATUS_VS;
}
diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index f712b1c..b785456 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -1101,14 +1101,14 @@ DEF_HELPER_6(vslidedown_vx_b, void, ptr, ptr, tl, ptr, env, i32)
DEF_HELPER_6(vslidedown_vx_h, void, ptr, ptr, tl, ptr, env, i32)
DEF_HELPER_6(vslidedown_vx_w, void, ptr, ptr, tl, ptr, env, i32)
DEF_HELPER_6(vslidedown_vx_d, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1up_vx_b, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1up_vx_h, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1up_vx_w, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1up_vx_d, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1down_vx_b, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1down_vx_h, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1down_vx_w, void, ptr, ptr, tl, ptr, env, i32)
-DEF_HELPER_6(vslide1down_vx_d, void, ptr, ptr, tl, ptr, env, i32)
+DEF_HELPER_6(vslide1up_vx_b, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1up_vx_h, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1up_vx_w, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1up_vx_d, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1down_vx_b, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1down_vx_h, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1down_vx_w, void, ptr, ptr, i64, ptr, env, i32)
+DEF_HELPER_6(vslide1down_vx_d, void, ptr, ptr, i64, ptr, env, i32)
DEF_HELPER_6(vfslide1up_vf_h, void, ptr, ptr, i64, ptr, env, i32)
DEF_HELPER_6(vfslide1up_vf_w, void, ptr, ptr, i64, ptr, env, i32)
@@ -1284,3 +1284,8 @@ DEF_HELPER_4(vgmul_vv, void, ptr, ptr, env, i32)
DEF_HELPER_5(vsm4k_vi, void, ptr, ptr, i32, env, i32)
DEF_HELPER_4(vsm4r_vv, void, ptr, ptr, env, i32)
DEF_HELPER_4(vsm4r_vs, void, ptr, ptr, env, i32)
+
+/* CFI (zicfiss) helpers */
+#ifndef CONFIG_USER_ONLY
+DEF_HELPER_1(ssamoswap_disabled, void, env)
+#endif
diff --git a/target/riscv/insn_trans/trans_rvv.c.inc b/target/riscv/insn_trans/trans_rvv.c.inc
index 71f98fb..f4b5460 100644
--- a/target/riscv/insn_trans/trans_rvv.c.inc
+++ b/target/riscv/insn_trans/trans_rvv.c.inc
@@ -3561,7 +3561,6 @@ static bool slideup_check(DisasContext *s, arg_rmrr *a)
}
GEN_OPIVX_TRANS(vslideup_vx, slideup_check)
-GEN_OPIVX_TRANS(vslide1up_vx, slideup_check)
GEN_OPIVI_TRANS(vslideup_vi, IMM_ZX, vslideup_vx, slideup_check)
static bool slidedown_check(DisasContext *s, arg_rmrr *a)
@@ -3572,9 +3571,56 @@ static bool slidedown_check(DisasContext *s, arg_rmrr *a)
}
GEN_OPIVX_TRANS(vslidedown_vx, slidedown_check)
-GEN_OPIVX_TRANS(vslide1down_vx, slidedown_check)
GEN_OPIVI_TRANS(vslidedown_vi, IMM_ZX, vslidedown_vx, slidedown_check)
+typedef void gen_helper_vslide1_vx(TCGv_ptr, TCGv_ptr, TCGv_i64, TCGv_ptr,
+ TCGv_env, TCGv_i32);
+
+#define GEN_OPIVX_VSLIDE1_TRANS(NAME, CHECK) \
+static bool trans_##NAME(DisasContext *s, arg_rmrr *a) \
+{ \
+ if (CHECK(s, a)) { \
+ static gen_helper_vslide1_vx * const fns[4] = { \
+ gen_helper_##NAME##_b, gen_helper_##NAME##_h, \
+ gen_helper_##NAME##_w, gen_helper_##NAME##_d, \
+ }; \
+ \
+ TCGv_ptr dest, src2, mask; \
+ TCGv_i64 src1; \
+ TCGv_i32 desc; \
+ uint32_t data = 0; \
+ \
+ dest = tcg_temp_new_ptr(); \
+ mask = tcg_temp_new_ptr(); \
+ src2 = tcg_temp_new_ptr(); \
+ src1 = tcg_temp_new_i64(); \
+ \
+ data = FIELD_DP32(data, VDATA, VM, a->vm); \
+ data = FIELD_DP32(data, VDATA, LMUL, s->lmul); \
+ data = FIELD_DP32(data, VDATA, VTA, s->vta); \
+ data = FIELD_DP32(data, VDATA, VTA_ALL_1S, s->cfg_vta_all_1s); \
+ data = FIELD_DP32(data, VDATA, VMA, s->vma); \
+ desc = tcg_constant_i32(simd_desc(s->cfg_ptr->vlenb, \
+ s->cfg_ptr->vlenb, data)); \
+ \
+ tcg_gen_addi_ptr(dest, tcg_env, vreg_ofs(s, a->rd)); \
+ tcg_gen_addi_ptr(src2, tcg_env, vreg_ofs(s, a->rs2)); \
+ tcg_gen_addi_ptr(mask, tcg_env, vreg_ofs(s, 0)); \
+ tcg_gen_ext_tl_i64(src1, get_gpr(s, a->rs1, EXT_SIGN)); \
+ \
+ fns[s->sew](dest, mask, src1, src2, tcg_env, desc); \
+ \
+ tcg_gen_movi_tl(cpu_vstart, 0); \
+ finalize_rvv_inst(s); \
+ \
+ return true; \
+ } \
+ return false; \
+}
+
+GEN_OPIVX_VSLIDE1_TRANS(vslide1up_vx, slideup_check)
+GEN_OPIVX_VSLIDE1_TRANS(vslide1down_vx, slidedown_check)
+
/* Vector Floating-Point Slide Instructions */
static bool fslideup_check(DisasContext *s, arg_rmrr *a)
{
diff --git a/target/riscv/insn_trans/trans_rvzce.c.inc b/target/riscv/insn_trans/trans_rvzce.c.inc
index c77c2b9..dd15af0 100644
--- a/target/riscv/insn_trans/trans_rvzce.c.inc
+++ b/target/riscv/insn_trans/trans_rvzce.c.inc
@@ -88,13 +88,13 @@ static bool trans_c_lbu(DisasContext *ctx, arg_c_lbu *a)
static bool trans_c_lhu(DisasContext *ctx, arg_c_lhu *a)
{
REQUIRE_ZCB(ctx);
- return gen_load(ctx, a, MO_UW);
+ return gen_load(ctx, a, MO_TEUW);
}
static bool trans_c_lh(DisasContext *ctx, arg_c_lh *a)
{
REQUIRE_ZCB(ctx);
- return gen_load(ctx, a, MO_SW);
+ return gen_load(ctx, a, MO_TESW);
}
static bool trans_c_sb(DisasContext *ctx, arg_c_sb *a)
@@ -106,7 +106,7 @@ static bool trans_c_sb(DisasContext *ctx, arg_c_sb *a)
static bool trans_c_sh(DisasContext *ctx, arg_c_sh *a)
{
REQUIRE_ZCB(ctx);
- return gen_store(ctx, a, MO_UW);
+ return gen_store(ctx, a, MO_TEUW);
}
#define X_S0 8
diff --git a/target/riscv/insn_trans/trans_rvzicfiss.c.inc b/target/riscv/insn_trans/trans_rvzicfiss.c.inc
index b0096ad..f4a1c12 100644
--- a/target/riscv/insn_trans/trans_rvzicfiss.c.inc
+++ b/target/riscv/insn_trans/trans_rvzicfiss.c.inc
@@ -40,6 +40,7 @@ static bool trans_sspopchk(DisasContext *ctx, arg_sspopchk *a)
tcg_gen_brcond_tl(TCG_COND_EQ, data, rs1, skip);
tcg_gen_st_tl(tcg_constant_tl(RISCV_EXCP_SW_CHECK_BCFI_TVAL),
tcg_env, offsetof(CPURISCVState, sw_check_code));
+ gen_update_pc(ctx, 0);
gen_helper_raise_exception(tcg_env,
tcg_constant_i32(RISCV_EXCP_SW_CHECK));
gen_set_label(skip);
@@ -90,7 +91,11 @@ static bool trans_ssamoswap_w(DisasContext *ctx, arg_amoswap_w *a)
}
if (!ctx->bcfi_enabled) {
+#ifndef CONFIG_USER_ONLY
+ gen_helper_ssamoswap_disabled(tcg_env);
+#else
return false;
+#endif
}
TCGv dest = dest_gpr(ctx, a->rd);
@@ -115,7 +120,11 @@ static bool trans_ssamoswap_d(DisasContext *ctx, arg_amoswap_w *a)
}
if (!ctx->bcfi_enabled) {
+#ifndef CONFIG_USER_ONLY
+ gen_helper_ssamoswap_disabled(tcg_env);
+#else
return false;
+#endif
}
TCGv dest = dest_gpr(ctx, a->rd);
diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c
index 5c19062..187c2c9 100644
--- a/target/riscv/kvm/kvm-cpu.c
+++ b/target/riscv/kvm/kvm-cpu.c
@@ -1588,7 +1588,7 @@ static void kvm_riscv_handle_sbi_dbcn(CPUState *cs, struct kvm_run *run)
* Handle the case where a 32 bit CPU is running in a
* 64 bit addressing env.
*/
- if (riscv_cpu_mxl(&cpu->env) == MXL_RV32) {
+ if (riscv_cpu_is_32bit(cpu)) {
addr |= (uint64_t)run->riscv_sbi.args[2] << 32;
}
diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index 1600ec4..18d790a 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -131,7 +131,8 @@ static bool vector_needed(void *opaque)
RISCVCPU *cpu = opaque;
CPURISCVState *env = &cpu->env;
- return riscv_has_ext(env, RVV);
+ return kvm_enabled() ? riscv_has_ext(env, RVV) :
+ riscv_cpu_cfg(env)->ext_zve32x;
}
static const VMStateDescription vmstate_vector = {
@@ -400,6 +401,30 @@ static const VMStateDescription vmstate_ssp = {
}
};
+static bool sstc_timer_needed(void *opaque)
+{
+ RISCVCPU *cpu = opaque;
+ CPURISCVState *env = &cpu->env;
+
+ if (!cpu->cfg.ext_sstc) {
+ return false;
+ }
+
+ return env->stimer != NULL || env->vstimer != NULL;
+}
+
+static const VMStateDescription vmstate_sstc = {
+ .name = "cpu/timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = sstc_timer_needed,
+ .fields = (const VMStateField[]) {
+ VMSTATE_TIMER_PTR(env.stimer, RISCVCPU),
+ VMSTATE_TIMER_PTR(env.vstimer, RISCVCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
const VMStateDescription vmstate_riscv_cpu = {
.name = "cpu",
.version_id = 10,
@@ -476,6 +501,7 @@ const VMStateDescription vmstate_riscv_cpu = {
&vmstate_elp,
&vmstate_ssp,
&vmstate_ctr,
+ &vmstate_sstc,
NULL
}
};
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index 110292e..8382aa9 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -717,4 +717,53 @@ target_ulong helper_hyp_hlvx_wu(CPURISCVState *env, target_ulong addr)
return cpu_ldl_code_mmu(env, addr, oi, ra);
}
+void helper_ssamoswap_disabled(CPURISCVState *env)
+{
+ int exception = RISCV_EXCP_ILLEGAL_INST;
+
+ /*
+ * Here we follow the RISC-V CFI spec [1] to implement the exception type
+ * of ssamoswap* instruction.
+ *
+ * [1] RISC-V CFI spec v1.0, ch2.7 Atomic Swap from a Shadow Stack Location
+ *
+ * Note: We have already checked some conditions in trans_* functions:
+ * 1. The effective priv mode is not M-mode.
+ * 2. The xSSE specific to the effictive priv mode is disabled.
+ */
+ if (!get_field(env->menvcfg, MENVCFG_SSE)) {
+ /*
+ * Disabled M-mode SSE always trigger illegal instruction when
+ * current priv mode is not M-mode.
+ */
+ exception = RISCV_EXCP_ILLEGAL_INST;
+ goto done;
+ }
+
+ if (!riscv_has_ext(env, RVS)) {
+ /* S-mode is not implemented */
+ exception = RISCV_EXCP_ILLEGAL_INST;
+ goto done;
+ } else if (env->virt_enabled) {
+ /*
+ * VU/VS-mode with disabled xSSE will trigger the virtual instruction
+ * exception.
+ */
+ exception = RISCV_EXCP_VIRT_INSTRUCTION_FAULT;
+ goto done;
+ } else {
+ /*
+ * U-mode with disabled S-mode SSE will trigger the illegal instruction
+ * exception.
+ *
+ * Note: S-mode is already handled in the disabled M-mode SSE case.
+ */
+ exception = RISCV_EXCP_ILLEGAL_INST;
+ goto done;
+ }
+
+done:
+ riscv_raise_exception(env, exception, GETPC());
+}
+
#endif /* !CONFIG_USER_ONLY */
diff --git a/target/riscv/riscv-qmp-cmds.c b/target/riscv/riscv-qmp-cmds.c
index 8a1856c..c499f9b 100644
--- a/target/riscv/riscv-qmp-cmds.c
+++ b/target/riscv/riscv-qmp-cmds.c
@@ -31,6 +31,10 @@
#include "qapi/qobject-input-visitor.h"
#include "qapi/visitor.h"
#include "qom/qom-qobject.h"
+#include "qemu/ctype.h"
+#include "qemu/qemu-print.h"
+#include "monitor/hmp.h"
+#include "monitor/hmp-target.h"
#include "system/kvm.h"
#include "system/tcg.h"
#include "cpu-qom.h"
@@ -240,3 +244,147 @@ CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type,
return expansion_info;
}
+
+/*
+ * We have way too many potential CSRs and regs being added
+ * regularly to register them in a static array.
+ *
+ * Declare an empty array instead, making get_monitor_def() use
+ * the target_get_monitor_def() API directly.
+ */
+const MonitorDef monitor_defs[] = { { } };
+const MonitorDef *target_monitor_defs(void)
+{
+ return monitor_defs;
+}
+
+static bool reg_is_ulong_integer(CPURISCVState *env, const char *name,
+ target_ulong *val, bool is_gprh)
+{
+ const char * const *reg_names;
+ target_ulong *vals;
+
+ if (is_gprh) {
+ reg_names = riscv_int_regnamesh;
+ vals = env->gprh;
+ } else {
+ reg_names = riscv_int_regnames;
+ vals = env->gpr;
+ }
+
+ for (int i = 0; i < 32; i++) {
+ g_autofree char *reg_name = g_strdup(reg_names[i]);
+ char *reg1 = strtok(reg_name, "/");
+ char *reg2 = strtok(NULL, "/");
+
+ if (strcasecmp(reg1, name) == 0 ||
+ (reg2 && strcasecmp(reg2, name) == 0)) {
+ *val = vals[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool reg_is_u64_fpu(CPURISCVState *env, const char *name, uint64_t *val)
+{
+ if (qemu_tolower(name[0]) != 'f') {
+ return false;
+ }
+
+ for (int i = 0; i < 32; i++) {
+ g_autofree char *reg_name = g_strdup(riscv_fpr_regnames[i]);
+ char *reg1 = strtok(reg_name, "/");
+ char *reg2 = strtok(NULL, "/");
+
+ if (strcasecmp(reg1, name) == 0 ||
+ (reg2 && strcasecmp(reg2, name) == 0)) {
+ *val = env->fpr[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool reg_is_vreg(const char *name)
+{
+ if (qemu_tolower(name[0]) != 'v' || strlen(name) > 3) {
+ return false;
+ }
+
+ for (int i = 0; i < 32; i++) {
+ if (strcasecmp(name, riscv_rvv_regnames[i]) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int target_get_monitor_def(CPUState *cs, const char *name, uint64_t *pval)
+{
+ CPURISCVState *env = &RISCV_CPU(cs)->env;
+ target_ulong val = 0;
+ uint64_t val64 = 0;
+ int i;
+
+ if (reg_is_ulong_integer(env, name, &val, false) ||
+ reg_is_ulong_integer(env, name, &val, true)) {
+ *pval = val;
+ return 0;
+ }
+
+ if (reg_is_u64_fpu(env, name, &val64)) {
+ *pval = val64;
+ return 0;
+ }
+
+ if (reg_is_vreg(name)) {
+ if (!riscv_cpu_cfg(env)->ext_zve32x) {
+ return -EINVAL;
+ }
+
+ qemu_printf("Unable to print the value of vector "
+ "vreg '%s' from this API\n", name);
+
+ /*
+ * We're returning 0 because returning -EINVAL triggers
+ * an 'unknown register' message in exp_unary() later,
+ * which feels ankward after our own error message.
+ */
+ *pval = 0;
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(csr_ops); i++) {
+ RISCVException res;
+ int csrno = i;
+
+ /*
+ * Early skip when possible since we're going
+ * through a lot of NULL entries.
+ */
+ if (csr_ops[csrno].predicate == NULL) {
+ continue;
+ }
+
+ if (strcasecmp(csr_ops[csrno].name, name) != 0) {
+ continue;
+ }
+
+ res = riscv_csrrw_debug(env, csrno, &val, 0, 0);
+
+ /*
+ * Rely on the smode, hmode, etc, predicates within csr.c
+ * to do the filtering of the registers that are present.
+ */
+ if (res == RISCV_EXCP_NONE) {
+ *pval = val;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index 143ab07..1150bd1 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -417,12 +417,21 @@ static void riscv_cpu_validate_misa_priv(CPURISCVState *env, Error **errp)
static void riscv_cpu_validate_v(CPURISCVState *env, RISCVCPUConfig *cfg,
Error **errp)
{
+ uint32_t min_vlen;
uint32_t vlen = cfg->vlenb << 3;
- if (vlen > RV_VLEN_MAX || vlen < 128) {
+ if (riscv_has_ext(env, RVV)) {
+ min_vlen = 128;
+ } else if (cfg->ext_zve64x) {
+ min_vlen = 64;
+ } else if (cfg->ext_zve32x) {
+ min_vlen = 32;
+ }
+
+ if (vlen > RV_VLEN_MAX || vlen < min_vlen) {
error_setg(errp,
"Vector extension implementation only supports VLEN "
- "in the range [128, %d]", RV_VLEN_MAX);
+ "in the range [%d, %d]", min_vlen, RV_VLEN_MAX);
return;
}
@@ -432,6 +441,12 @@ static void riscv_cpu_validate_v(CPURISCVState *env, RISCVCPUConfig *cfg,
"in the range [8, 64]");
return;
}
+
+ if (vlen < cfg->elen) {
+ error_setg(errp, "Vector extension implementation requires VLEN "
+ "to be greater than or equal to ELEN");
+ return;
+ }
}
static void riscv_cpu_disable_priv_spec_isa_exts(RISCVCPU *cpu)
@@ -661,7 +676,7 @@ void riscv_cpu_validate_set_extensions(RISCVCPU *cpu, Error **errp)
return;
}
- if (riscv_has_ext(env, RVV)) {
+ if (cpu->cfg.ext_zve32x) {
riscv_cpu_validate_v(env, &cpu->cfg, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index 9ddef2d..6fc06c7 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -24,6 +24,7 @@
#include "exec/helper-gen.h"
#include "exec/target_page.h"
#include "exec/translator.h"
+#include "accel/tcg/cpu-ldst.h"
#include "exec/translation-block.h"
#include "exec/log.h"
#include "semihosting/semihost.h"
@@ -1166,7 +1167,7 @@ static uint32_t opcode_at(DisasContextBase *dcbase, target_ulong pc)
CPUState *cpu = ctx->cs;
CPURISCVState *env = cpu_env(cpu);
- return translator_ldl(env, &ctx->base, pc);
+ return cpu_ldl_code(env, pc);
}
#define SS_MMU_INDEX(ctx) (ctx->mem_idx | MMU_IDX_SS_WRITE)
diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c
index 7c67d67..41ea223 100644
--- a/target/riscv/vector_helper.c
+++ b/target/riscv/vector_helper.c
@@ -5198,11 +5198,11 @@ GEN_VEXT_VSLIE1UP(16, H2)
GEN_VEXT_VSLIE1UP(32, H4)
GEN_VEXT_VSLIE1UP(64, H8)
-#define GEN_VEXT_VSLIDE1UP_VX(NAME, BITWIDTH) \
-void HELPER(NAME)(void *vd, void *v0, target_ulong s1, void *vs2, \
- CPURISCVState *env, uint32_t desc) \
-{ \
- vslide1up_##BITWIDTH(vd, v0, s1, vs2, env, desc); \
+#define GEN_VEXT_VSLIDE1UP_VX(NAME, BITWIDTH) \
+void HELPER(NAME)(void *vd, void *v0, uint64_t s1, void *vs2, \
+ CPURISCVState *env, uint32_t desc) \
+{ \
+ vslide1up_##BITWIDTH(vd, v0, s1, vs2, env, desc); \
}
/* vslide1up.vx vd, vs2, rs1, vm # vd[0]=x[rs1], vd[i+1] = vs2[i] */
@@ -5249,11 +5249,11 @@ GEN_VEXT_VSLIDE1DOWN(16, H2)
GEN_VEXT_VSLIDE1DOWN(32, H4)
GEN_VEXT_VSLIDE1DOWN(64, H8)
-#define GEN_VEXT_VSLIDE1DOWN_VX(NAME, BITWIDTH) \
-void HELPER(NAME)(void *vd, void *v0, target_ulong s1, void *vs2, \
- CPURISCVState *env, uint32_t desc) \
-{ \
- vslide1down_##BITWIDTH(vd, v0, s1, vs2, env, desc); \
+#define GEN_VEXT_VSLIDE1DOWN_VX(NAME, BITWIDTH) \
+void HELPER(NAME)(void *vd, void *v0, uint64_t s1, void *vs2, \
+ CPURISCVState *env, uint32_t desc) \
+{ \
+ vslide1down_##BITWIDTH(vd, v0, s1, vs2, env, desc); \
}
/* vslide1down.vx vd, vs2, rs1, vm # vd[i] = vs2[i+1], vd[vl-1]=x[rs1] */
diff --git a/tcg/region.c b/tcg/region.c
index 7ea0b37..2181267 100644
--- a/tcg/region.c
+++ b/tcg/region.c
@@ -832,13 +832,16 @@ void tcg_region_init(size_t tb_size, int splitwx, unsigned max_threads)
} else {
#ifdef CONFIG_POSIX
rc = mprotect(start, end - start, need_prot);
+ if (rc) {
+ error_report("mprotect of jit buffer: %s",
+ strerror(errno));
+ }
#else
g_assert_not_reached();
#endif
}
if (rc) {
- error_setg_errno(&error_fatal, errno,
- "mprotect of jit buffer");
+ exit(1);
}
}
if (have_prot != 0) {
diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
index 4fa8ac5..6b892ef 100644
--- a/tests/qtest/bios-tables-test.c
+++ b/tests/qtest/bios-tables-test.c
@@ -2864,7 +2864,7 @@ int main(int argc, char *argv[])
test_acpi_riscv64_virt_tcg_acpi_spcr);
}
} else if (strcmp(arch, "loongarch64") == 0) {
- if (has_tcg) {
+ if (has_tcg && qtest_has_machine("virt")) {
qtest_add_func("acpi/virt", test_acpi_loongarch64_virt);
qtest_add_func("acpi/virt/topology",
test_acpi_loongarch64_virt_topology);
diff --git a/tests/qtest/cpu-plug-test.c b/tests/qtest/cpu-plug-test.c
index 44d7046..0aa4ccc 100644
--- a/tests/qtest/cpu-plug-test.c
+++ b/tests/qtest/cpu-plug-test.c
@@ -190,7 +190,7 @@ int main(int argc, char **argv)
qtest_cb_for_every_machine(add_pseries_test_case, g_test_quick());
} else if (g_str_equal(arch, "s390x")) {
qtest_cb_for_every_machine(add_s390x_test_case, g_test_quick());
- } else if (g_str_equal(arch, "loongarch64")) {
+ } else if (g_str_equal(arch, "loongarch64") && qtest_has_machine("virt")) {
add_loongarch_test_case("virt");
}
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 94526b7..933d085 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -357,7 +357,7 @@ void qtest_remove_abrt_handler(void *data)
}
}
-static const char *qtest_qemu_binary(const char *var)
+const char *qtest_qemu_binary(const char *var)
{
const char *qemu_bin;
@@ -409,30 +409,30 @@ static pid_t qtest_create_process(char *cmd)
}
#endif /* _WIN32 */
-static QTestState *G_GNUC_PRINTF(2, 3) qtest_spawn_qemu(const char *qemu_bin,
- const char *fmt, ...)
+static QTestState *qtest_create_test_state(int pid)
{
- va_list ap;
QTestState *s = g_new0(QTestState, 1);
- const char *trace = g_getenv("QTEST_TRACE");
- g_autofree char *tracearg = trace ?
- g_strdup_printf("-trace %s ", trace) : g_strdup("");
- g_autoptr(GString) command = g_string_new("");
-
- va_start(ap, fmt);
- g_string_append_printf(command, CMD_EXEC "%s %s", qemu_bin, tracearg);
- g_string_append_vprintf(command, fmt, ap);
- va_end(ap);
+ s->qemu_pid = pid;
qtest_add_abrt_handler(kill_qemu_hook_func, s);
+ return s;
+}
+
+static QTestState *qtest_spawn_qemu(const char *qemu_bin, const char *args,
+ void *opaque)
+{
+ int pid;
+ g_autoptr(GString) command = g_string_new("");
+
+ g_string_printf(command, CMD_EXEC "%s %s", qemu_bin, args);
if (!silence_spawn_log) {
g_test_message("starting QEMU: %s", command->str);
}
#ifndef _WIN32
- s->qemu_pid = fork();
- if (s->qemu_pid == 0) {
+ pid = fork();
+ if (pid == 0) {
#ifdef __linux__
/*
* Although we register a ABRT handler to kill off QEMU
@@ -455,10 +455,10 @@ static QTestState *G_GNUC_PRINTF(2, 3) qtest_spawn_qemu(const char *qemu_bin,
exit(1);
}
#else
- s->qemu_pid = qtest_create_process(command->str);
+ pid = qtest_create_process(command->str);
#endif /* _WIN32 */
- return s;
+ return qtest_create_test_state(pid);
}
static char *qtest_socket_path(const char *suffix)
@@ -466,14 +466,48 @@ static char *qtest_socket_path(const char *suffix)
return g_strdup_printf("%s/qtest-%d.%s", g_get_tmp_dir(), getpid(), suffix);
}
+gchar *qtest_qemu_args(const char *extra_args)
+{
+ g_autofree gchar *socket_path = qtest_socket_path("sock");
+ g_autofree gchar *qmp_socket_path = qtest_socket_path("qmp");
+ const char *trace = g_getenv("QTEST_TRACE");
+ g_autofree char *tracearg = trace ? g_strdup_printf("-trace %s ", trace) :
+ g_strdup("");
+ gchar *args = g_strdup_printf(
+ "%s"
+ "-qtest unix:%s "
+ "-qtest-log %s "
+ "-chardev socket,path=%s,id=char0 "
+ "-mon chardev=char0,mode=control "
+ "-display none "
+ "-audio none "
+ "%s"
+ " -accel qtest",
+
+ tracearg,
+ socket_path,
+ getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
+ qmp_socket_path,
+ extra_args ?: "");
+
+ return args;
+}
+
+typedef QTestState *(*qtest_qemu_spawn_func)(const char *qemu_bin,
+ const char *extra_args,
+ void *opaque);
+
static QTestState *qtest_init_internal(const char *qemu_bin,
const char *extra_args,
- bool do_connect)
+ bool do_connect,
+ qtest_qemu_spawn_func spawn,
+ void *opaque)
{
QTestState *s;
int sock, qmpsock, i;
g_autofree gchar *socket_path = qtest_socket_path("sock");
g_autofree gchar *qmp_socket_path = qtest_socket_path("qmp");
+ g_autofree gchar *args = qtest_qemu_args(extra_args);
/*
* It's possible that if an earlier test run crashed it might
@@ -488,19 +522,7 @@ static QTestState *qtest_init_internal(const char *qemu_bin,
sock = init_socket(socket_path);
qmpsock = init_socket(qmp_socket_path);
- s = qtest_spawn_qemu(qemu_bin,
- "-qtest unix:%s "
- "-qtest-log %s "
- "-chardev socket,path=%s,id=char0 "
- "-mon chardev=char0,mode=control "
- "-display none "
- "-audio none "
- "%s"
- " -accel qtest",
- socket_path,
- getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
- qmp_socket_path,
- extra_args ?: "");
+ s = spawn(qemu_bin, args, opaque);
qtest_client_set_rx_handler(s, qtest_client_socket_recv_line);
qtest_client_set_tx_handler(s, qtest_client_socket_send);
@@ -555,7 +577,8 @@ void qtest_connect(QTestState *s)
QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
{
- return qtest_init_internal(qtest_qemu_binary(NULL), extra_args, true);
+ return qtest_init_internal(qtest_qemu_binary(NULL), extra_args, true,
+ qtest_spawn_qemu, NULL);
}
void qtest_qmp_handshake(QTestState *s, QList *capabilities)
@@ -578,7 +601,7 @@ QTestState *qtest_init_ext(const char *var, const char *extra_args,
QList *capabilities, bool do_connect)
{
QTestState *s = qtest_init_internal(qtest_qemu_binary(var), extra_args,
- do_connect);
+ do_connect, qtest_spawn_qemu, NULL);
if (do_connect) {
qtest_qmp_handshake(s, capabilities);
@@ -592,6 +615,25 @@ QTestState *qtest_init_ext(const char *var, const char *extra_args,
return s;
}
+static QTestState *qtest_attach_qemu(const char *qemu_bin,
+ const char *extra_args,
+ void *opaque)
+{
+ int pid = *(int *)opaque;
+ return qtest_create_test_state(pid);
+}
+
+QTestState *qtest_init_after_exec(QTestState *qts)
+{
+ void *opaque = (void *)&qts->qemu_pid;
+ QTestState *s;
+
+ s = qtest_init_internal(NULL, NULL, true, qtest_attach_qemu, opaque);
+ qts->qemu_pid = -1;
+ qtest_qmp_handshake(s, NULL);
+ return s;
+}
+
QTestState *qtest_init(const char *extra_args)
{
return qtest_init_ext(NULL, extra_args, NULL, true);
@@ -1630,7 +1672,8 @@ static void qtest_free_machine_list(struct MachInfo *machines)
static struct MachInfo *qtest_get_machines(const char *var)
{
static struct MachInfo *machines;
- static char *qemu_var;
+ static char *qemu_bin;
+ const char *new_qemu_bin;
QDict *response, *minfo;
QList *list;
const QListEntry *p;
@@ -1639,9 +1682,10 @@ static struct MachInfo *qtest_get_machines(const char *var)
QTestState *qts;
int idx;
- if (g_strcmp0(qemu_var, var)) {
- g_free(qemu_var);
- qemu_var = g_strdup(var);
+ new_qemu_bin = qtest_qemu_binary(var);
+ if (g_strcmp0(qemu_bin, new_qemu_bin)) {
+ g_free(qemu_bin);
+ qemu_bin = g_strdup(new_qemu_bin);
/* new qemu, clear the cache */
qtest_free_machine_list(machines);
@@ -1654,7 +1698,7 @@ static struct MachInfo *qtest_get_machines(const char *var)
silence_spawn_log = !g_test_verbose();
- qts = qtest_init_ext(qemu_var, "-machine none", NULL, true);
+ qts = qtest_init_ext(var, "-machine none", NULL, true);
response = qtest_qmp(qts, "{ 'execute': 'query-machines' }");
g_assert(response);
list = qdict_get_qlist(response, "return");
diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h
index fd27521..9c118c8 100644
--- a/tests/qtest/libqtest.h
+++ b/tests/qtest/libqtest.h
@@ -48,6 +48,31 @@ QTestState *qtest_initf(const char *fmt, ...) G_GNUC_PRINTF(1, 2);
QTestState *qtest_vinitf(const char *fmt, va_list ap) G_GNUC_PRINTF(1, 0);
/**
+ * qtest_qemu_binary:
+ * @var: environment variable name
+ *
+ * Look up @var and return its value as the qemu binary path.
+ * If @var is NULL, look up the default var name.
+ */
+const char *qtest_qemu_binary(const char *var);
+
+/**
+ * qtest_init_after_exec:
+ * @qts: the previous QEMU state
+ *
+ * Return a test state representing new QEMU after @qts exec's it.
+ */
+QTestState *qtest_init_after_exec(QTestState *qts);
+
+/**
+ * qtest_qemu_args:
+ * @extra_args: Other arguments to pass to QEMU.
+ *
+ * Return the command line used to start QEMU, sans binary.
+ */
+gchar *qtest_qemu_args(const char *extra_args);
+
+/**
* qtest_init:
* @extra_args: other arguments to pass to QEMU. CAUTION: these
* arguments are subject to word splitting and shell evaluation.
diff --git a/tests/qtest/migration/bootfile.c b/tests/qtest/migration/bootfile.c
index fac059d..479c432 100644
--- a/tests/qtest/migration/bootfile.c
+++ b/tests/qtest/migration/bootfile.c
@@ -68,3 +68,8 @@ char *bootfile_create(const char *arch, const char *dir, bool suspend_me)
return bootpath;
}
+
+char *bootfile_get(void)
+{
+ return bootpath;
+}
diff --git a/tests/qtest/migration/bootfile.h b/tests/qtest/migration/bootfile.h
index 6d6a673..96e784b 100644
--- a/tests/qtest/migration/bootfile.h
+++ b/tests/qtest/migration/bootfile.h
@@ -35,5 +35,6 @@
void bootfile_delete(void);
char *bootfile_create(const char *arch, const char *dir, bool suspend_me);
+char *bootfile_get(void);
#endif /* BOOTFILE_H */
diff --git a/tests/qtest/migration/cpr-tests.c b/tests/qtest/migration/cpr-tests.c
index 5e764a6..9388ad6 100644
--- a/tests/qtest/migration/cpr-tests.c
+++ b/tests/qtest/migration/cpr-tests.c
@@ -97,7 +97,10 @@ static void test_mode_transfer_common(bool incoming_defer)
.start_hook = test_mode_transfer_start,
};
- test_precopy_common(&args);
+ if (test_precopy_common(&args) < 0) {
+ close(cpr_sockfd);
+ unlink(cpr_path);
+ }
}
static void test_mode_transfer(void)
@@ -110,6 +113,138 @@ static void test_mode_transfer_defer(void)
test_mode_transfer_common(true);
}
+static void set_cpr_exec_args(QTestState *who, MigrateCommon *args)
+{
+ g_autofree char *qtest_from_args = NULL;
+ g_autofree char *from_args = NULL;
+ g_autofree char *to_args = NULL;
+ g_autofree char *exec_args = NULL;
+ g_auto(GStrv) argv = NULL;
+ char *from_str, *src, *dst;
+ int ret;
+
+ /*
+ * hide_stderr appends "2>/dev/null" to the command line, but cpr-exec
+ * passes the command-line words to execv, not to the shell, so suppress it
+ * here. fd 2 was already bound in the source VM, and execv preserves it.
+ */
+ g_assert(args->start.hide_stderr == false);
+
+ ret = migrate_args(&from_args, &to_args, args->listen_uri, &args->start);
+ g_assert(!ret);
+ qtest_from_args = qtest_qemu_args(from_args);
+
+ /*
+ * The generated args may have been formatted using "%s %s" with empty
+ * strings, which can produce consecutive spaces, which g_strsplit would
+ * convert into empty strings. Ditto for leading and trailing space.
+ * De-dup spaces to avoid that.
+ */
+
+ from_str = src = dst = g_strstrip(qtest_from_args);
+ do {
+ if (*src != ' ' || src[-1] != ' ') {
+ *dst++ = *src;
+ }
+ } while (*src++);
+
+ exec_args = g_strconcat(qtest_qemu_binary(migration_get_env()->qemu_dst),
+ " -incoming defer ", from_str, NULL);
+ argv = g_strsplit(exec_args, " ", -1);
+ migrate_set_parameter_strv(who, "cpr-exec-command", argv);
+}
+
+static void wait_for_migration_event(QTestState *who, const char *waitfor)
+{
+ QDict *rsp, *data;
+ char *status;
+ bool done = false;
+
+ while (!done) {
+ rsp = qtest_qmp_eventwait_ref(who, "MIGRATION");
+ g_assert(qdict_haskey(rsp, "data"));
+ data = qdict_get_qdict(rsp, "data");
+ g_assert(qdict_haskey(data, "status"));
+ status = g_strdup(qdict_get_str(data, "status"));
+ g_assert(strcmp(status, "failed"));
+ done = !strcmp(status, waitfor);
+ qobject_unref(rsp);
+ }
+}
+
+static void test_cpr_exec(MigrateCommon *args)
+{
+ QTestState *from, *to;
+ void *data_hook = NULL;
+ g_autofree char *connect_uri = g_strdup(args->connect_uri);
+ g_autofree char *filename = g_strdup_printf("%s/%s", tmpfs,
+ FILE_TEST_FILENAME);
+
+ if (migrate_start(&from, NULL, args->listen_uri, &args->start)) {
+ return;
+ }
+
+ /* Source and dest never run concurrently */
+ g_assert_false(args->live);
+
+ if (args->start_hook) {
+ data_hook = args->start_hook(from, NULL);
+ }
+
+ wait_for_serial("src_serial");
+ set_cpr_exec_args(from, args);
+ migrate_set_capability(from, "events", true);
+ migrate_qmp(from, NULL, connect_uri, NULL, "{}");
+ wait_for_migration_event(from, "completed");
+
+ to = qtest_init_after_exec(from);
+
+ qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
+ " 'arguments': { "
+ " 'channels': [ { 'channel-type': 'main',"
+ " 'addr': { 'transport': 'file',"
+ " 'filename': %s,"
+ " 'offset': 0 } } ] } }",
+ filename);
+ wait_for_migration_complete(to);
+
+ wait_for_resume(to, get_dst());
+ /* Device on target is still named src_serial because args do not change */
+ wait_for_serial("src_serial");
+
+ if (args->end_hook) {
+ args->end_hook(from, to, data_hook);
+ }
+
+ migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
+}
+
+static void *test_mode_exec_start(QTestState *from, QTestState *to)
+{
+ assert(!to);
+ migrate_set_parameter_str(from, "mode", "cpr-exec");
+ return NULL;
+}
+
+static void test_mode_exec(void)
+{
+ g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
+ FILE_TEST_FILENAME);
+ g_autofree char *listen_uri = g_strdup_printf("defer");
+
+ MigrateCommon args = {
+ .start.only_source = true,
+ .start.opts_source = "-machine aux-ram-share=on -nodefaults",
+ .start.memory_backend = "-object memory-backend-memfd,id=pc.ram,size=%s"
+ " -machine memory-backend=pc.ram",
+ .connect_uri = uri,
+ .listen_uri = listen_uri,
+ .start_hook = test_mode_exec_start,
+ };
+
+ test_cpr_exec(&args);
+}
+
void migration_test_add_cpr(MigrationTestEnv *env)
{
tmpfs = env->tmpfs;
@@ -132,5 +267,6 @@ void migration_test_add_cpr(MigrationTestEnv *env)
migration_test_add("/migration/mode/transfer", test_mode_transfer);
migration_test_add("/migration/mode/transfer/defer",
test_mode_transfer_defer);
+ migration_test_add("/migration/mode/exec", test_mode_exec);
}
}
diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c
index 407c902..a9be9c2 100644
--- a/tests/qtest/migration/framework.c
+++ b/tests/qtest/migration/framework.c
@@ -234,7 +234,7 @@ static void migrate_start_set_capabilities(QTestState *from, QTestState *to,
* to mimic as closer as that.
*/
migrate_set_capability(from, "events", true);
- if (!args->defer_target_connect) {
+ if (!args->defer_target_connect && to) {
migrate_set_capability(to, "events", true);
}
@@ -246,20 +246,26 @@ static void migrate_start_set_capabilities(QTestState *from, QTestState *to,
if (args->caps[MIGRATION_CAPABILITY_MULTIFD]) {
migrate_set_parameter_int(from, "multifd-channels",
MULTIFD_TEST_CHANNELS);
- migrate_set_parameter_int(to, "multifd-channels",
- MULTIFD_TEST_CHANNELS);
+ if (to) {
+ migrate_set_parameter_int(to, "multifd-channels",
+ MULTIFD_TEST_CHANNELS);
+ }
}
return;
}
-int migrate_start(QTestState **from, QTestState **to, const char *uri,
- MigrateStart *args)
+static char *test_shmem_path(void)
+{
+ return g_strdup_printf("/dev/shm/qemu-%d", getpid());
+}
+
+int migrate_args(char **from, char **to, const char *uri, MigrateStart *args)
{
/* options for source and target */
g_autofree gchar *arch_opts = NULL;
- g_autofree gchar *cmd_source = NULL;
- g_autofree gchar *cmd_target = NULL;
+ gchar *cmd_source = NULL;
+ gchar *cmd_target = NULL;
const gchar *ignore_stderr;
g_autofree char *shmem_opts = NULL;
g_autofree char *shmem_path = NULL;
@@ -268,23 +274,10 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri,
const char *memory_size;
const char *machine_alias, *machine_opts = "";
g_autofree char *machine = NULL;
- const char *bootpath;
- g_autoptr(QList) capabilities = migrate_start_get_qmp_capabilities(args);
+ const char *bootpath = bootfile_get();
g_autofree char *memory_backend = NULL;
const char *events;
- if (args->use_shmem) {
- if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) {
- g_test_skip("/dev/shm is not supported");
- return -1;
- }
- }
-
- dst_state = (QTestMigrationState) { };
- src_state = (QTestMigrationState) { };
- bootpath = bootfile_create(arch, tmpfs, args->suspend_me);
- src_state.suspend_me = args->suspend_me;
-
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
memory_size = "150M";
@@ -340,7 +333,7 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri,
}
if (args->use_shmem) {
- shmem_path = g_strdup_printf("/dev/shm/qemu-%d", getpid());
+ shmem_path = test_shmem_path();
shmem_opts = g_strdup_printf(
"-object memory-backend-file,id=mem0,size=%s"
",mem-path=%s,share=on -numa node,memdev=mem0",
@@ -381,12 +374,6 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri,
shmem_opts ? shmem_opts : "",
args->opts_source ? args->opts_source : "",
ignore_stderr);
- if (!args->only_target) {
- *from = qtest_init_ext(QEMU_ENV_SRC, cmd_source, capabilities, true);
- qtest_qmp_set_event_callback(*from,
- migrate_watch_for_events,
- &src_state);
- }
/*
* If the monitor connection is deferred, enable events on the command line
@@ -410,21 +397,62 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri,
shmem_opts ? shmem_opts : "",
args->opts_target ? args->opts_target : "",
ignore_stderr);
- *to = qtest_init_ext(QEMU_ENV_DST, cmd_target, capabilities,
- !args->defer_target_connect);
- qtest_qmp_set_event_callback(*to,
- migrate_watch_for_events,
- &dst_state);
+
+ *from = cmd_source;
+ *to = cmd_target;
+ return 0;
+}
+
+int migrate_start(QTestState **from, QTestState **to, const char *uri,
+ MigrateStart *args)
+{
+ g_autofree gchar *cmd_source = NULL;
+ g_autofree gchar *cmd_target = NULL;
+ g_autoptr(QList) capabilities = migrate_start_get_qmp_capabilities(args);
+
+ if (args->use_shmem) {
+ if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) {
+ g_test_skip("/dev/shm is not supported");
+ return -1;
+ }
+ }
+
+ dst_state = (QTestMigrationState) { };
+ src_state = (QTestMigrationState) { };
+ bootfile_create(qtest_get_arch(), tmpfs, args->suspend_me);
+ src_state.suspend_me = args->suspend_me;
+
+ if (migrate_args(&cmd_source, &cmd_target, uri, args)) {
+ return -1;
+ }
+
+ if (!args->only_target) {
+ *from = qtest_init_ext(QEMU_ENV_SRC, cmd_source, capabilities, true);
+ qtest_qmp_set_event_callback(*from,
+ migrate_watch_for_events,
+ &src_state);
+ }
+
+ if (!args->only_source) {
+ *to = qtest_init_ext(QEMU_ENV_DST, cmd_target, capabilities,
+ !args->defer_target_connect);
+ qtest_qmp_set_event_callback(*to,
+ migrate_watch_for_events,
+ &dst_state);
+ }
/*
* Remove shmem file immediately to avoid memory leak in test failed case.
* It's valid because QEMU has already opened this file
*/
if (args->use_shmem) {
+ g_autofree char *shmem_path = test_shmem_path();
unlink(shmem_path);
}
- migrate_start_set_capabilities(*from, *to, args);
+ migrate_start_set_capabilities(*from,
+ args->only_source ? NULL : *to,
+ args);
return 0;
}
@@ -736,7 +764,7 @@ void test_postcopy_recovery_common(MigrateCommon *args)
migrate_postcopy_complete(from, to, args);
}
-void test_precopy_common(MigrateCommon *args)
+int test_precopy_common(MigrateCommon *args)
{
QTestState *from, *to;
void *data_hook = NULL;
@@ -746,7 +774,7 @@ void test_precopy_common(MigrateCommon *args)
g_assert(!args->cpr_channel || args->connect_channels);
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
- return;
+ return -1;
}
if (args->start_hook) {
@@ -869,6 +897,8 @@ finish:
}
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
+
+ return 0;
}
static void file_dirty_offset_region(void)
@@ -994,6 +1024,11 @@ QTestMigrationState *get_src(void)
return &src_state;
}
+QTestMigrationState *get_dst(void)
+{
+ return &dst_state;
+}
+
MigrationTestEnv *migration_get_env(void)
{
static MigrationTestEnv *env;
diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h
index 01e425e..9bb584a 100644
--- a/tests/qtest/migration/framework.h
+++ b/tests/qtest/migration/framework.h
@@ -103,6 +103,8 @@ typedef struct {
*/
bool hide_stderr;
bool use_shmem;
+ /* only launch the source process */
+ bool only_source;
/* only launch the target process */
bool only_target;
/* Use dirty ring if true; dirty logging otherwise */
@@ -221,13 +223,15 @@ typedef struct {
void wait_for_serial(const char *side);
void migrate_prepare_for_dirty_mem(QTestState *from);
void migrate_wait_for_dirty_mem(QTestState *from, QTestState *to);
+
+int migrate_args(char **from, char **to, const char *uri, MigrateStart *args);
int migrate_start(QTestState **from, QTestState **to, const char *uri,
MigrateStart *args);
void migrate_end(QTestState *from, QTestState *to, bool test_dest);
void test_postcopy_common(MigrateCommon *args);
void test_postcopy_recovery_common(MigrateCommon *args);
-void test_precopy_common(MigrateCommon *args);
+int test_precopy_common(MigrateCommon *args);
void test_file_common(MigrateCommon *args, bool stop_src);
void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
QTestState *to,
@@ -235,6 +239,7 @@ void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
typedef struct QTestMigrationState QTestMigrationState;
QTestMigrationState *get_src(void);
+QTestMigrationState *get_dst(void);
#ifdef CONFIG_GNUTLS
void migration_test_add_tls(MigrationTestEnv *env);
diff --git a/tests/qtest/migration/migration-qmp.c b/tests/qtest/migration/migration-qmp.c
index 66dd369..c803fce 100644
--- a/tests/qtest/migration/migration-qmp.c
+++ b/tests/qtest/migration/migration-qmp.c
@@ -442,6 +442,22 @@ void migrate_set_parameter_str(QTestState *who, const char *parameter,
migrate_check_parameter_str(who, parameter, value);
}
+void migrate_set_parameter_strv(QTestState *who, const char *parameter,
+ char **strv)
+{
+ g_autofree char *args = g_strjoinv("\",\"", strv);
+ g_autoptr(GString) value = g_string_new("");
+ g_autofree char *command = NULL;
+
+ g_string_printf(value, "\"%s\"", args);
+
+ command = g_strdup_printf("{ 'execute': 'migrate-set-parameters',"
+ "'arguments': { %%s: [ %s ]}}",
+ value->str);
+
+ qtest_qmp_assert_success(who, command, parameter);
+}
+
static long long migrate_get_parameter_bool(QTestState *who,
const char *parameter)
{
diff --git a/tests/qtest/migration/migration-qmp.h b/tests/qtest/migration/migration-qmp.h
index faa8181..44482d2 100644
--- a/tests/qtest/migration/migration-qmp.h
+++ b/tests/qtest/migration/migration-qmp.h
@@ -34,6 +34,8 @@ void read_blocktime(QTestState *who);
void wait_for_migration_pass(QTestState *who, QTestMigrationState *src_state);
void migrate_set_parameter_str(QTestState *who, const char *parameter,
const char *value);
+void migrate_set_parameter_strv(QTestState *who, const char *parameter,
+ char **strv);
void migrate_set_parameter_bool(QTestState *who, const char *parameter,
int value);
void migrate_ensure_non_converge(QTestState *who);
diff --git a/tests/qtest/riscv-csr-test.c b/tests/qtest/riscv-csr-test.c
index ff5c29e..bb1b0ff 100644
--- a/tests/qtest/riscv-csr-test.c
+++ b/tests/qtest/riscv-csr-test.c
@@ -50,7 +50,9 @@ int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
- qtest_add_func("/cpu/csr", run_test_csr);
+ if (qtest_has_machine("virt")) {
+ qtest_add_func("/cpu/csr", run_test_csr);
+ }
return g_test_run();
}
diff --git a/tests/tracetool/ftrace.h b/tests/tracetool/ftrace.h
index fe22ea0..1dfe423 100644
--- a/tests/tracetool/ftrace.h
+++ b/tests/tracetool/ftrace.h
@@ -21,18 +21,10 @@ extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
static inline void trace_test_blah(void *context, const char *filename)
{
- {
- char ftrace_buf[MAX_TRACE_STRLEN];
- int unused __attribute__ ((unused));
- int trlen;
- if (trace_event_get_state(TRACE_TEST_BLAH)) {
+ if (trace_event_get_state(TRACE_TEST_BLAH)) {
#line 4 "trace-events"
- trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,
- "test_blah " "Blah context=%p filename=%s" "\n" , context, filename);
-#line 33 "ftrace.h"
- trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);
- unused = write(trace_marker_fd, ftrace_buf, trlen);
- }
+ ftrace_write("test_blah " "Blah context=%p filename=%s" "\n" , context, filename);
+#line 28 "ftrace.h"
}
}
@@ -42,18 +34,10 @@ static inline void trace_test_blah(void *context, const char *filename)
static inline void trace_test_wibble(void *context, int value)
{
- {
- char ftrace_buf[MAX_TRACE_STRLEN];
- int unused __attribute__ ((unused));
- int trlen;
- if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
+ if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
#line 5 "trace-events"
- trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,
- "test_wibble " "Wibble context=%p value=%d" "\n" , context, value);
-#line 54 "ftrace.h"
- trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);
- unused = write(trace_marker_fd, ftrace_buf, trlen);
- }
+ ftrace_write("test_wibble " "Wibble context=%p value=%d" "\n" , context, value);
+#line 41 "ftrace.h"
}
}
#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/ftrace.rs b/tests/tracetool/ftrace.rs
new file mode 100644
index 0000000..07b9259
--- /dev/null
+++ b/tests/tracetool/ftrace.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// This file is @generated by tracetool, do not edit.
+
+#[allow(unused_imports)]
+use std::ffi::c_char;
+#[allow(unused_imports)]
+use util::bindings;
+
+#[inline(always)]
+fn trace_event_state_is_enabled(dstate: u16) -> bool {
+ (unsafe { trace_events_enabled_count }) != 0 && dstate != 0
+}
+
+extern "C" {
+ static mut trace_events_enabled_count: u32;
+}
+extern "C" {
+ static mut _TRACE_TEST_BLAH_DSTATE: u16;
+ static mut _TRACE_TEST_WIBBLE_DSTATE: u16;
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_blah(_context: *mut (), _filename: &std::ffi::CStr)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_BLAH_DSTATE}) {
+ let format_string = c"Blah context=%p filename=%s";
+ unsafe {bindings::ftrace_write(format_string.as_ptr() as *const c_char, _context /* as *mut () */, _filename.as_ptr());}
+ }
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_wibble(_context: *mut (), _value: std::ffi::c_int)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_WIBBLE_DSTATE}) {
+ let format_string = c"Wibble context=%p value=%d";
+ unsafe {bindings::ftrace_write(format_string.as_ptr() as *const c_char, _context /* as *mut () */, _value /* as std::ffi::c_int */);}
+ }
+}
diff --git a/tests/tracetool/log.h b/tests/tracetool/log.h
index edcc7f9..c779587 100644
--- a/tests/tracetool/log.h
+++ b/tests/tracetool/log.h
@@ -21,10 +21,12 @@ extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
static inline void trace_test_blah(void *context, const char *filename)
{
- if (trace_event_get_state(TRACE_TEST_BLAH) && qemu_loglevel_mask(LOG_TRACE)) {
+ if (trace_event_get_state(TRACE_TEST_BLAH)) {
+ if (qemu_loglevel_mask(LOG_TRACE)) {
#line 4 "trace-events"
- qemu_log("test_blah " "Blah context=%p filename=%s" "\n", context, filename);
-#line 28 "log.h"
+ qemu_log("test_blah " "Blah context=%p filename=%s" "\n", context, filename);
+#line 29 "log.h"
+ }
}
}
@@ -34,10 +36,12 @@ static inline void trace_test_blah(void *context, const char *filename)
static inline void trace_test_wibble(void *context, int value)
{
- if (trace_event_get_state(TRACE_TEST_WIBBLE) && qemu_loglevel_mask(LOG_TRACE)) {
+ if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
+ if (qemu_loglevel_mask(LOG_TRACE)) {
#line 5 "trace-events"
- qemu_log("test_wibble " "Wibble context=%p value=%d" "\n", context, value);
-#line 41 "log.h"
+ qemu_log("test_wibble " "Wibble context=%p value=%d" "\n", context, value);
+#line 44 "log.h"
+ }
}
}
#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/log.rs b/tests/tracetool/log.rs
new file mode 100644
index 0000000..c191895
--- /dev/null
+++ b/tests/tracetool/log.rs
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// This file is @generated by tracetool, do not edit.
+
+#[allow(unused_imports)]
+use std::ffi::c_char;
+#[allow(unused_imports)]
+use util::bindings;
+
+#[inline(always)]
+fn trace_event_state_is_enabled(dstate: u16) -> bool {
+ (unsafe { trace_events_enabled_count }) != 0 && dstate != 0
+}
+
+extern "C" {
+ static mut trace_events_enabled_count: u32;
+}
+extern "C" {
+ static mut _TRACE_TEST_BLAH_DSTATE: u16;
+ static mut _TRACE_TEST_WIBBLE_DSTATE: u16;
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_blah(_context: *mut (), _filename: &std::ffi::CStr)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_BLAH_DSTATE}) {
+ let format_string = c"test_blah Blah context=%p filename=%s\n";
+ if (unsafe { bindings::qemu_loglevel } & bindings::LOG_TRACE) != 0 {
+ unsafe { bindings::qemu_log(format_string.as_ptr() as *const c_char, _context /* as *mut () */, _filename.as_ptr());}
+ }
+ }
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_wibble(_context: *mut (), _value: std::ffi::c_int)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_WIBBLE_DSTATE}) {
+ let format_string = c"test_wibble Wibble context=%p value=%d\n";
+ if (unsafe { bindings::qemu_loglevel } & bindings::LOG_TRACE) != 0 {
+ unsafe { bindings::qemu_log(format_string.as_ptr() as *const c_char, _context /* as *mut () */, _value /* as std::ffi::c_int */);}
+ }
+ }
+}
diff --git a/tests/tracetool/simple.rs b/tests/tracetool/simple.rs
new file mode 100644
index 0000000..9ee3949
--- /dev/null
+++ b/tests/tracetool/simple.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// This file is @generated by tracetool, do not edit.
+
+#[allow(unused_imports)]
+use std::ffi::c_char;
+#[allow(unused_imports)]
+use util::bindings;
+
+#[inline(always)]
+fn trace_event_state_is_enabled(dstate: u16) -> bool {
+ (unsafe { trace_events_enabled_count }) != 0 && dstate != 0
+}
+
+extern "C" {
+ static mut trace_events_enabled_count: u32;
+}
+extern "C" {
+ static mut _TRACE_TEST_BLAH_DSTATE: u16;
+ static mut _TRACE_TEST_WIBBLE_DSTATE: u16;
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_blah(_context: *mut (), _filename: &std::ffi::CStr)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_BLAH_DSTATE}) {
+ extern "C" { fn _simple_trace_test_blah(_context: *mut (), _filename: *const std::ffi::c_char); }
+ unsafe { _simple_trace_test_blah(_context, _filename.as_ptr()); }
+ }
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_wibble(_context: *mut (), _value: std::ffi::c_int)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_WIBBLE_DSTATE}) {
+ extern "C" { fn _simple_trace_test_wibble(_context: *mut (), _value: std::ffi::c_int); }
+ unsafe { _simple_trace_test_wibble(_context, _value); }
+ }
+}
diff --git a/tests/tracetool/syslog.rs b/tests/tracetool/syslog.rs
new file mode 100644
index 0000000..9d3675a
--- /dev/null
+++ b/tests/tracetool/syslog.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// This file is @generated by tracetool, do not edit.
+
+#[allow(unused_imports)]
+use std::ffi::c_char;
+#[allow(unused_imports)]
+use util::bindings;
+
+#[inline(always)]
+fn trace_event_state_is_enabled(dstate: u16) -> bool {
+ (unsafe { trace_events_enabled_count }) != 0 && dstate != 0
+}
+
+extern "C" {
+ static mut trace_events_enabled_count: u32;
+}
+extern "C" {
+ static mut _TRACE_TEST_BLAH_DSTATE: u16;
+ static mut _TRACE_TEST_WIBBLE_DSTATE: u16;
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_blah(_context: *mut (), _filename: &std::ffi::CStr)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_BLAH_DSTATE}) {
+ let format_string = c"Blah context=%p filename=%s";
+ unsafe {::trace::syslog(::trace::LOG_INFO, format_string.as_ptr() as *const c_char, _context /* as *mut () */, _filename.as_ptr());}
+ }
+}
+
+#[inline(always)]
+#[allow(dead_code)]
+pub fn trace_test_wibble(_context: *mut (), _value: std::ffi::c_int)
+{
+ if trace_event_state_is_enabled(unsafe { _TRACE_TEST_WIBBLE_DSTATE}) {
+ let format_string = c"Wibble context=%p value=%d";
+ unsafe {::trace::syslog(::trace::LOG_INFO, format_string.as_ptr() as *const c_char, _context /* as *mut () */, _value /* as std::ffi::c_int */);}
+ }
+}
diff --git a/tests/tracetool/tracetool-test.py b/tests/tracetool/tracetool-test.py
index 65430fd..786083a 100755
--- a/tests/tracetool/tracetool-test.py
+++ b/tests/tracetool/tracetool-test.py
@@ -14,6 +14,8 @@ def get_formats(backend):
"c",
"h",
]
+ if backend in {"ftrace", "log", "simple", "syslog"}:
+ formats += ["rs"]
if backend == "dtrace":
formats += [
"d",
diff --git a/tests/unit/test-error-report.c b/tests/unit/test-error-report.c
index 54319c8..0cbde3c 100644
--- a/tests/unit/test-error-report.c
+++ b/tests/unit/test-error-report.c
@@ -104,22 +104,6 @@ test_error_report_timestamp(void)
");
}
-static void
-test_error_warn(void)
-{
- if (g_test_subprocess()) {
- error_setg(&error_warn, "Testing &error_warn");
- return;
- }
-
- g_test_trap_subprocess(NULL, 0, 0);
- g_test_trap_assert_passed();
- g_test_trap_assert_stderr("\
-test-error-report: warning: Testing &error_warn*\
-");
-}
-
-
int
main(int argc, char *argv[])
{
@@ -133,7 +117,6 @@ main(int argc, char *argv[])
g_test_add_func("/error-report/glog", test_error_report_glog);
g_test_add_func("/error-report/once", test_error_report_once);
g_test_add_func("/error-report/timestamp", test_error_report_timestamp);
- g_test_add_func("/error-report/warn", test_error_warn);
return g_test_run();
}
diff --git a/tests/unit/test-vmstate.c b/tests/unit/test-vmstate.c
index 63f28f2..cadbab3 100644
--- a/tests/unit/test-vmstate.c
+++ b/tests/unit/test-vmstate.c
@@ -30,6 +30,7 @@
#include "../migration/savevm.h"
#include "qemu/module.h"
#include "io/channel-file.h"
+#include "qapi/error.h"
static int temp_fd;
@@ -66,9 +67,13 @@ static QEMUFile *open_test_file(bool write)
static void save_vmstate(const VMStateDescription *desc, void *obj)
{
QEMUFile *f = open_test_file(true);
+ Error *local_err = NULL;
/* Save file with vmstate */
- int ret = vmstate_save_state(f, desc, obj, NULL);
+ int ret = vmstate_save_state(f, desc, obj, NULL, &local_err);
+ if (ret) {
+ error_report_err(local_err);
+ }
g_assert(!ret);
qemu_put_byte(f, QEMU_VM_EOF);
g_assert(!qemu_file_get_error(f));
@@ -108,14 +113,16 @@ static int load_vmstate_one(const VMStateDescription *desc, void *obj,
{
QEMUFile *f;
int ret;
+ Error *local_err = NULL;
f = open_test_file(true);
qemu_put_buffer(f, wire, size);
qemu_fclose(f);
f = open_test_file(false);
- ret = vmstate_load_state(f, desc, obj, version);
+ ret = vmstate_load_state(f, desc, obj, version, &local_err);
if (ret) {
+ error_report_err(local_err);
g_assert(qemu_file_get_error(f));
} else{
g_assert(!qemu_file_get_error(f));
@@ -355,6 +362,8 @@ static const VMStateDescription vmstate_versioned = {
static void test_load_v1(void)
{
+ Error *local_err = NULL;
+ int ret;
uint8_t buf[] = {
0, 0, 0, 10, /* a */
0, 0, 0, 30, /* c */
@@ -365,7 +374,10 @@ static void test_load_v1(void)
QEMUFile *loading = open_test_file(false);
TestStruct obj = { .b = 200, .e = 500, .f = 600 };
- vmstate_load_state(loading, &vmstate_versioned, &obj, 1);
+ ret = vmstate_load_state(loading, &vmstate_versioned, &obj, 1, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
g_assert(!qemu_file_get_error(loading));
g_assert_cmpint(obj.a, ==, 10);
g_assert_cmpint(obj.b, ==, 200);
@@ -378,6 +390,8 @@ static void test_load_v1(void)
static void test_load_v2(void)
{
+ Error *local_err = NULL;
+ int ret;
uint8_t buf[] = {
0, 0, 0, 10, /* a */
0, 0, 0, 20, /* b */
@@ -391,7 +405,10 @@ static void test_load_v2(void)
QEMUFile *loading = open_test_file(false);
TestStruct obj;
- vmstate_load_state(loading, &vmstate_versioned, &obj, 2);
+ ret = vmstate_load_state(loading, &vmstate_versioned, &obj, 2, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
g_assert_cmpint(obj.a, ==, 10);
g_assert_cmpint(obj.b, ==, 20);
g_assert_cmpint(obj.c, ==, 30);
@@ -425,10 +442,15 @@ static const VMStateDescription vmstate_skipping = {
static void test_save_noskip(void)
{
+ Error *local_err = NULL;
QEMUFile *fsave = open_test_file(true);
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
.skip_c_e = false };
- int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL);
+ int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL,
+ &local_err);
+ if (ret) {
+ error_report_err(local_err);
+ }
g_assert(!ret);
g_assert(!qemu_file_get_error(fsave));
@@ -447,10 +469,15 @@ static void test_save_noskip(void)
static void test_save_skip(void)
{
+ Error *local_err = NULL;
QEMUFile *fsave = open_test_file(true);
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
.skip_c_e = true };
- int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL);
+ int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL,
+ &local_err);
+ if (ret) {
+ error_report_err(local_err);
+ }
g_assert(!ret);
g_assert(!qemu_file_get_error(fsave));
@@ -467,6 +494,8 @@ static void test_save_skip(void)
static void test_load_noskip(void)
{
+ Error *local_err = NULL;
+ int ret;
uint8_t buf[] = {
0, 0, 0, 10, /* a */
0, 0, 0, 20, /* b */
@@ -480,7 +509,10 @@ static void test_load_noskip(void)
QEMUFile *loading = open_test_file(false);
TestStruct obj = { .skip_c_e = false };
- vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
+ ret = vmstate_load_state(loading, &vmstate_skipping, &obj, 2, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
g_assert(!qemu_file_get_error(loading));
g_assert_cmpint(obj.a, ==, 10);
g_assert_cmpint(obj.b, ==, 20);
@@ -493,6 +525,8 @@ static void test_load_noskip(void)
static void test_load_skip(void)
{
+ Error *local_err = NULL;
+ int ret;
uint8_t buf[] = {
0, 0, 0, 10, /* a */
0, 0, 0, 20, /* b */
@@ -504,7 +538,10 @@ static void test_load_skip(void)
QEMUFile *loading = open_test_file(false);
TestStruct obj = { .skip_c_e = true, .c = 300, .e = 500 };
- vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
+ ret = vmstate_load_state(loading, &vmstate_skipping, &obj, 2, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
g_assert(!qemu_file_get_error(loading));
g_assert_cmpint(obj.a, ==, 10);
g_assert_cmpint(obj.b, ==, 20);
@@ -744,6 +781,8 @@ static void test_save_q(void)
static void test_load_q(void)
{
+ int ret;
+ Error *local_err = NULL;
TestQtailq obj_q = {
.i16 = -512,
.i32 = 70000,
@@ -773,7 +812,10 @@ static void test_load_q(void)
TestQtailq tgt;
QTAILQ_INIT(&tgt.q);
- vmstate_load_state(fload, &vmstate_q, &tgt, 1);
+ ret = vmstate_load_state(fload, &vmstate_q, &tgt, 1, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
char eof = qemu_get_byte(fload);
g_assert(!qemu_file_get_error(fload));
g_assert_cmpint(tgt.i16, ==, obj_q.i16);
@@ -1115,6 +1157,8 @@ static void diff_iommu(TestGTreeIOMMU *iommu1, TestGTreeIOMMU *iommu2)
static void test_gtree_load_domain(void)
{
+ Error *local_err = NULL;
+ int ret;
TestGTreeDomain *dest_domain = g_new0(TestGTreeDomain, 1);
TestGTreeDomain *orig_domain = create_first_domain();
QEMUFile *fload, *fsave;
@@ -1127,7 +1171,11 @@ static void test_gtree_load_domain(void)
fload = open_test_file(false);
- vmstate_load_state(fload, &vmstate_domain, dest_domain, 1);
+ ret = vmstate_load_state(fload, &vmstate_domain, dest_domain, 1,
+ &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
eof = qemu_get_byte(fload);
g_assert(!qemu_file_get_error(fload));
g_assert_cmpint(orig_domain->id, ==, dest_domain->id);
@@ -1230,6 +1278,8 @@ static void test_gtree_save_iommu(void)
static void test_gtree_load_iommu(void)
{
+ Error *local_err = NULL;
+ int ret;
TestGTreeIOMMU *dest_iommu = g_new0(TestGTreeIOMMU, 1);
TestGTreeIOMMU *orig_iommu = create_iommu();
QEMUFile *fsave, *fload;
@@ -1241,7 +1291,10 @@ static void test_gtree_load_iommu(void)
qemu_fclose(fsave);
fload = open_test_file(false);
- vmstate_load_state(fload, &vmstate_iommu, dest_iommu, 1);
+ ret = vmstate_load_state(fload, &vmstate_iommu, dest_iommu, 1, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
eof = qemu_get_byte(fload);
g_assert(!qemu_file_get_error(fload));
g_assert_cmpint(orig_iommu->id, ==, dest_iommu->id);
@@ -1363,6 +1416,8 @@ static void test_save_qlist(void)
static void test_load_qlist(void)
{
+ Error *local_err = NULL;
+ int ret;
QEMUFile *fsave, *fload;
TestQListContainer *orig_container = alloc_container();
TestQListContainer *dest_container = g_new0(TestQListContainer, 1);
@@ -1376,7 +1431,11 @@ static void test_load_qlist(void)
qemu_fclose(fsave);
fload = open_test_file(false);
- vmstate_load_state(fload, &vmstate_container, dest_container, 1);
+ ret = vmstate_load_state(fload, &vmstate_container, dest_container, 1,
+ &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ }
eof = qemu_get_byte(fload);
g_assert(!qemu_file_get_error(fload));
g_assert_cmpint(eof, ==, QEMU_VM_EOF);
diff --git a/trace/ftrace.c b/trace/ftrace.c
index 9749543..6875fae 100644
--- a/trace/ftrace.c
+++ b/trace/ftrace.c
@@ -38,6 +38,21 @@ static int find_mount(char *mount_point, const char *fstype)
return ret;
}
+void ftrace_write(const char *fmt, ...)
+{
+ char ftrace_buf[MAX_TRACE_STRLEN];
+ int unused __attribute__ ((unused));
+ int trlen;
+ va_list ap;
+
+ va_start(ap, fmt);
+ trlen = vsnprintf(ftrace_buf, MAX_TRACE_STRLEN, fmt, ap);
+ va_end(ap);
+
+ trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);
+ unused = write(trace_marker_fd, ftrace_buf, trlen);
+}
+
bool ftrace_init(void)
{
char mount_point[PATH_MAX];
diff --git a/trace/ftrace.h b/trace/ftrace.h
index cb5e35d..16c1228 100644
--- a/trace/ftrace.h
+++ b/trace/ftrace.h
@@ -8,5 +8,6 @@
extern int trace_marker_fd;
bool ftrace_init(void);
+G_GNUC_PRINTF(1, 2) void ftrace_write(const char *fmt, ...);
#endif /* TRACE_FTRACE_H */
diff --git a/trace/meson.build b/trace/meson.build
index 9c42a57..d89a0db 100644
--- a/trace/meson.build
+++ b/trace/meson.build
@@ -1,5 +1,5 @@
system_ss.add(files('control-target.c', 'trace-hmp-cmds.c'))
-
+trace_rs_targets = []
trace_events_files = []
foreach item : [ '.' ] + trace_events_subdirs + qapi_trace_events
if item in qapi_trace_events
@@ -24,6 +24,11 @@ foreach item : [ '.' ] + trace_events_subdirs + qapi_trace_events
input: trace_events_file,
command: [ tracetool, group, '--format=c', '@INPUT@', '@OUTPUT@' ],
depend_files: tracetool_depends)
+ trace_rs = custom_target(fmt.format('trace', 'rs'),
+ output: fmt.format('trace', 'rs'),
+ input: trace_events_file,
+ command: [ tracetool, group, '--format=rs', '@INPUT@', '@OUTPUT@' ],
+ depend_files: tracetool_depends)
if 'ust' in get_option('trace_backends')
trace_ust_h = custom_target(fmt.format('trace-ust', 'h'),
output: fmt.format('trace-ust', 'h'),
@@ -34,6 +39,7 @@ foreach item : [ '.' ] + trace_events_subdirs + qapi_trace_events
genh += trace_ust_h
endif
trace_ss.add(trace_h, trace_c)
+ trace_rs_targets += trace_rs
if 'dtrace' in get_option('trace_backends')
trace_dtrace = custom_target(fmt.format('trace-dtrace', 'dtrace'),
output: fmt.format('trace-dtrace', 'dtrace'),
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
index 42875b8..52e041e 100644
--- a/ui/dbus-listener.c
+++ b/ui/dbus-listener.c
@@ -214,24 +214,32 @@ static void dbus_update_gl_cb(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
- g_autoptr(GError) err = NULL;
+ g_autoptr(GError) gerr = NULL;
+#ifdef WIN32
+ Error *err = NULL;
+#endif
DBusDisplayListener *ddl = user_data;
bool success;
#ifdef CONFIG_GBM
success = qemu_dbus_display1_listener_call_update_dmabuf_finish(
- ddl->proxy, res, &err);
+ ddl->proxy, res, &gerr);
+ if (!success) {
+ error_report("Failed to call update: %s", gerr->message);
+ }
#endif
#ifdef WIN32
success = qemu_dbus_display1_listener_win32_d3d11_call_update_texture2d_finish(
- ddl->d3d11_proxy, res, &err);
- d3d_texture2d_acquire0(ddl->d3d_texture, &error_warn);
-#endif
-
+ ddl->d3d11_proxy, res, &gerr);
if (!success) {
- error_report("Failed to call update: %s", err->message);
+ error_report("Failed to call update: %s", gerr->message);
+ }
+
+ if (!d3d_texture2d_acquire0(ddl->d3d_texture, &err)) {
+ error_report_err(err);
}
+#endif
graphic_hw_gl_block(ddl->dcl.con, false);
g_object_unref(ddl);
diff --git a/ui/gtk.c b/ui/gtk.c
index e91d093..9a08cad 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1181,6 +1181,7 @@ static gboolean gd_touch_event(GtkWidget *widget, GdkEventTouch *touch,
void *opaque)
{
VirtualConsole *vc = opaque;
+ Error *err = NULL;
uint64_t num_slot = GPOINTER_TO_UINT(touch->sequence);
int type = -1;
@@ -1203,7 +1204,10 @@ static gboolean gd_touch_event(GtkWidget *widget, GdkEventTouch *touch,
console_handle_touch_event(vc->gfx.dcl.con, touch_slots,
num_slot, surface_width(vc->gfx.ds),
surface_height(vc->gfx.ds), touch->x,
- touch->y, type, &error_warn);
+ touch->y, type, &err);
+ if (err) {
+ warn_report_err(err);
+ }
return TRUE;
}
diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c
index ef4e71d..e46c623 100644
--- a/ui/qemu-pixman.c
+++ b/ui/qemu-pixman.c
@@ -288,7 +288,10 @@ qemu_pixman_shareable_free(qemu_pixman_shareable handle,
void *ptr, size_t size)
{
#ifdef WIN32
- qemu_win32_map_free(ptr, handle, &error_warn);
+ Error *err = NULL;
+
+ qemu_win32_map_free(ptr, handle, &err);
+ error_report_err(err);
#else
qemu_memfd_free(ptr, size, handle);
#endif
diff --git a/ui/spice-core.c b/ui/spice-core.c
index 2645e96..8a6050f 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -126,11 +126,13 @@ static void watch_update_mask(SpiceWatch *watch, int event_mask)
static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
{
SpiceWatch *watch;
-
#ifdef WIN32
+ g_autofree char *msg = NULL;
+
fd = _open_osfhandle(fd, _O_BINARY);
if (fd < 0) {
- error_setg_win32(&error_warn, WSAGetLastError(), "Couldn't associate a FD with the SOCKET");
+ msg = g_win32_error_message(WSAGetLastError());
+ warn_report("Couldn't associate a FD with the SOCKET: %s", msg);
return NULL;
}
#endif
diff --git a/ui/vdagent.c b/ui/vdagent.c
index c0746fe..ddb91e7 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -992,7 +992,8 @@ static int put_cbinfo(QEMUFile *f, void *pv, size_t size,
}
}
- return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc);
+ return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc,
+ &error_fatal);
}
static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
@@ -1001,6 +1002,7 @@ static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv);
struct CBInfoArray cbinfo = {};
int i, ret;
+ Error *local_err = NULL;
if (!have_clipboard(vd)) {
return 0;
@@ -1008,8 +1010,10 @@ static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
vdagent_clipboard_peer_register(vd);
- ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0);
+ ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0,
+ &local_err);
if (ret) {
+ error_report_err(local_err);
return ret;
}
diff --git a/util/aio-win32.c b/util/aio-win32.c
index 6583d5c..c6fbce6 100644
--- a/util/aio-win32.c
+++ b/util/aio-win32.c
@@ -121,7 +121,7 @@ void aio_set_fd_handler(AioContext *ctx,
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
event = event_notifier_get_handle(&ctx->notifier);
- qemu_socket_select(fd, event, bitmask, NULL);
+ qemu_socket_select_nofail(fd, event, bitmask);
}
if (old_node) {
aio_remove_fd_handler(ctx, old_node);
diff --git a/util/error.c b/util/error.c
index daea214..0ae0822 100644
--- a/util/error.c
+++ b/util/error.c
@@ -19,7 +19,6 @@
Error *error_abort;
Error *error_fatal;
-Error *error_warn;
static void error_handle(Error **errp, Error *err)
{
@@ -41,9 +40,7 @@ static void error_handle(Error **errp, Error *err)
error_report_err(err);
exit(1);
}
- if (errp == &error_warn) {
- warn_report_err(err);
- } else if (errp && !*errp) {
+ if (errp && !*errp) {
*errp = err;
} else {
error_free(err);
diff --git a/util/log.c b/util/log.c
index abdcb6b..41f78ce 100644
--- a/util/log.c
+++ b/util/log.c
@@ -44,7 +44,7 @@ static FILE *global_file;
static __thread FILE *thread_file;
static __thread Notifier qemu_log_thread_cleanup_notifier;
-int qemu_loglevel;
+unsigned qemu_loglevel;
static bool log_per_thread;
static GArray *debug_regions;
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
index 14cf94a..3c14b72 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
@@ -305,6 +305,15 @@ int qemu_socketpair(int domain, int type, int protocol, int sv[2])
return ret;
}
+void qemu_clear_cloexec(int fd)
+{
+ int f;
+ f = fcntl(fd, F_GETFD);
+ assert(f != -1);
+ f = fcntl(fd, F_SETFD, f & ~FD_CLOEXEC);
+ assert(f != -1);
+}
+
char *
qemu_get_local_state_dir(void)
{
diff --git a/util/oslib-win32.c b/util/oslib-win32.c
index b9ce2f9..839b8a4 100644
--- a/util/oslib-win32.c
+++ b/util/oslib-win32.c
@@ -182,7 +182,7 @@ bool qemu_set_blocking(int fd, bool block, Error **errp)
unsigned long opt = block ? 0 : 1;
if (block) {
- qemu_socket_unselect(fd, NULL);
+ qemu_socket_unselect_nofail(fd);
}
if (ioctlsocket(fd, FIONBIO, &opt) != NO_ERROR) {
@@ -219,6 +219,10 @@ void qemu_set_cloexec(int fd)
{
}
+void qemu_clear_cloexec(int fd)
+{
+}
+
int qemu_get_thread_id(void)
{
return GetCurrentThreadId();
@@ -293,10 +297,6 @@ bool qemu_socket_select(int sockfd, WSAEVENT hEventObject,
{
SOCKET s = _get_osfhandle(sockfd);
- if (errp == NULL) {
- errp = &error_warn;
- }
-
if (s == INVALID_SOCKET) {
error_setg(errp, "invalid socket fd=%d", sockfd);
return false;
@@ -315,6 +315,25 @@ bool qemu_socket_unselect(int sockfd, Error **errp)
return qemu_socket_select(sockfd, NULL, 0, errp);
}
+void qemu_socket_select_nofail(int sockfd, WSAEVENT hEventObject,
+ long lNetworkEvents)
+{
+ Error *err = NULL;
+
+ if (!qemu_socket_select(sockfd, hEventObject, lNetworkEvents, &err)) {
+ warn_report_err(err);
+ }
+}
+
+void qemu_socket_unselect_nofail(int sockfd)
+{
+ Error *err = NULL;
+
+ if (!qemu_socket_unselect(sockfd, &err)) {
+ warn_report_err(err);
+ }
+}
+
int qemu_socketpair(int domain, int type, int protocol, int sv[2])
{
struct sockaddr_un addr = {