diff options
161 files changed, 4303 insertions, 1948 deletions
diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml index 39da7698b0..4cc1923931 100644 --- a/.gitlab-ci.d/buildtest-template.yml +++ b/.gitlab-ci.d/buildtest-template.yml @@ -63,6 +63,7 @@ stage: test image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG script: + - export RUST_BACKTRACE=1 - source scripts/ci/gitlab-ci-section - section_start buildenv "Setting up to run tests" - scripts/git-submodule.sh update roms/SLOF diff --git a/MAINTAINERS b/MAINTAINERS index dbf39cfbb0..bf737eb6db 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1158,6 +1158,7 @@ F: hw/*/microbit*.c F: include/hw/*/nrf51*.h F: include/hw/*/microbit*.h F: tests/qtest/microbit-test.c +F: tests/functional/test_arm_microbit.py F: docs/system/arm/nrf.rst ARM PL011 Rust device @@ -1453,6 +1454,7 @@ F: include/hw/pci-host/uninorth.h F: include/hw/input/adb* F: pc-bios/qemu_vga.ndrv F: tests/functional/test_ppc_mac.py +F: tests/functional/test_ppc64_mac99.py Old World (g3beige) M: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> @@ -3461,6 +3463,7 @@ F: include/migration/ F: include/qemu/userfaultfd.h F: migration/ F: scripts/vmstate-static-checker.py +F: tests/functional/test_migration.py F: tests/vmstate-static-checker-data/ F: tests/qtest/migration/ F: tests/qtest/migration-* @@ -3631,6 +3634,7 @@ F: stubs/replay.c F: tests/avocado/replay_kernel.py F: tests/avocado/replay_linux.py F: tests/avocado/reverse_debugging.py +F: tests/functional/*replay*.py F: qapi/replay.json IOVA Tree diff --git a/accel/tcg/vcpu-state.h b/accel/tcg/vcpu-state.h index e407d914df..2e3464b5ee 100644 --- a/accel/tcg/vcpu-state.h +++ b/accel/tcg/vcpu-state.h @@ -1,6 +1,11 @@ /* - * SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> - * SPDX-FileCopyrightText: 2023 Linaro Ltd. + * TaskState helpers for QEMU + * + * Copyright (c) 2023 Linaro Ltd. + * + * Authors: + * Philippe Mathieu-Daudé + * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef ACCEL_TCG_VCPU_STATE_H diff --git a/backends/hostmem-epc.c b/backends/hostmem-epc.c index eb4b95dfd7..1fa2d031e4 100644 --- a/backends/hostmem-epc.c +++ b/backends/hostmem-epc.c @@ -36,7 +36,7 @@ sgx_epc_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) backend->aligned = true; name = object_get_canonical_path(OBJECT(backend)); - ram_flags = (backend->share ? RAM_SHARED : 0) | RAM_PROTECTED; + ram_flags = (backend->share ? RAM_SHARED : RAM_PRIVATE) | RAM_PROTECTED; return memory_region_init_ram_from_fd(&backend->mr, OBJECT(backend), name, backend->size, ram_flags, fd, 0, errp); } diff --git a/backends/hostmem-file.c b/backends/hostmem-file.c index 46321fda84..691a827819 100644 --- a/backends/hostmem-file.c +++ b/backends/hostmem-file.c @@ -82,7 +82,7 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) backend->aligned = true; name = host_memory_backend_get_name(backend); - ram_flags = backend->share ? RAM_SHARED : 0; + ram_flags = backend->share ? RAM_SHARED : RAM_PRIVATE; ram_flags |= fb->readonly ? RAM_READONLY_FD : 0; ram_flags |= fb->rom == ON_OFF_AUTO_ON ? RAM_READONLY : 0; ram_flags |= backend->reserve ? 0 : RAM_NORESERVE; diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c index d4d0620e6c..85daa1432c 100644 --- a/backends/hostmem-memfd.c +++ b/backends/hostmem-memfd.c @@ -17,6 +17,7 @@ #include "qemu/module.h" #include "qapi/error.h" #include "qom/object.h" +#include "migration/cpr.h" OBJECT_DECLARE_SIMPLE_TYPE(HostMemoryBackendMemfd, MEMORY_BACKEND_MEMFD) @@ -33,15 +34,19 @@ static bool memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) { HostMemoryBackendMemfd *m = MEMORY_BACKEND_MEMFD(backend); - g_autofree char *name = NULL; + g_autofree char *name = host_memory_backend_get_name(backend); + int fd = cpr_find_fd(name, 0); uint32_t ram_flags; - int fd; if (!backend->size) { error_setg(errp, "can't create backend with size 0"); return false; } + if (fd >= 0) { + goto have_fd; + } + fd = qemu_memfd_create(TYPE_MEMORY_BACKEND_MEMFD, backend->size, m->hugetlb, m->hugetlbsize, m->seal ? F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL : 0, @@ -49,10 +54,11 @@ memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) if (fd == -1) { return false; } + cpr_save_fd(name, 0, fd); +have_fd: backend->aligned = true; - name = host_memory_backend_get_name(backend); - ram_flags = backend->share ? RAM_SHARED : 0; + ram_flags = backend->share ? RAM_SHARED : RAM_PRIVATE; ram_flags |= backend->reserve ? 0 : RAM_NORESERVE; ram_flags |= backend->guest_memfd ? RAM_GUEST_MEMFD : 0; return memory_region_init_ram_from_fd(&backend->mr, OBJECT(backend), name, diff --git a/backends/hostmem-ram.c b/backends/hostmem-ram.c index 39aac6bf35..868ae6ca80 100644 --- a/backends/hostmem-ram.c +++ b/backends/hostmem-ram.c @@ -28,7 +28,7 @@ ram_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) } name = host_memory_backend_get_name(backend); - ram_flags = backend->share ? RAM_SHARED : 0; + ram_flags = backend->share ? RAM_SHARED : RAM_PRIVATE; ram_flags |= backend->reserve ? 0 : RAM_NORESERVE; ram_flags |= backend->guest_memfd ? RAM_GUEST_MEMFD : 0; return memory_region_init_ram_flags_nomigrate(&backend->mr, OBJECT(backend), diff --git a/backends/hostmem-shm.c b/backends/hostmem-shm.c index 5551ba78a6..f67ad2740b 100644 --- a/backends/hostmem-shm.c +++ b/backends/hostmem-shm.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include "system/hostmem.h" #include "qapi/error.h" +#include "migration/cpr.h" #define TYPE_MEMORY_BACKEND_SHM "memory-backend-shm" @@ -25,11 +26,9 @@ struct HostMemoryBackendShm { static bool shm_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) { - g_autoptr(GString) shm_name = g_string_new(NULL); - g_autofree char *backend_name = NULL; + g_autofree char *backend_name = host_memory_backend_get_name(backend); uint32_t ram_flags; - int fd, oflag; - mode_t mode; + int fd = cpr_find_fd(backend_name, 0); if (!backend->size) { error_setg(errp, "can't create shm backend with size 0"); @@ -41,48 +40,18 @@ shm_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) return false; } - /* - * Let's use `mode = 0` because we don't want other processes to open our - * memory unless we share the file descriptor with them. - */ - mode = 0; - oflag = O_RDWR | O_CREAT | O_EXCL; - backend_name = host_memory_backend_get_name(backend); - - /* - * Some operating systems allow creating anonymous POSIX shared memory - * objects (e.g. FreeBSD provides the SHM_ANON constant), but this is not - * defined by POSIX, so let's create a unique name. - * - * From Linux's shm_open(3) man-page: - * For portable use, a shared memory object should be identified - * by a name of the form /somename;" - */ - g_string_printf(shm_name, "/qemu-" FMT_pid "-shm-%s", getpid(), - backend_name); - - fd = shm_open(shm_name->str, oflag, mode); - if (fd < 0) { - error_setg_errno(errp, errno, - "failed to create POSIX shared memory"); - return false; + if (fd >= 0) { + goto have_fd; } - /* - * We have the file descriptor, so we no longer need to expose the - * POSIX shared memory object. However it will remain allocated as long as - * there are file descriptors pointing to it. - */ - shm_unlink(shm_name->str); - - if (ftruncate(fd, backend->size) == -1) { - error_setg_errno(errp, errno, - "failed to resize POSIX shared memory to %" PRIu64, - backend->size); - close(fd); + fd = qemu_shm_alloc(backend->size, errp); + if (fd < 0) { return false; } + cpr_save_fd(backend_name, 0, fd); +have_fd: + /* Let's do the same as memory-backend-ram,share=on would do. */ ram_flags = RAM_SHARED; ram_flags |= backend->reserve ? 0 : RAM_NORESERVE; diff --git a/block/vvfat.c b/block/vvfat.c index 8ffe8b3b9b..bfbcc5562c 100644 --- a/block/vvfat.c +++ b/block/vvfat.c @@ -403,7 +403,6 @@ static direntry_t *create_long_filename(BDRVVVFATState *s, const char *filename) { int number_of_entries, i; glong length; - direntry_t *entry; gunichar2 *longname = g_utf8_to_utf16(filename, -1, NULL, &length, NULL); if (!longname) { @@ -414,24 +413,24 @@ static direntry_t *create_long_filename(BDRVVVFATState *s, const char *filename) number_of_entries = DIV_ROUND_UP(length * 2, 26); for(i=0;i<number_of_entries;i++) { - entry=array_get_next(&(s->directory)); + direntry_t *entry=array_get_next(&(s->directory)); entry->attributes=0xf; entry->reserved[0]=0; entry->begin=0; entry->name[0]=(number_of_entries-i)|(i==0?0x40:0); } for(i=0;i<26*number_of_entries;i++) { + unsigned char *entry=array_get(&(s->directory),s->directory.next-1-(i/26)); int offset=(i%26); if(offset<10) offset=1+offset; else if(offset<22) offset=14+offset-10; else offset=28+offset-22; - entry=array_get(&(s->directory),s->directory.next-1-(i/26)); if (i >= 2 * length + 2) { - entry->name[offset] = 0xff; + entry[offset] = 0xff; } else if (i % 2 == 0) { - entry->name[offset] = longname[i / 2] & 0xff; + entry[offset] = longname[i / 2] & 0xff; } else { - entry->name[offset] = longname[i / 2] >> 8; + entry[offset] = longname[i / 2] >> 8; } } g_free(longname); diff --git a/docs/devel/migration/CPR.rst b/docs/devel/migration/CPR.rst index 63c36470cf..7897873c86 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, -cpr-reboot is the only available mode. +the cpr-reboot and cpr-transfer 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 @@ -53,7 +53,7 @@ RAM is copied to the migration URI. Outgoing: * Set the migration mode parameter to ``cpr-reboot``. * Set the ``x-ignore-shared`` capability if desired. - * Issue the ``migrate`` command. It is recommended the the URI be a + * Issue the ``migrate`` command. It is recommended 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. @@ -145,3 +145,183 @@ Caveats cpr-reboot mode may not be used with postcopy, background-snapshot, or COLO. + +cpr-transfer mode +----------------- + +This mode allows the user to transfer a guest to a new QEMU instance +on the same host with minimal guest pause time, by preserving guest +RAM in place, albeit with new virtual addresses in new QEMU. Devices +and their pinned memory pages will also be preserved in a future QEMU +release. + +The user starts new QEMU on the same host as old QEMU, with command- +line arguments to create the same machine, plus the ``-incoming`` +option for the main migration channel, like normal live migration. +In addition, the user adds a second -incoming option with channel +type ``cpr``. This CPR channel must support file descriptor transfer +with SCM_RIGHTS, i.e. it must be a UNIX domain socket. + +To initiate CPR, the user issues a migrate command to old QEMU, +adding a second migration channel of type ``cpr`` in the channels +argument. Old QEMU stops the VM, saves state to the migration +channels, and enters the postmigrate state. Execution resumes in +new QEMU. + +New QEMU reads the CPR channel before opening a monitor, hence +the CPR channel cannot be specified in the list of channels for a +migrate-incoming command. It may only be specified on the command +line. + +Usage +^^^^^ + +Memory backend objects must have the ``share=on`` attribute. + +The VM must be started with the ``-machine aux-ram-share=on`` +option. This causes implicit RAM blocks (those not described by +a memory-backend object) to be allocated by mmap'ing a memfd. +Examples include VGA and ROM. + +Outgoing: + * Set the migration mode parameter to ``cpr-transfer``. + * Issue the ``migrate`` command, containing a main channel and + a cpr channel. + +Incoming: + * Start new QEMU with two ``-incoming`` options. + * If the VM was running when the outgoing ``migrate`` command was + issued, then QEMU automatically resumes VM execution. + +Caveats +^^^^^^^ + +cpr-transfer mode may not be used with postcopy, background-snapshot, +or COLO. + +memory-backend-epc is not supported. + +The main incoming migration channel address cannot be a file type. + +If the main incoming channel address is an inet socket, then the port +cannot be 0 (meaning dynamically choose a port). + +When using ``-incoming defer``, you must issue the migrate command to +old QEMU before issuing any monitor commands to new QEMU, because new +QEMU blocks waiting to read from the cpr channel before starting its +monitor, and old QEMU does not write to the channel until the migrate +command is issued. However, new QEMU does not open and read the +main migration channel until you issue the migrate incoming command. + +Example 1: incoming channel +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In these examples, we simply restart the same version of QEMU, but +in a real scenario one would start new QEMU on the incoming side. +Note that new QEMU does not print the monitor prompt until old QEMU +has issued the migrate command. The outgoing side uses QMP because +HMP cannot specify a CPR channel. Some QMP responses are omitted for +brevity. + +:: + + Outgoing: Incoming: + + # qemu-kvm -qmp stdio + -object memory-backend-file,id=ram0,size=4G, + mem-path=/dev/shm/ram0,share=on -m 4G + -machine memory-backend=ram0 + -machine aux-ram-share=on + ... + # qemu-kvm -monitor stdio + -incoming tcp:0:44444 + -incoming '{"channel-type": "cpr", + "addr": { "transport": "socket", + "type": "unix", "path": "cpr.sock"}}' + ... + {"execute":"qmp_capabilities"} + + {"execute": "query-status"} + {"return": {"status": "running", + "running": true}} + + {"execute":"migrate-set-parameters", + "arguments":{"mode":"cpr-transfer"}} + + {"execute": "migrate", "arguments": { "channels": [ + {"channel-type": "main", + "addr": { "transport": "socket", "type": "inet", + "host": "0", "port": "44444" }}, + {"channel-type": "cpr", + "addr": { "transport": "socket", "type": "unix", + "path": "cpr.sock" }}]}} + + QEMU 10.0.50 monitor + (qemu) info status + VM status: running + + {"execute": "query-status"} + {"return": {"status": "postmigrate", + "running": false}} + +Example 2: incoming defer +^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example uses ``-incoming defer`` to hot plug a device before +accepting the main migration channel. Again note you must issue the +migrate command to old QEMU before you can issue any monitor +commands to new QEMU. + + +:: + + Outgoing: Incoming: + + # qemu-kvm -monitor stdio + -object memory-backend-file,id=ram0,size=4G, + mem-path=/dev/shm/ram0,share=on -m 4G + -machine memory-backend=ram0 + -machine aux-ram-share=on + ... + # qemu-kvm -monitor stdio + -incoming defer + -incoming '{"channel-type": "cpr", + "addr": { "transport": "socket", + "type": "unix", "path": "cpr.sock"}}' + ... + {"execute":"qmp_capabilities"} + + {"execute": "device_add", + "arguments": {"driver": "pcie-root-port"}} + + {"execute":"migrate-set-parameters", + "arguments":{"mode":"cpr-transfer"}} + + {"execute": "migrate", "arguments": { "channels": [ + {"channel-type": "main", + "addr": { "transport": "socket", "type": "inet", + "host": "0", "port": "44444" }}, + {"channel-type": "cpr", + "addr": { "transport": "socket", "type": "unix", + "path": "cpr.sock" }}]}} + + QEMU 10.0.50 monitor + (qemu) info status + VM status: paused (inmigrate) + (qemu) device_add pcie-root-port + (qemu) migrate_incoming tcp:0:44444 + (qemu) info status + VM status: running + + {"execute": "query-status"} + {"return": {"status": "postmigrate", + "running": false}} + +Futures +^^^^^^^ + +cpr-transfer mode is based on a capability to transfer open file +descriptors from old to new QEMU. In the future, descriptors for +vfio, iommufd, 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. diff --git a/docs/devel/testing/functional.rst b/docs/devel/testing/functional.rst index ae238ed3fc..ecc738922b 100644 --- a/docs/devel/testing/functional.rst +++ b/docs/devel/testing/functional.rst @@ -351,5 +351,13 @@ the code snippet below: Tests should not live in this state forever and should either be fixed or eventually removed. +QEMU_TEST_ALLOW_SLOW +^^^^^^^^^^^^^^^^^^^^ +Tests that have a very long runtime and might run into timeout issues +e.g. if the QEMU binary has been compiled with debugging options enabled. +To avoid these timeout issues by default and to save some precious CPU +cycles during normal testing, such tests are disabled by default unless +the QEMU_TEST_ALLOW_SLOW environment variable has been set. + .. _unittest: https://docs.python.org/3/library/unittest.html diff --git a/docs/system/cpu-models-x86.rst.inc b/docs/system/cpu-models-x86.rst.inc index ba27b5683f..6a770ca835 100644 --- a/docs/system/cpu-models-x86.rst.inc +++ b/docs/system/cpu-models-x86.rst.inc @@ -71,6 +71,16 @@ mixture of host CPU models between machines, if live migration compatibility is required, use the newest CPU model that is compatible across all desired hosts. +``ClearwaterForest`` + Intel Xeon Processor (ClearwaterForest, 2025) + +``SierraForest``, ``SierraForest-v2`` + Intel Xeon Processor (SierraForest, 2024), SierraForest-v2 mitigates + the GDS and RFDS vulnerabilities with stepping 3. + +``GraniteRapids``, ``GraniteRapids-v2`` + Intel Xeon Processor (GraniteRapids, 2024) + ``Cascadelake-Server``, ``Cascadelake-Server-noTSX`` Intel Xeon Processor (Cascade Lake, 2019), with "stepping" levels 6 or 7 only. (The Cascade Lake Xeon processor with *stepping 5 is @@ -181,7 +191,7 @@ features are included if using "Host passthrough" or "Host model". CVE-2018-12127, [MSBDS] CVE-2018-12126). This is an MSR (Model-Specific Register) feature rather than a CPUID feature, - so it will not appear in the Linux ``/proc/cpuinfo`` in the host or + therefore it will not appear in the Linux ``/proc/cpuinfo`` in the host or guest. Instead, the host kernel uses it to populate the MDS vulnerability file in ``sysfs``. @@ -189,10 +199,10 @@ features are included if using "Host passthrough" or "Host model". affected} in the ``/sys/devices/system/cpu/vulnerabilities/mds`` file. ``taa-no`` - Recommended to inform that the guest that the host is ``not`` + Recommended to inform the guest that the host is ``not`` vulnerable to CVE-2019-11135, TSX Asynchronous Abort (TAA). - This too is an MSR feature, so it does not show up in the Linux + This is also an MSR feature, therefore it does not show up in the Linux ``/proc/cpuinfo`` in the host or guest. It should only be enabled for VMs if the host reports ``Not affected`` @@ -214,7 +224,7 @@ features are included if using "Host passthrough" or "Host model". By disabling TSX, KVM-based guests can avoid paying the price of mitigating TSX-based attacks. - Note that ``tsx-ctrl`` too is an MSR feature, so it does not show + Note that ``tsx-ctrl`` is also an MSR feature, therefore it does not show up in the Linux ``/proc/cpuinfo`` in the host or guest. To validate that Intel TSX is indeed disabled for the guest, there are @@ -223,6 +233,38 @@ features are included if using "Host passthrough" or "Host model". ``/sys/devices/system/cpu/vulnerabilities/tsx_async_abort`` file in the guest should report ``Mitigation: TSX disabled``. +``bhi-no`` + Recommended to inform the guest that the host is ``not`` + vulnerable to CVE-2022-0001, Branch History Injection (BHI). + + This is also an MSR feature, therefore it does not show up in the Linux + ``/proc/cpuinfo`` in the host or guest. + + It should only be enabled for VMs if the host reports + ``BHI: Not affected`` in the + ``/sys/devices/system/cpu/vulnerabilities/spectre_v2`` file. + +``gds-no`` + Recommended to inform the guest that the host is ``not`` + vulnerable to CVE-2022-40982, Gather Data Sampling (GDS). + + This is also an MSR feature, therefore it does not show up in the Linux + ``/proc/cpuinfo`` in the host or guest. + + It should only be enabled for VMs if the host reports ``Not affected`` + in the ``/sys/devices/system/cpu/vulnerabilities/gather_data_sampling`` + file. + +``rfds-no`` + Recommended to inform the guest that the host is ``not`` + vulnerable to CVE-2023-28746, Register File Data Sampling (RFDS). + + This is also an MSR feature, therefore it does not show up in the Linux + ``/proc/cpuinfo`` in the host or guest. + + It should only be enabled for VMs if the host reports ``Not affected`` + in the ``/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling`` + file. Preferred CPU models for AMD x86 hosts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/fpu/softfloat-parts.c.inc b/fpu/softfloat-parts.c.inc index ebde42992f..fee05d0a86 100644 --- a/fpu/softfloat-parts.c.inc +++ b/fpu/softfloat-parts.c.inc @@ -198,7 +198,7 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status, if (likely(frac_eqz(p))) { p->cls = float_class_zero; } else if (status->flush_inputs_to_zero) { - float_raise(float_flag_input_denormal, status); + float_raise(float_flag_input_denormal_flushed, status); p->cls = float_class_zero; frac_clear(p); } else { @@ -334,7 +334,7 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, } frac_shr(p, frac_shift); } else if (s->flush_to_zero) { - flags |= float_flag_output_denormal; + flags |= float_flag_output_denormal_flushed; p->cls = float_class_zero; exp = 0; frac_clear(p); diff --git a/fpu/softfloat.c b/fpu/softfloat.c index 8d75d66817..26f3a8dc87 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -132,7 +132,7 @@ this code that are retained. if (unlikely(soft_t ## _is_denormal(*a))) { \ *a = soft_t ## _set_sign(soft_t ## _zero, \ soft_t ## _is_neg(*a)); \ - float_raise(float_flag_input_denormal, s); \ + float_raise(float_flag_input_denormal_flushed, s); \ } \ } @@ -4848,7 +4848,7 @@ float128 float128_silence_nan(float128 a, float_status *status) static bool parts_squash_denormal(FloatParts64 p, float_status *status) { if (p.exp == 0 && p.frac != 0) { - float_raise(float_flag_input_denormal, status); + float_raise(float_flag_input_denormal_flushed, status); return true; } @@ -5017,7 +5017,7 @@ floatx80 roundAndPackFloatx80(FloatX80RoundPrec roundingPrecision, bool zSign, } if ( zExp <= 0 ) { if (status->flush_to_zero) { - float_raise(float_flag_output_denormal, status); + float_raise(float_flag_output_denormal_flushed, status); return packFloatx80(zSign, 0, 0); } isTiny = status->tininess_before_rounding diff --git a/gdbstub/user-target.c b/gdbstub/user-target.c index 22bf4008c0..4bfcf78aaa 100644 --- a/gdbstub/user-target.c +++ b/gdbstub/user-target.c @@ -317,9 +317,9 @@ void gdb_handle_v_file_open(GArray *params, void *user_ctx) int fd = open(filename, flags, mode); #endif if (fd < 0) { - g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); } else { - g_string_printf(gdbserver_state.str_buf, "F%d", fd); + g_string_printf(gdbserver_state.str_buf, "F%x", fd); } gdb_put_strbuf(); } @@ -329,7 +329,7 @@ void gdb_handle_v_file_close(GArray *params, void *user_ctx) int fd = gdb_get_cmd_param(params, 0)->val_ul; if (close(fd) == -1) { - g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); gdb_put_strbuf(); return; } @@ -352,7 +352,7 @@ void gdb_handle_v_file_pread(GArray *params, void *user_ctx) ssize_t n = pread(fd, buf, bufsiz, offset); if (n < 0) { - g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); gdb_put_strbuf(); return; } @@ -375,7 +375,7 @@ void gdb_handle_v_file_readlink(GArray *params, void *user_ctx) ssize_t n = readlink(filename, buf, BUFSIZ); #endif if (n < 0) { - g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); + g_string_printf(gdbserver_state.str_buf, "F-1,%x", errno); gdb_put_strbuf(); return; } diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index e779b5af95..256013ca80 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -516,6 +516,7 @@ config FSL_IMX6 select PL310 # cache controller select PCI_EXPRESS_DESIGNWARE select SDHCI + select OR_IRQ config ASPEED_SOC bool @@ -573,6 +574,7 @@ config FSL_IMX7 select WDT_IMX2 select PCI_EXPRESS_DESIGNWARE select SDHCI + select OR_IRQ select UNIMP config ARM_SMMUV3 diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index 5002a40f06..c9a5209216 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -82,7 +82,7 @@ static void bl475e_init(MachineState *machine) sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); sc = STM32L4X5_SOC_GET_CLASS(&s->soc); - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0, + armv7m_load_kernel(s->soc.armv7m.cpu, machine->kernel_filename, 0, sc->flash_size); if (object_class_by_name(TYPE_DM163)) { diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index ac8c66e242..88b9ccff49 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -106,6 +106,8 @@ static void fsl_imx6_init(Object *obj) object_initialize_child(obj, "eth", &s->eth, TYPE_IMX_ENET); object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); + object_initialize_child(obj, "pcie4-msi-irq", &s->pcie4_msi_irq, + TYPE_OR_IRQ); } static void fsl_imx6_realize(DeviceState *dev, Error **errp) @@ -435,14 +437,23 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_realize(SYS_BUS_DEVICE(&s->pcie), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie), 0, FSL_IMX6_PCIe_REG_ADDR); + object_property_set_int(OBJECT(&s->pcie4_msi_irq), "num-lines", 2, + &error_abort); + qdev_realize(DEVICE(&s->pcie4_msi_irq), NULL, &error_abort); + + irq = qdev_get_gpio_in(DEVICE(&s->a9mpcore), FSL_IMX6_PCIE4_MSI_IRQ); + qdev_connect_gpio_out(DEVICE(&s->pcie4_msi_irq), 0, irq); + irq = qdev_get_gpio_in(DEVICE(&s->a9mpcore), FSL_IMX6_PCIE1_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, irq); irq = qdev_get_gpio_in(DEVICE(&s->a9mpcore), FSL_IMX6_PCIE2_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, irq); irq = qdev_get_gpio_in(DEVICE(&s->a9mpcore), FSL_IMX6_PCIE3_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a9mpcore), FSL_IMX6_PCIE4_IRQ); + irq = qdev_get_gpio_in(DEVICE(&s->pcie4_msi_irq), 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, irq); + irq = qdev_get_gpio_in(DEVICE(&s->pcie4_msi_irq), 1); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 4, irq); /* * PCIe PHY diff --git a/hw/arm/fsl-imx7.c b/hw/arm/fsl-imx7.c index 05e3389fbe..004bf49937 100644 --- a/hw/arm/fsl-imx7.c +++ b/hw/arm/fsl-imx7.c @@ -150,6 +150,8 @@ static void fsl_imx7_init(Object *obj) * PCIE */ object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); + object_initialize_child(obj, "pcie4-msi-irq", &s->pcie4_msi_irq, + TYPE_OR_IRQ); /* * USBs @@ -597,14 +599,23 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_realize(SYS_BUS_DEVICE(&s->pcie), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie), 0, FSL_IMX7_PCIE_REG_ADDR); + object_property_set_int(OBJECT(&s->pcie4_msi_irq), "num-lines", 2, + &error_abort); + qdev_realize(DEVICE(&s->pcie4_msi_irq), NULL, &error_abort); + + irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTD_MSI_IRQ); + qdev_connect_gpio_out(DEVICE(&s->pcie4_msi_irq), 0, irq); + irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTA_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, irq); irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTB_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, irq); irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTC_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTD_IRQ); + irq = qdev_get_gpio_in(DEVICE(&s->pcie4_msi_irq), 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, irq); + irq = qdev_get_gpio_in(DEVICE(&s->pcie4_msi_irq), 1); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 4, irq); /* * USBs diff --git a/hw/arm/microbit.c b/hw/arm/microbit.c index 374fbcb361..3f56fb45ce 100644 --- a/hw/arm/microbit.c +++ b/hw/arm/microbit.c @@ -56,7 +56,7 @@ static void microbit_init(MachineState *machine) memory_region_add_subregion_overlap(&s->nrf51.container, NRF51_TWI_BASE, mr, -1); - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(s->nrf51.armv7m.cpu, machine->kernel_filename, 0, s->nrf51.flash_size); } diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index 0136e419bf..d3a9f1b03a 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c @@ -1211,7 +1211,7 @@ static void mps2tz_common_init(MachineState *machine) mms->remap_irq); } - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(mms->iotkit.armv7m[0].cpu, machine->kernel_filename, 0, boot_ram_size(mms)); } diff --git a/hw/arm/mps2.c b/hw/arm/mps2.c index efb3500742..56b2af40f1 100644 --- a/hw/arm/mps2.c +++ b/hw/arm/mps2.c @@ -460,7 +460,7 @@ static void mps2_common_init(MachineState *machine) qdev_get_gpio_in(armv7m, mmc->fpga_type == FPGA_AN511 ? 47 : 13)); - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(mms->armv7m.cpu, machine->kernel_filename, 0, 0x400000); } diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c index 5c415abe85..9b20f1e2c9 100644 --- a/hw/arm/msf2-som.c +++ b/hw/arm/msf2-som.c @@ -92,7 +92,7 @@ static void emcraft_sf2_s2s010_init(MachineState *machine) cs_line = qdev_get_gpio_in_named(spi_flash, SSI_GPIO_CS, 0); sysbus_connect_irq(SYS_BUS_DEVICE(&soc->spi[0]), 1, cs_line); - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(soc->armv7m.cpu, machine->kernel_filename, 0, soc->envm_size); } diff --git a/hw/arm/musca.c b/hw/arm/musca.c index 3c3b534cb7..e9c092abc3 100644 --- a/hw/arm/musca.c +++ b/hw/arm/musca.c @@ -590,7 +590,7 @@ static void musca_init(MachineState *machine) "cfg_sec_resp", 0)); } - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(mms->sse.armv7m[0].cpu, machine->kernel_filename, 0, 0x2000000); } diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c index 8b1a9a2437..df793c77fe 100644 --- a/hw/arm/netduino2.c +++ b/hw/arm/netduino2.c @@ -48,7 +48,7 @@ static void netduino2_init(MachineState *machine) qdev_connect_clock_in(dev, "sysclk", sysclk); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + armv7m_load_kernel(STM32F205_SOC(dev)->armv7m.cpu, machine->kernel_filename, 0, FLASH_SIZE); } diff --git a/hw/arm/netduinoplus2.c b/hw/arm/netduinoplus2.c index bccd100354..81b6334cf7 100644 --- a/hw/arm/netduinoplus2.c +++ b/hw/arm/netduinoplus2.c @@ -48,7 +48,7 @@ static void netduinoplus2_init(MachineState *machine) qdev_connect_clock_in(dev, "sysclk", sysclk); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - armv7m_load_kernel(ARM_CPU(first_cpu), + armv7m_load_kernel(STM32F405_SOC(dev)->armv7m.cpu, machine->kernel_filename, 0, FLASH_SIZE); } diff --git a/hw/arm/nrf51_soc.c b/hw/arm/nrf51_soc.c index 37dd4cf5f4..dee06ab565 100644 --- a/hw/arm/nrf51_soc.c +++ b/hw/arm/nrf51_soc.c @@ -76,16 +76,16 @@ static void nrf51_soc_realize(DeviceState *dev_soc, Error **errp) } /* This clock doesn't need migration because it is fixed-frequency */ clock_set_hz(s->sysclk, HCLK_FRQ); - qdev_connect_clock_in(DEVICE(&s->cpu), "cpuclk", s->sysclk); + qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk); /* * This SoC has no systick device, so don't connect refclk. * TODO: model the lack of systick (currently the armv7m object * will always provide one). */ - object_property_set_link(OBJECT(&s->cpu), "memory", OBJECT(&s->container), + object_property_set_link(OBJECT(&s->armv7m), "memory", OBJECT(&s->container), &error_abort); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->cpu), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) { return; } @@ -104,7 +104,7 @@ static void nrf51_soc_realize(DeviceState *dev_soc, Error **errp) mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->uart), 0); memory_region_add_subregion_overlap(&s->container, NRF51_UART_BASE, mr, 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart), 0, - qdev_get_gpio_in(DEVICE(&s->cpu), + qdev_get_gpio_in(DEVICE(&s->armv7m), BASE_TO_IRQ(NRF51_UART_BASE))); /* RNG */ @@ -115,7 +115,7 @@ static void nrf51_soc_realize(DeviceState *dev_soc, Error **errp) mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rng), 0); memory_region_add_subregion_overlap(&s->container, NRF51_RNG_BASE, mr, 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->rng), 0, - qdev_get_gpio_in(DEVICE(&s->cpu), + qdev_get_gpio_in(DEVICE(&s->armv7m), BASE_TO_IRQ(NRF51_RNG_BASE))); /* UICR, FICR, NVMC, FLASH */ @@ -161,7 +161,7 @@ static void nrf51_soc_realize(DeviceState *dev_soc, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer[i]), 0, base_addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer[i]), 0, - qdev_get_gpio_in(DEVICE(&s->cpu), + qdev_get_gpio_in(DEVICE(&s->armv7m), BASE_TO_IRQ(base_addr))); } @@ -185,10 +185,10 @@ static void nrf51_soc_init(Object *obj) memory_region_init(&s->container, obj, "nrf51-container", UINT64_MAX); - object_initialize_child(OBJECT(s), "armv6m", &s->cpu, TYPE_ARMV7M); - qdev_prop_set_string(DEVICE(&s->cpu), "cpu-type", + object_initialize_child(OBJECT(s), "armv6m", &s->armv7m, TYPE_ARMV7M); + qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type", ARM_CPU_TYPE_NAME("cortex-m0")); - qdev_prop_set_uint32(DEVICE(&s->cpu), "num-irq", 32); + qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", 32); object_initialize_child(obj, "uart", &s->uart, TYPE_NRF51_UART); object_property_add_alias(obj, "serial0", OBJECT(&s->uart), "chardev"); diff --git a/hw/arm/olimex-stm32-h405.c b/hw/arm/olimex-stm32-h405.c index 4ad7b043be..1f15620f9f 100644 --- a/hw/arm/olimex-stm32-h405.c +++ b/hw/arm/olimex-stm32-h405.c @@ -51,7 +51,7 @@ static void olimex_stm32_h405_init(MachineState *machine) qdev_connect_clock_in(dev, "sysclk", sysclk); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - armv7m_load_kernel(ARM_CPU(first_cpu), + armv7m_load_kernel(STM32F405_SOC(dev)->armv7m.cpu, machine->kernel_filename, 0, FLASH_SIZE); } diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c index 1bba96df14..c3c3fd0410 100644 --- a/hw/arm/stellaris.c +++ b/hw/arm/stellaris.c @@ -8,6 +8,7 @@ */ #include "qemu/osdep.h" +#include "qemu/bitops.h" #include "qapi/error.h" #include "hw/core/split-irq.h" #include "hw/sysbus.h" @@ -49,6 +50,31 @@ #define NUM_IRQ_LINES 64 #define NUM_PRIO_BITS 3 +#define NUM_GPIO 7 +#define NUM_UART 4 +#define NUM_GPTM 4 +#define NUM_I2C 2 + +/* + * See Stellaris Data Sheet chapter 5.2.5 "System Control", + * Register 13 .. 17: Device Capabilities 0 .. 4 (DC0 .. DC4). + */ +#define DC1_WDT 3 +#define DC1_HIB 6 +#define DC1_MPU 7 +#define DC1_ADC 16 +#define DC1_PWM 20 +#define DC2_UART(n) (n) +#define DC2_SSI 4 +#define DC2_QEI(n) (8 + n) +#define DC2_I2C(n) (12 + 2 * n) +#define DC2_GPTM(n) (16 + n) +#define DC2_COMP(n) (24 + n) +#define DC4_GPIO(n) (n) +#define DC4_EMAC 28 + +#define DEV_CAP(_dc, _cap) extract32(board->dc##_dc, DC##_dc##_##_cap, 1) + typedef const struct { const char *name; uint32_t did0; @@ -101,7 +127,7 @@ static void ssys_update(ssys_state *s) qemu_set_irq(s->irq, (s->int_status & s->int_mask) != 0); } -static uint32_t pllcfg_sandstorm[16] = { +static const uint32_t pllcfg_sandstorm[16] = { 0x31c0, /* 1 Mhz */ 0x1ae0, /* 1.8432 Mhz */ 0x18c0, /* 2 Mhz */ @@ -120,7 +146,7 @@ static uint32_t pllcfg_sandstorm[16] = { 0x585b /* 8.192 Mhz */ }; -static uint32_t pllcfg_fury[16] = { +static const uint32_t pllcfg_fury[16] = { 0x3200, /* 1 Mhz */ 0x1b20, /* 1.8432 Mhz */ 0x1900, /* 2 Mhz */ @@ -964,7 +990,7 @@ static void stellaris_adc_init(Object *obj) } /* Board init. */ -static stellaris_board_info stellaris_boards[] = { +static const stellaris_board_info stellaris_boards[] = { { "LM3S811EVB", 0, 0x0032000e, @@ -989,19 +1015,20 @@ static stellaris_board_info stellaris_boards[] = { static void stellaris_init(MachineState *ms, stellaris_board_info *board) { - static const int uart_irq[] = {5, 6, 33, 34}; - static const int timer_irq[] = {19, 21, 23, 35}; - static const uint32_t gpio_addr[7] = + static const int uart_irq[NUM_UART] = {5, 6, 33, 34}; + static const int timer_irq[NUM_GPTM] = {19, 21, 23, 35}; + static const uint32_t gpio_addr[NUM_GPIO] = { 0x40004000, 0x40005000, 0x40006000, 0x40007000, 0x40024000, 0x40025000, 0x40026000}; - static const int gpio_irq[7] = {0, 1, 2, 3, 4, 30, 31}; + static const int gpio_irq[NUM_GPIO] = {0, 1, 2, 3, 4, 30, 31}; + static const uint32_t i2c_addr[NUM_I2C] = {0x40020000, 0x40021000}; + static const int i2c_irq[NUM_I2C] = {8, 37}; /* Memory map of SoC devices, from * Stellaris LM3S6965 Microcontroller Data Sheet (rev I) * http://www.ti.com/lit/ds/symlink/lm3s6965.pdf * * 40000000 wdtimer - * 40002000 i2c (unimplemented) * 40004000 GPIO * 40005000 GPIO * 40006000 GPIO @@ -1031,13 +1058,13 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) */ Object *soc_container; - DeviceState *gpio_dev[7], *nvic; - qemu_irq gpio_in[7][8]; - qemu_irq gpio_out[7][8]; + DeviceState *gpio_dev[NUM_GPIO], *armv7m, *nvic; + qemu_irq gpio_in[NUM_GPIO][8]; + qemu_irq gpio_out[NUM_GPIO][8]; qemu_irq adc; int sram_size; int flash_size; - I2CBus *i2c; + DeviceState *i2c_dev[NUM_I2C] = { }; DeviceState *dev; DeviceState *ssys_dev; int i; @@ -1095,25 +1122,26 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) qdev_prop_set_uint32(ssys_dev, "dc4", board->dc4); sysbus_realize_and_unref(SYS_BUS_DEVICE(ssys_dev), &error_fatal); - nvic = qdev_new(TYPE_ARMV7M); - object_property_add_child(soc_container, "v7m", OBJECT(nvic)); - qdev_prop_set_uint32(nvic, "num-irq", NUM_IRQ_LINES); - qdev_prop_set_uint8(nvic, "num-prio-bits", NUM_PRIO_BITS); - qdev_prop_set_string(nvic, "cpu-type", ms->cpu_type); - qdev_prop_set_bit(nvic, "enable-bitband", true); - qdev_connect_clock_in(nvic, "cpuclk", + armv7m = qdev_new(TYPE_ARMV7M); + object_property_add_child(soc_container, "v7m", OBJECT(armv7m)); + qdev_prop_set_uint32(armv7m, "num-irq", NUM_IRQ_LINES); + qdev_prop_set_uint8(armv7m, "num-prio-bits", NUM_PRIO_BITS); + qdev_prop_set_string(armv7m, "cpu-type", ms->cpu_type); + qdev_prop_set_bit(armv7m, "enable-bitband", true); + qdev_connect_clock_in(armv7m, "cpuclk", qdev_get_clock_out(ssys_dev, "SYSCLK")); /* This SoC does not connect the systick reference clock */ - object_property_set_link(OBJECT(nvic), "memory", + object_property_set_link(OBJECT(armv7m), "memory", OBJECT(get_system_memory()), &error_abort); /* This will exit with an error if the user passed us a bad cpu_type */ - sysbus_realize_and_unref(SYS_BUS_DEVICE(nvic), &error_fatal); + sysbus_realize_and_unref(SYS_BUS_DEVICE(armv7m), &error_fatal); + nvic = armv7m; /* Now we can wire up the IRQ and MMIO of the system registers */ sysbus_mmio_map(SYS_BUS_DEVICE(ssys_dev), 0, 0x400fe000); sysbus_connect_irq(SYS_BUS_DEVICE(ssys_dev), 0, qdev_get_gpio_in(nvic, 28)); - if (board->dc1 & (1 << 16)) { + if (DEV_CAP(1, ADC)) { dev = sysbus_create_varargs(TYPE_STELLARIS_ADC, 0x40038000, qdev_get_gpio_in(nvic, 14), qdev_get_gpio_in(nvic, 15), @@ -1124,8 +1152,8 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) } else { adc = NULL; } - for (i = 0; i < 4; i++) { - if (board->dc2 & (0x10000 << i)) { + for (i = 0; i < NUM_GPTM; i++) { + if (DEV_CAP(2, GPTM(i))) { SysBusDevice *sbd; dev = qdev_new(TYPE_STELLARIS_GPTM); @@ -1142,7 +1170,7 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) } } - if (board->dc1 & (1 << 3)) { /* watchdog present */ + if (DEV_CAP(1, WDT)) { dev = qdev_new(TYPE_LUMINARY_WATCHDOG); object_property_add_child(soc_container, "wdg", OBJECT(dev)); qdev_connect_clock_in(dev, "WDOGCLK", @@ -1158,8 +1186,8 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) } - for (i = 0; i < 7; i++) { - if (board->dc4 & (1 << i)) { + for (i = 0; i < NUM_GPIO; i++) { + if (DEV_CAP(4, GPIO(i))) { gpio_dev[i] = sysbus_create_simple("pl061_luminary", gpio_addr[i], qdev_get_gpio_in(nvic, gpio_irq[i])); @@ -1170,17 +1198,21 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) } } - if (board->dc2 & (1 << 12)) { - dev = sysbus_create_simple(TYPE_STELLARIS_I2C, 0x40020000, - qdev_get_gpio_in(nvic, 8)); - i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c"); - if (board->peripherals & BP_OLED_I2C) { - i2c_slave_create_simple(i2c, "ssd0303", 0x3d); + for (i = 0; i < NUM_I2C; i++) { + if (DEV_CAP(2, I2C(i))) { + i2c_dev[i] = sysbus_create_simple(TYPE_STELLARIS_I2C, i2c_addr[i], + qdev_get_gpio_in(nvic, + i2c_irq[i])); } } + if (board->peripherals & BP_OLED_I2C) { + I2CBus *bus = (I2CBus *)qdev_get_child_bus(i2c_dev[0], "i2c"); - for (i = 0; i < 4; i++) { - if (board->dc2 & (1 << i)) { + i2c_slave_create_simple(bus, "ssd0303", 0x3d); + } + + for (i = 0; i < NUM_UART; i++) { + if (DEV_CAP(2, UART(i))) { SysBusDevice *sbd; dev = qdev_new("pl011_luminary"); @@ -1192,7 +1224,7 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(nvic, uart_irq[i])); } } - if (board->dc2 & (1 << 4)) { + if (DEV_CAP(2, SSI)) { dev = sysbus_create_simple("pl022", 0x40008000, qdev_get_gpio_in(nvic, 7)); if (board->peripherals & BP_OLED_SSI) { @@ -1301,7 +1333,7 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) qemu_irq_raise(gpio_out[GPIO_D][0]); } } - if (board->dc4 & (1 << 28)) { + if (DEV_CAP(4, EMAC)) { DeviceState *enet; enet = qdev_new("stellaris_enet"); @@ -1356,8 +1388,6 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) /* Add dummy regions for the devices we don't implement yet, * so guest accesses don't cause unlogged crashes. */ - create_unimplemented_device("i2c-0", 0x40002000, 0x1000); - create_unimplemented_device("i2c-2", 0x40021000, 0x1000); create_unimplemented_device("PWM", 0x40028000, 0x1000); create_unimplemented_device("QEI-0", 0x4002c000, 0x1000); create_unimplemented_device("QEI-1", 0x4002d000, 0x1000); @@ -1365,7 +1395,7 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board) create_unimplemented_device("hibernation", 0x400fc000, 0x1000); create_unimplemented_device("flash-control", 0x400fd000, 0x1000); - armv7m_load_kernel(ARM_CPU(first_cpu), ms->kernel_filename, 0, flash_size); + armv7m_load_kernel(ARMV7M(armv7m)->cpu, ms->kernel_filename, 0, flash_size); } /* FIXME: Figure out how to generate these from stellaris_boards. */ @@ -1379,6 +1409,10 @@ static void lm3s6965evb_init(MachineState *machine) stellaris_init(machine, &stellaris_boards[1]); } +/* + * Stellaris LM3S811 Evaluation Board Schematics: + * https://www.ti.com/lit/ug/symlink/spmu030.pdf + */ static void lm3s811evb_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -1395,6 +1429,10 @@ static const TypeInfo lm3s811evb_type = { .class_init = lm3s811evb_class_init, }; +/* + * Stellaris: LM3S6965 Evaluation Board Schematics: + * https://www.ti.com/lit/ug/symlink/spmu029.pdf + */ static void lm3s6965evb_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c index cc41935160..e6c1f5b8d7 100644 --- a/hw/arm/stm32vldiscovery.c +++ b/hw/arm/stm32vldiscovery.c @@ -51,7 +51,7 @@ static void stm32vldiscovery_init(MachineState *machine) qdev_connect_clock_in(dev, "sysclk", sysclk); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - armv7m_load_kernel(ARM_CPU(first_cpu), + armv7m_load_kernel(STM32F100_SOC(dev)->armv7m.cpu, machine->kernel_filename, 0, FLASH_SIZE); } diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c index 7c353fde50..38b4865157 100644 --- a/hw/char/imx_serial.c +++ b/hw/char/imx_serial.c @@ -160,6 +160,7 @@ static void imx_serial_reset(IMXSerialState *s) s->ucr3 = 0x700; s->ubmr = 0; s->ubrc = 4; + s->ufcr = BIT(11) | BIT(0); fifo32_reset(&s->rx_fifo); timer_del(&s->ageing_timer); @@ -400,14 +401,14 @@ static void imx_put_data(void *opaque, uint32_t value) if (fifo32_num_used(&s->rx_fifo) >= rxtl) { s->usr1 |= USR1_RRDY; } - - imx_serial_rx_fifo_ageing_timer_restart(s); - s->usr2 |= USR2_RDR; s->uts1 &= ~UTS1_RXEMPTY; if (value & URXD_BRK) { s->usr2 |= USR2_BRCD; } + + imx_serial_rx_fifo_ageing_timer_restart(s); + imx_update(s); } diff --git a/hw/core/machine.c b/hw/core/machine.c index c23b399496..254cc20c4c 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -38,6 +38,10 @@ GlobalProperty hw_compat_9_2[] = { {"arm-cpu", "backcompat-pauth-default-use-qarma5", "true"}, + { "virtio-balloon-pci", "vectors", "0" }, + { "virtio-balloon-pci-transitional", "vectors", "0" }, + { "virtio-balloon-pci-non-transitional", "vectors", "0" }, + { "virtio-mem-pci", "vectors", "0" }, }; const size_t hw_compat_9_2_len = G_N_ELEMENTS(hw_compat_9_2); @@ -457,6 +461,22 @@ static void machine_set_mem_merge(Object *obj, bool value, Error **errp) ms->mem_merge = value; } +#ifdef CONFIG_POSIX +static bool machine_get_aux_ram_share(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return ms->aux_ram_share; +} + +static void machine_set_aux_ram_share(Object *obj, bool value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->aux_ram_share = value; +} +#endif + static bool machine_get_usb(Object *obj, Error **errp) { MachineState *ms = MACHINE(obj); @@ -1162,6 +1182,12 @@ static void machine_class_init(ObjectClass *oc, void *data) object_class_property_set_description(oc, "mem-merge", "Enable/disable memory merge support"); +#ifdef CONFIG_POSIX + object_class_property_add_bool(oc, "aux-ram-share", + machine_get_aux_ram_share, + machine_set_aux_ram_share); +#endif + object_class_property_add_bool(oc, "usb", machine_get_usb, machine_set_usb); object_class_property_set_description(oc, "usb", diff --git a/hw/misc/ivshmem-flat.c b/hw/misc/ivshmem-flat.c index 33fc9425d2..40309a8ff3 100644 --- a/hw/misc/ivshmem-flat.c +++ b/hw/misc/ivshmem-flat.c @@ -1,9 +1,10 @@ /* * Inter-VM Shared Memory Flat Device * - * SPDX-FileCopyrightText: 2023 Linaro Ltd. - * SPDX-FileContributor: Gustavo Romero <gustavo.romero@linaro.org> * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2023 Linaro Ltd. + * Authors: + * Gustavo Romero * */ diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c index c3fc37b904..3e8c36e6a7 100644 --- a/hw/pci-host/designware.c +++ b/hw/pci-host/designware.c @@ -55,8 +55,6 @@ #define DESIGNWARE_PCIE_ATU_DEVFN(x) (((x) >> 16) & 0xff) #define DESIGNWARE_PCIE_ATU_UPPER_TARGET 0x91C -#define DESIGNWARE_PCIE_IRQ_MSI 3 - static DesignwarePCIEHost * designware_pcie_root_to_host(DesignwarePCIERoot *root) { @@ -90,7 +88,7 @@ static void designware_pcie_root_msi_write(void *opaque, hwaddr addr, root->msi.intr[0].status |= BIT(val) & root->msi.intr[0].enable; if (root->msi.intr[0].status & ~root->msi.intr[0].mask) { - qemu_set_irq(host->pci.irqs[DESIGNWARE_PCIE_IRQ_MSI], 1); + qemu_set_irq(host->pci.msi, 1); } } @@ -335,7 +333,7 @@ static void designware_pcie_root_config_write(PCIDevice *d, uint32_t address, case DESIGNWARE_PCIE_MSI_INTR0_STATUS: root->msi.intr[0].status ^= val; if (!root->msi.intr[0].status) { - qemu_set_irq(host->pci.irqs[DESIGNWARE_PCIE_IRQ_MSI], 0); + qemu_set_irq(host->pci.msi, 0); } break; @@ -680,6 +678,7 @@ static void designware_pcie_host_realize(DeviceState *dev, Error **errp) for (i = 0; i < ARRAY_SIZE(s->pci.irqs); i++) { sysbus_init_irq(sbd, &s->pci.irqs[i]); } + sysbus_init_irq(sbd, &s->pci.msi); memory_region_init_io(&s->mmio, OBJECT(s), diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index 3af613d4e9..d9e683c5b4 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -48,6 +48,7 @@ #include "kvm/kvm_s390x.h" #include "hw/virtio/virtio-md-pci.h" #include "hw/s390x/virtio-ccw-md.h" +#include "system/replay.h" #include CONFIG_DEVICES static Error *pv_mig_blocker; @@ -454,6 +455,18 @@ static void s390_machine_reset(MachineState *machine, ResetType type) CPUState *cs, *t; S390CPU *cpu; + /* + * Temporarily drop the record/replay mutex to let rr_cpu_thread_fn() + * process the run_on_cpu() requests below. This is safe, because at this + * point one of the following is true: + * - All CPU threads are not running, either because the machine is being + * initialized, or because the guest requested a reset using diag 308. + * There is no risk to desync the record/replay state. + * - A snapshot is about to be loaded. The record/replay state consistency + * is not important. + */ + replay_mutex_unlock(); + /* get the reset parameters, reset them once done */ s390_ipl_get_reset_request(&cs, &reset_type); @@ -533,7 +546,7 @@ static void s390_machine_reset(MachineState *machine, ResetType type) * went wrong. */ s390_cpu_set_state(S390_CPU_STATE_OPERATING, cpu); - return; + goto out_lock; } run_on_cpu(cs, s390_do_cpu_load_normal, RUN_ON_CPU_NULL); @@ -546,6 +559,15 @@ static void s390_machine_reset(MachineState *machine, ResetType type) run_on_cpu(t, s390_do_cpu_set_diag318, RUN_ON_CPU_HOST_ULONG(0)); } s390_ipl_clear_reset_request(); + +out_lock: + /* + * Re-take the record/replay mutex, temporarily dropping the BQL in order + * to satisfy the ordering requirements. + */ + bql_unlock(); + replay_mutex_lock(); + bql_lock(); } static void s390_machine_device_pre_plug(HotplugHandler *hotplug_dev, @@ -554,8 +576,7 @@ static void s390_machine_device_pre_plug(HotplugHandler *hotplug_dev, if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { virtio_ccw_md_pre_plug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { - error_setg(errp, - "PCI-attached virtio based memory devices not supported"); + virtio_md_pci_pre_plug(VIRTIO_MD_PCI(dev), MACHINE(hotplug_dev), errp); } } @@ -566,7 +587,8 @@ static void s390_machine_device_plug(HotplugHandler *hotplug_dev, if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { s390_cpu_plug(hotplug_dev, dev, errp); - } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW) || + object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { /* * At this point, the device is realized and set all memdevs mapped, so * qemu_maxrampagesize() will pick up the page sizes of these memdevs @@ -580,7 +602,11 @@ static void s390_machine_device_plug(HotplugHandler *hotplug_dev, " initial memory"); return; } - virtio_ccw_md_plug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); + if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + virtio_ccw_md_plug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); + } else { + virtio_md_pci_plug(VIRTIO_MD_PCI(dev), MACHINE(hotplug_dev), errp); + } } } @@ -589,10 +615,12 @@ static void s390_machine_device_unplug_request(HotplugHandler *hotplug_dev, { if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { error_setg(errp, "CPU hot unplug not supported on this machine"); - return; } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { virtio_ccw_md_unplug_request(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { + virtio_md_pci_unplug_request(VIRTIO_MD_PCI(dev), MACHINE(hotplug_dev), + errp); } } @@ -601,7 +629,9 @@ static void s390_machine_device_unplug(HotplugHandler *hotplug_dev, { if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { virtio_ccw_md_unplug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); - } + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { + virtio_md_pci_unplug(VIRTIO_MD_PCI(dev), MACHINE(hotplug_dev), errp); + } } static CpuInstanceProperties s390_cpu_index_to_props(MachineState *ms, diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c index fae212f053..e2d66179e0 100644 --- a/hw/usb/canokey.c +++ b/hw/usb/canokey.c @@ -197,8 +197,8 @@ static void canokey_handle_data(USBDevice *dev, USBPacket *p) switch (p->pid) { case USB_TOKEN_OUT: trace_canokey_handle_data_out(ep_out, p->iov.size); - usb_packet_copy(p, key->ep_out_buffer[ep_out], p->iov.size); out_pos = 0; + /* segment packet into (possibly multiple) ep_out */ while (out_pos != p->iov.size) { /* * key->ep_out[ep_out] set by prepare_receive @@ -207,8 +207,8 @@ static void canokey_handle_data(USBDevice *dev, USBPacket *p) * to be the buffer length */ out_len = MIN(p->iov.size - out_pos, key->ep_out_size[ep_out]); - memcpy(key->ep_out[ep_out], - key->ep_out_buffer[ep_out] + out_pos, out_len); + /* usb_packet_copy would update the pos offset internally */ + usb_packet_copy(p, key->ep_out[ep_out], out_len); out_pos += out_len; /* update ep_out_size to actual len */ key->ep_out_size[ep_out] = out_len; diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h index e528889d33..1b60d73485 100644 --- a/hw/usb/canokey.h +++ b/hw/usb/canokey.h @@ -24,8 +24,6 @@ #define CANOKEY_EP_NUM 3 /* BULK/INTR IN can be up to 1352 bytes, e.g. get key info */ #define CANOKEY_EP_IN_BUFFER_SIZE 2048 -/* BULK OUT can be up to 270 bytes, e.g. PIV import cert */ -#define CANOKEY_EP_OUT_BUFFER_SIZE 512 typedef enum { CANOKEY_EP_IN_WAIT, @@ -59,8 +57,6 @@ typedef struct CanoKeyState { /* OUT pointer to canokey recv buffer */ uint8_t *ep_out[CANOKEY_EP_NUM]; uint32_t ep_out_size[CANOKEY_EP_NUM]; - /* For large BULK OUT, multiple write to ep_out is needed */ - uint8_t ep_out_buffer[CANOKEY_EP_NUM][CANOKEY_EP_OUT_BUFFER_SIZE]; /* Properties */ char *file; /* canokey-file */ diff --git a/hw/virtio/virtio-balloon-pci.c b/hw/virtio/virtio-balloon-pci.c index ce2645ba71..db7e1cb475 100644 --- a/hw/virtio/virtio-balloon-pci.c +++ b/hw/virtio/virtio-balloon-pci.c @@ -35,11 +35,22 @@ struct VirtIOBalloonPCI { VirtIOBalloon vdev; }; +static const Property virtio_balloon_properties[] = { + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, + DEV_NVECTORS_UNSPECIFIED), +}; + static void virtio_balloon_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) { VirtIOBalloonPCI *dev = VIRTIO_BALLOON_PCI(vpci_dev); DeviceState *vdev = DEVICE(&dev->vdev); + if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { + vpci_dev->nvectors = 2; + } + vpci_dev->class_code = PCI_CLASS_OTHERS; qdev_realize(vdev, BUS(&vpci_dev->bus), errp); } @@ -55,6 +66,7 @@ static void virtio_balloon_pci_class_init(ObjectClass *klass, void *data) pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_BALLOON; pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; pcidev_k->class_id = PCI_CLASS_OTHERS; + device_class_set_props(dc, virtio_balloon_properties); } static void virtio_balloon_pci_instance_init(Object *obj) diff --git a/hw/virtio/virtio-mem-pci.c b/hw/virtio/virtio-mem-pci.c index 1b4e9a3284..6cc5f0fd3b 100644 --- a/hw/virtio/virtio-mem-pci.c +++ b/hw/virtio/virtio-mem-pci.c @@ -22,6 +22,10 @@ static void virtio_mem_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) VirtIOMEMPCI *mem_pci = VIRTIO_MEM_PCI(vpci_dev); DeviceState *vdev = DEVICE(&mem_pci->vdev); + if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { + vpci_dev->nvectors = 2; + } + virtio_pci_force_virtio_1(vpci_dev); qdev_realize(vdev, BUS(&vpci_dev->bus), errp); } @@ -152,6 +156,13 @@ static void virtio_mem_pci_set_requested_size(Object *obj, Visitor *v, object_property_set(OBJECT(&pci_mem->vdev), name, v, errp); } +static const Property virtio_mem_pci_class_properties[] = { + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, + DEV_NVECTORS_UNSPECIFIED), +}; + static void virtio_mem_pci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -164,6 +175,7 @@ static void virtio_mem_pci_class_init(ObjectClass *klass, void *data) set_bit(DEVICE_CATEGORY_MISC, dc->categories); pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; pcidev_k->class_id = PCI_CLASS_OTHERS; + device_class_set_props(dc, virtio_mem_pci_class_properties); mdc->get_addr = virtio_mem_pci_get_addr; mdc->set_addr = virtio_mem_pci_set_addr; diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h index e27c18f3dc..060b7e7131 100644 --- a/include/exec/memattrs.h +++ b/include/exec/memattrs.h @@ -23,12 +23,6 @@ * different semantics. */ typedef struct MemTxAttrs { - /* Bus masters which don't specify any attributes will get this - * (via the MEMTXATTRS_UNSPECIFIED constant), so that we can - * distinguish "all attributes deliberately clear" from - * "didn't specify" if necessary. - */ - unsigned int unspecified:1; /* * ARM/AMBA: TrustZone Secure access * x86: System Management Mode access @@ -57,14 +51,27 @@ typedef struct MemTxAttrs { * PID (PCI PASID) support: Limited to 8 bits process identifier. */ unsigned int pid:8; + + /* + * Bus masters which don't specify any attributes will get this + * (via the MEMTXATTRS_UNSPECIFIED constant), so that we can + * distinguish "all attributes deliberately clear" from + * "didn't specify" if necessary. + */ + bool unspecified; + + uint8_t _reserved1; + uint16_t _reserved2; } MemTxAttrs; +QEMU_BUILD_BUG_ON(sizeof(MemTxAttrs) > 8); + /* Bus masters which don't specify any attributes will get this, * which has all attribute bits clear except the topmost one * (so that we can distinguish "all attributes deliberately clear" * from "didn't specify" if necessary). */ -#define MEMTXATTRS_UNSPECIFIED ((MemTxAttrs) { .unspecified = 1 }) +#define MEMTXATTRS_UNSPECIFIED ((MemTxAttrs) { .unspecified = true }) /* New-style MMIO accessors can indicate that the transaction failed. * A zero (MEMTX_OK) response means success; anything else is a failure diff --git a/include/exec/memory.h b/include/exec/memory.h index 3ee1901b52..9f73b59867 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -246,6 +246,16 @@ typedef struct IOMMUTLBEvent { /* RAM can be private that has kvm guest memfd backend */ #define RAM_GUEST_MEMFD (1 << 12) +/* + * In RAMBlock creation functions, if MAP_SHARED is 0 in the flags parameter, + * the implementation may still create a shared mapping if other conditions + * require it. Callers who specifically want a private mapping, eg objects + * specified by the user, must pass RAM_PRIVATE. + * After RAMBlock creation, MAP_SHARED in the block's flags indicates whether + * the block is shared or private, and MAP_PRIVATE is omitted. + */ +#define RAM_PRIVATE (1 << 13) + static inline void iommu_notifier_init(IOMMUNotifier *n, IOMMUNotify fn, IOMMUNotifierFlag flags, hwaddr start, hwaddr end, diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h index ff157c1f42..94bb3ccbe4 100644 --- a/include/exec/ram_addr.h +++ b/include/exec/ram_addr.h @@ -111,23 +111,30 @@ long qemu_maxrampagesize(void); * * Parameters: * @size: the size in bytes of the ram block + * @max_size: the maximum size of the block after resizing * @mr: the memory region where the ram block is + * @resized: callback after calls to qemu_ram_resize * @ram_flags: RamBlock flags. Supported flags: RAM_SHARED, RAM_PMEM, * RAM_NORESERVE, RAM_PROTECTED, RAM_NAMED_FILE, RAM_READONLY, * RAM_READONLY_FD, RAM_GUEST_MEMFD * @mem_path or @fd: specify the backing file or device * @offset: Offset into target file + * @grow: extend file if necessary (but an empty file is always extended). * @errp: pointer to Error*, to store an error if it happens * * Return: * On success, return a pointer to the ram block. * On failure, return NULL. */ +typedef void (*qemu_ram_resize_cb)(const char *, uint64_t length, void *host); + RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, uint32_t ram_flags, const char *mem_path, off_t offset, Error **errp); -RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr, +RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, ram_addr_t max_size, + qemu_ram_resize_cb resized, MemoryRegion *mr, uint32_t ram_flags, int fd, off_t offset, + bool grow, Error **errp); RAMBlock *qemu_ram_alloc_from_ptr(ram_addr_t size, void *host, @@ -135,9 +142,7 @@ RAMBlock *qemu_ram_alloc_from_ptr(ram_addr_t size, void *host, RAMBlock *qemu_ram_alloc(ram_addr_t size, uint32_t ram_flags, MemoryRegion *mr, Error **errp); RAMBlock *qemu_ram_alloc_resizeable(ram_addr_t size, ram_addr_t max_size, - void (*resized)(const char*, - uint64_t length, - void *host), + qemu_ram_resize_cb resized, MemoryRegion *mr, Error **errp); void qemu_ram_free(RAMBlock *block); diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index 9d37cdfaa8..616c290145 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -154,8 +154,10 @@ enum { float_flag_overflow = 0x0004, float_flag_underflow = 0x0008, float_flag_inexact = 0x0010, - float_flag_input_denormal = 0x0020, - float_flag_output_denormal = 0x0040, + /* We flushed an input denormal to 0 (because of flush_inputs_to_zero) */ + float_flag_input_denormal_flushed = 0x0020, + /* We flushed an output denormal to 0 (because of flush_to_zero) */ + float_flag_output_denormal_flushed = 0x0040, float_flag_invalid_isi = 0x0080, /* inf - inf */ float_flag_invalid_imz = 0x0100, /* inf * 0 */ float_flag_invalid_idi = 0x0200, /* inf / inf */ @@ -300,9 +302,9 @@ typedef struct float_status { Float3NaNPropRule float_3nan_prop_rule; FloatInfZeroNaNRule float_infzeronan_rule; bool tininess_before_rounding; - /* should denormalised results go to zero and set the inexact flag? */ + /* should denormalised results go to zero and set output_denormal_flushed? */ bool flush_to_zero; - /* should denormalised inputs go to zero and set the input_denormal flag? */ + /* should denormalised inputs go to zero and set input_denormal_flushed? */ bool flush_inputs_to_zero; bool default_nan_mode; /* diff --git a/include/hw/arm/fsl-imx6.h b/include/hw/arm/fsl-imx6.h index 61c593ffd2..9da32fc189 100644 --- a/include/hw/arm/fsl-imx6.h +++ b/include/hw/arm/fsl-imx6.h @@ -33,6 +33,7 @@ #include "hw/usb/chipidea.h" #include "hw/usb/imx-usb-phy.h" #include "hw/pci-host/designware.h" +#include "hw/or-irq.h" #include "exec/memory.h" #include "cpu.h" #include "qom/object.h" @@ -73,6 +74,7 @@ struct FslIMX6State { ChipideaState usb[FSL_IMX6_NUM_USBS]; IMXFECState eth; DesignwarePCIEHost pcie; + OrIRQState pcie4_msi_irq; MemoryRegion rom; MemoryRegion caam; MemoryRegion ocram; @@ -457,7 +459,7 @@ struct FslIMX6State { #define FSL_IMX6_PCIE1_IRQ 120 #define FSL_IMX6_PCIE2_IRQ 121 #define FSL_IMX6_PCIE3_IRQ 122 -#define FSL_IMX6_PCIE4_IRQ 123 +#define FSL_IMX6_PCIE4_MSI_IRQ 123 #define FSL_IMX6_DCIC1_IRQ 124 #define FSL_IMX6_DCIC2_IRQ 125 #define FSL_IMX6_MLB150_HIGH_IRQ 126 diff --git a/include/hw/arm/fsl-imx7.h b/include/hw/arm/fsl-imx7.h index 411fa1c2e3..aa7818c499 100644 --- a/include/hw/arm/fsl-imx7.h +++ b/include/hw/arm/fsl-imx7.h @@ -36,6 +36,7 @@ #include "hw/net/imx_fec.h" #include "hw/pci-host/designware.h" #include "hw/usb/chipidea.h" +#include "hw/or-irq.h" #include "cpu.h" #include "qom/object.h" #include "qemu/units.h" @@ -85,6 +86,7 @@ struct FslIMX7State { IMX7GPRState gpr; ChipideaState usb[FSL_IMX7_NUM_USBS]; DesignwarePCIEHost pcie; + OrIRQState pcie4_msi_irq; MemoryRegion rom; MemoryRegion caam; MemoryRegion ocram; @@ -428,7 +430,7 @@ enum FslIMX7IRQs { FSL_IMX7_PCI_INTA_IRQ = 125, FSL_IMX7_PCI_INTB_IRQ = 124, FSL_IMX7_PCI_INTC_IRQ = 123, - FSL_IMX7_PCI_INTD_IRQ = 122, + FSL_IMX7_PCI_INTD_MSI_IRQ = 122, FSL_IMX7_UART7_IRQ = 126, diff --git a/include/hw/arm/nrf51_soc.h b/include/hw/arm/nrf51_soc.h index e52a56e75e..f88ab1b7d3 100644 --- a/include/hw/arm/nrf51_soc.h +++ b/include/hw/arm/nrf51_soc.h @@ -30,7 +30,7 @@ struct NRF51State { SysBusDevice parent_obj; /*< public >*/ - ARMv7MState cpu; + ARMv7MState armv7m; NRF51UARTState uart; NRF51RNGState rng; diff --git a/include/hw/boards.h b/include/hw/boards.h index 2ad711e56d..e1f41b2a53 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -410,6 +410,7 @@ struct MachineState { bool enable_graphics; ConfidentialGuestSupport *cgs; HostMemoryBackend *memdev; + bool aux_ram_share; /* * convenience alias to ram_memdev_id backend memory region * or to numa container memory region diff --git a/include/hw/char/imx_serial.h b/include/hw/char/imx_serial.h index 65f0e97c76..90ba3ff18c 100644 --- a/include/hw/char/imx_serial.h +++ b/include/hw/char/imx_serial.h @@ -109,13 +109,13 @@ struct IMXSerialState { uint32_t ucr1; uint32_t ucr2; uint32_t uts1; + uint32_t ufcr; /* * The registers below are implemented just so that the * guest OS sees what it has written */ uint32_t onems; - uint32_t ufcr; uint32_t ubmr; uint32_t ubrc; uint32_t ucr3; diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index a558705cb9..103b54301f 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h @@ -302,9 +302,6 @@ extern const size_t pc_compat_2_5_len; extern GlobalProperty pc_compat_2_4[]; extern const size_t pc_compat_2_4_len; -extern GlobalProperty pc_compat_2_3[]; -extern const size_t pc_compat_2_3_len; - #define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn) \ static void pc_machine_##suffix##_class_init(ObjectClass *oc, void *data) \ { \ diff --git a/include/hw/misc/ivshmem-flat.h b/include/hw/misc/ivshmem-flat.h index 97ca0ddce6..0c2b015781 100644 --- a/include/hw/misc/ivshmem-flat.h +++ b/include/hw/misc/ivshmem-flat.h @@ -1,9 +1,10 @@ /* * Inter-VM Shared Memory Flat Device * - * SPDX-FileCopyrightText: 2023 Linaro Ltd. - * SPDX-FileContributor: Gustavo Romero <gustavo.romero@linaro.org> * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2023 Linaro Ltd. + * Authors: + * Gustavo Romero * */ diff --git a/include/hw/pci-host/designware.h b/include/hw/pci-host/designware.h index c484e377a8..bf8b278978 100644 --- a/include/hw/pci-host/designware.h +++ b/include/hw/pci-host/designware.h @@ -86,6 +86,7 @@ struct DesignwarePCIEHost { MemoryRegion io; qemu_irq irqs[4]; + qemu_irq msi; } pci; MemoryRegion mmio; diff --git a/include/migration/cpr.h b/include/migration/cpr.h new file mode 100644 index 0000000000..3a6deb7933 --- /dev/null +++ b/include/migration/cpr.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, 2024 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef MIGRATION_CPR_H +#define MIGRATION_CPR_H + +#include "qapi/qapi-types-migration.h" + +#define MIG_MODE_NONE -1 + +#define QEMU_CPR_FILE_MAGIC 0x51435052 +#define QEMU_CPR_FILE_VERSION 0x00000001 + +void cpr_save_fd(const char *name, int id, int fd); +void cpr_delete_fd(const char *name, int id); +int cpr_find_fd(const char *name, int id); + +MigMode cpr_get_incoming_mode(void); +void cpr_set_incoming_mode(MigMode mode); + +int cpr_state_save(MigrationChannel *channel, Error **errp); +int cpr_state_load(MigrationChannel *channel, Error **errp); +void cpr_state_close(void); +struct QIOChannel *cpr_state_ioc(void); + +QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp); +QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp); + +#endif diff --git a/include/migration/misc.h b/include/migration/misc.h index 67f7ef7a0e..c660be8095 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -108,4 +108,11 @@ bool migration_in_bg_snapshot(void); bool migration_block_activate(Error **errp); bool migration_block_inactivate(void); +/* True if @uri starts with a syntactically valid URI prefix */ +bool migrate_is_uri(const char *uri); + +/* Parse @uri and return @channel, returning true on success */ +bool migrate_uri_parse(const char *uri, MigrationChannel **channel, + Error **errp); + #endif diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index f313f2f408..a1dfab4460 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -230,6 +230,7 @@ extern const VMStateInfo vmstate_info_uint8; extern const VMStateInfo vmstate_info_uint16; extern const VMStateInfo vmstate_info_uint32; extern const VMStateInfo vmstate_info_uint64; +extern const VMStateInfo vmstate_info_fd; /** Put this in the stream when migrating a null pointer.*/ #define VMS_NULLPTR_MARKER (0x30U) /* '0' */ @@ -902,6 +903,9 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_UINT64_V(_f, _s, _v) \ VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint64, uint64_t) +#define VMSTATE_FD_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_fd, int32_t) + #ifdef CONFIG_LINUX #define VMSTATE_U8_V(_f, _s, _v) \ @@ -936,6 +940,9 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_UINT64(_f, _s) \ VMSTATE_UINT64_V(_f, _s, 0) +#define VMSTATE_FD(_f, _s) \ + VMSTATE_FD_V(_f, _s, 0) + #ifdef CONFIG_LINUX #define VMSTATE_U8(_f, _s) \ @@ -1009,6 +1016,8 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_UINT64_TEST(_f, _s, _t) \ VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_info_uint64, uint64_t) +#define VMSTATE_FD_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_info_fd, int32_t) #define VMSTATE_TIMER_PTR_TEST(_f, _s, _test) \ VMSTATE_POINTER_TEST(_f, _s, _test, vmstate_info_timer, QEMUTimer *) diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h index b94fb5fab8..112ebdff21 100644 --- a/include/qemu/osdep.h +++ b/include/qemu/osdep.h @@ -509,6 +509,7 @@ int qemu_daemon(int nochdir, int noclose); void *qemu_anon_ram_alloc(size_t size, uint64_t *align, bool shared, bool noreserve); void qemu_anon_ram_free(void *ptr, size_t size); +int qemu_shm_alloc(size_t size, Error **errp); #ifdef _WIN32 #define HAVE_CHARDEV_SERIAL 1 diff --git a/meson.build b/meson.build index 15a066043b..2c9ac9cfe1 100644 --- a/meson.build +++ b/meson.build @@ -3696,9 +3696,13 @@ libqemuutil = static_library('qemuutil', build_by_default: false, sources: util_ss.sources() + stub_ss.sources() + genh, dependencies: [util_ss.dependencies(), libm, threads, glib, socket, malloc]) +qemuutil_deps = [event_loop_base] +if host_os != 'windows' + qemuutil_deps += [rt] +endif qemuutil = declare_dependency(link_with: libqemuutil, sources: genh + version_res, - dependencies: [event_loop_base]) + dependencies: qemuutil_deps) if have_system or have_user decodetree = generator(find_program('scripts/decodetree.py'), @@ -4357,7 +4361,7 @@ if have_tools subdir('contrib/elf2dmp') executable('qemu-edid', files('qemu-edid.c', 'hw/display/edid-generate.c'), - dependencies: qemuutil, + dependencies: [qemuutil, rt], install: true) if have_vhost_user diff --git a/migration/cpr-transfer.c b/migration/cpr-transfer.c new file mode 100644 index 0000000000..e1f140359c --- /dev/null +++ b/migration/cpr-transfer.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "io/channel-file.h" +#include "io/channel-socket.h" +#include "io/net-listener.h" +#include "migration/cpr.h" +#include "migration/migration.h" +#include "migration/savevm.h" +#include "migration/qemu-file.h" +#include "migration/vmstate.h" +#include "trace.h" + +QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp) +{ + MigrationAddress *addr = channel->addr; + + if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET && + addr->u.socket.type == SOCKET_ADDRESS_TYPE_UNIX) { + + g_autoptr(QIOChannelSocket) sioc = qio_channel_socket_new(); + QIOChannel *ioc = QIO_CHANNEL(sioc); + SocketAddress *saddr = &addr->u.socket; + + if (qio_channel_socket_connect_sync(sioc, saddr, errp) < 0) { + return NULL; + } + trace_cpr_transfer_output(addr->u.socket.u.q_unix.path); + qio_channel_set_name(ioc, "cpr-out"); + return qemu_file_new_output(ioc); + + } else { + error_setg(errp, "bad cpr channel address; must be unix"); + return NULL; + } +} + +QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp) +{ + MigrationAddress *addr = channel->addr; + + if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET && + addr->u.socket.type == SOCKET_ADDRESS_TYPE_UNIX) { + + g_autoptr(QIOChannelSocket) sioc = NULL; + SocketAddress *saddr = &addr->u.socket; + g_autoptr(QIONetListener) listener = qio_net_listener_new(); + QIOChannel *ioc; + + qio_net_listener_set_name(listener, "cpr-socket-listener"); + if (qio_net_listener_open_sync(listener, saddr, 1, errp) < 0) { + return NULL; + } + + sioc = qio_net_listener_wait_client(listener); + ioc = QIO_CHANNEL(sioc); + trace_cpr_transfer_input(addr->u.socket.u.q_unix.path); + qio_channel_set_name(ioc, "cpr-in"); + return qemu_file_new_input(ioc); + + } else { + error_setg(errp, "bad cpr channel socket type; must be unix"); + return NULL; + } +} diff --git a/migration/cpr.c b/migration/cpr.c new file mode 100644 index 0000000000..584b0b98f7 --- /dev/null +++ b/migration/cpr.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2021-2024 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "migration/cpr.h" +#include "migration/misc.h" +#include "migration/options.h" +#include "migration/qemu-file.h" +#include "migration/savevm.h" +#include "migration/vmstate.h" +#include "system/runstate.h" +#include "trace.h" + +/*************************************************************************/ +/* cpr state container for all information to be saved. */ + +typedef QLIST_HEAD(CprFdList, CprFd) CprFdList; + +typedef struct CprState { + CprFdList fds; +} CprState; + +static CprState cpr_state; + +/****************************************************************************/ + +typedef struct CprFd { + char *name; + unsigned int namelen; + int id; + int fd; + QLIST_ENTRY(CprFd) next; +} CprFd; + +static const VMStateDescription vmstate_cpr_fd = { + .name = "cpr fd", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(namelen, CprFd), + VMSTATE_VBUFFER_ALLOC_UINT32(name, CprFd, 0, NULL, namelen), + VMSTATE_INT32(id, CprFd), + VMSTATE_FD(fd, CprFd), + VMSTATE_END_OF_LIST() + } +}; + +void cpr_save_fd(const char *name, int id, int fd) +{ + CprFd *elem = g_new0(CprFd, 1); + + trace_cpr_save_fd(name, id, fd); + elem->name = g_strdup(name); + elem->namelen = strlen(name) + 1; + elem->id = id; + elem->fd = fd; + QLIST_INSERT_HEAD(&cpr_state.fds, elem, next); +} + +static CprFd *find_fd(CprFdList *head, const char *name, int id) +{ + CprFd *elem; + + QLIST_FOREACH(elem, head, next) { + if (!strcmp(elem->name, name) && elem->id == id) { + return elem; + } + } + return NULL; +} + +void cpr_delete_fd(const char *name, int id) +{ + CprFd *elem = find_fd(&cpr_state.fds, name, id); + + if (elem) { + QLIST_REMOVE(elem, next); + g_free(elem->name); + g_free(elem); + } + + trace_cpr_delete_fd(name, id); +} + +int cpr_find_fd(const char *name, int id) +{ + CprFd *elem = find_fd(&cpr_state.fds, name, id); + int fd = elem ? elem->fd : -1; + + trace_cpr_find_fd(name, id, fd); + return fd; +} +/*************************************************************************/ +#define CPR_STATE "CprState" + +static const VMStateDescription vmstate_cpr_state = { + .name = CPR_STATE, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_QLIST_V(fds, CprState, 1, vmstate_cpr_fd, CprFd, next), + VMSTATE_END_OF_LIST() + } +}; +/*************************************************************************/ + +static QEMUFile *cpr_state_file; + +QIOChannel *cpr_state_ioc(void) +{ + return qemu_file_get_ioc(cpr_state_file); +} + +static MigMode incoming_mode = MIG_MODE_NONE; + +MigMode cpr_get_incoming_mode(void) +{ + return incoming_mode; +} + +void cpr_set_incoming_mode(MigMode mode) +{ + incoming_mode = mode; +} + +int cpr_state_save(MigrationChannel *channel, Error **errp) +{ + int ret; + QEMUFile *f; + MigMode mode = migrate_mode(); + + trace_cpr_state_save(MigMode_str(mode)); + + if (mode == MIG_MODE_CPR_TRANSFER) { + f = cpr_transfer_output(channel, errp); + } else { + return 0; + } + if (!f) { + return -1; + } + + 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); + if (ret) { + error_setg(errp, "vmstate_save_state error %d", ret); + qemu_fclose(f); + return ret; + } + + /* + * Close the socket only partially so we can later detect when the other + * end closes by getting a HUP event. + */ + qemu_fflush(f); + qio_channel_shutdown(qemu_file_get_ioc(f), QIO_CHANNEL_SHUTDOWN_WRITE, + NULL); + cpr_state_file = f; + return 0; +} + +int cpr_state_load(MigrationChannel *channel, Error **errp) +{ + int ret; + uint32_t v; + QEMUFile *f; + MigMode mode = 0; + + if (channel) { + mode = MIG_MODE_CPR_TRANSFER; + cpr_set_incoming_mode(mode); + f = cpr_transfer_input(channel, errp); + } else { + return 0; + } + if (!f) { + return -1; + } + + trace_cpr_state_load(MigMode_str(mode)); + + v = qemu_get_be32(f); + if (v != QEMU_CPR_FILE_MAGIC) { + error_setg(errp, "Not a migration stream (bad magic %x)", v); + qemu_fclose(f); + return -EINVAL; + } + v = qemu_get_be32(f); + if (v != QEMU_CPR_FILE_VERSION) { + error_setg(errp, "Unsupported migration stream version %d", v); + qemu_fclose(f); + return -ENOTSUP; + } + + ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1); + if (ret) { + error_setg(errp, "vmstate_load_state error %d", ret); + qemu_fclose(f); + return ret; + } + + /* + * Let the caller decide when to close the socket (and generate a HUP event + * for the sending side). + */ + cpr_state_file = f; + + return ret; +} + +void cpr_state_close(void) +{ + if (cpr_state_file) { + qemu_fclose(cpr_state_file); + cpr_state_file = NULL; + } +} diff --git a/migration/meson.build b/migration/meson.build index dac687ee3a..d3bfe84d62 100644 --- a/migration/meson.build +++ b/migration/meson.build @@ -14,6 +14,8 @@ system_ss.add(files( 'block-active.c', 'channel.c', 'channel-block.c', + 'cpr.c', + 'cpr-transfer.c', 'cpu-throttle.c', 'dirtyrate.c', 'exec.c', diff --git a/migration/migration.c b/migration/migration.c index 2d1da917c7..74c50cc72c 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -14,6 +14,7 @@ */ #include "qemu/osdep.h" +#include "qemu/ctype.h" #include "qemu/cutils.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" @@ -27,6 +28,7 @@ #include "system/cpu-throttle.h" #include "rdma.h" #include "ram.h" +#include "migration/cpr.h" #include "migration/global_state.h" #include "migration/misc.h" #include "migration.h" @@ -75,6 +77,7 @@ 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), }; /* Messages sent on the return path from destination to source */ @@ -102,12 +105,11 @@ static MigrationIncomingState *current_incoming; static GSList *migration_blockers[MIG_MODE__MAX]; static bool migration_object_check(MigrationState *ms, Error **errp); -static int migration_maybe_pause(MigrationState *s, - int *current_active_state, - int new_state); +static bool migration_switchover_start(MigrationState *s, Error **errp); static void migrate_fd_cancel(MigrationState *s); static bool close_return_path_on_source(MigrationState *s); static void migration_completion_end(MigrationState *s); +static void migrate_hup_delete(MigrationState *s); static void migration_downtime_start(MigrationState *s) { @@ -125,9 +127,19 @@ static void migration_downtime_end(MigrationState *s) */ if (!s->downtime) { s->downtime = now - s->downtime_start; + trace_vmstate_downtime_checkpoint("src-downtime-end"); + } +} + +static void precopy_notify_complete(void) +{ + Error *local_err = NULL; + + if (precopy_notify(PRECOPY_NOTIFY_COMPLETE, &local_err)) { + error_report_err(local_err); } - trace_vmstate_downtime_checkpoint("src-downtime-end"); + trace_migration_precopy_complete(); } static bool migration_needs_multiple_sockets(void) @@ -218,6 +230,12 @@ migration_channels_and_transport_compatible(MigrationAddress *addr, return false; } + if (migrate_mode() == MIG_MODE_CPR_TRANSFER && + addr->transport == MIGRATION_ADDRESS_TYPE_FILE) { + error_setg(errp, "Migration requires streamable transport (eg unix)"); + return false; + } + return true; } @@ -433,6 +451,7 @@ void migration_incoming_state_destroy(void) mis->postcopy_qemufile_dst = NULL; } + cpr_set_incoming_mode(MIG_MODE_NONE); yank_unregister_instance(MIGRATION_YANK_INSTANCE); } @@ -586,6 +605,16 @@ void migrate_add_address(SocketAddress *address) QAPI_CLONE(SocketAddress, address)); } +bool migrate_is_uri(const char *uri) +{ + while (*uri && *uri != ':') { + if (!qemu_isalpha(*uri++)) { + return false; + } + } + return *uri == ':'; +} + bool migrate_uri_parse(const char *uri, MigrationChannel **channel, Error **errp) { @@ -683,7 +712,8 @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels, if (channels) { /* To verify that Migrate channel list has only item */ if (channels->next) { - error_setg(errp, "Channel list has more than one entries"); + error_setg(errp, "Channel list must have only one entry, " + "for type 'main'"); return; } addr = channels->value->addr; @@ -734,6 +764,9 @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels, } else { error_setg(errp, "unknown migration protocol: %s", uri); } + + /* Close cpr socket to tell source that we are listening */ + cpr_state_close(); } static void process_incoming_migration_bh(void *opaque) @@ -1397,6 +1430,11 @@ void migrate_set_state(MigrationStatus *state, MigrationStatus old_state, } } +static void migration_cleanup_json_writer(MigrationState *s) +{ + g_clear_pointer(&s->vmdesc, json_writer_free); +} + static void migrate_fd_cleanup(MigrationState *s) { MigrationEventType type; @@ -1404,12 +1442,14 @@ static void migrate_fd_cleanup(MigrationState *s) trace_migrate_fd_cleanup(); + migration_cleanup_json_writer(s); + g_free(s->hostname); s->hostname = NULL; - json_writer_free(s->vmdesc); - s->vmdesc = NULL; qemu_savevm_state_cleanup(); + cpr_state_close(); + migrate_hup_delete(s); close_return_path_on_source(s); @@ -1521,6 +1561,7 @@ static void migrate_fd_error(MigrationState *s, const Error *error) static void migrate_fd_cancel(MigrationState *s) { int old_state ; + bool setup = (s->state == MIGRATION_STATUS_SETUP); trace_migrate_fd_cancel(); @@ -1555,6 +1596,17 @@ static void migrate_fd_cancel(MigrationState *s) } } } + + /* + * If qmp_migrate_finish has not been called, then there is no path that + * will complete the cancellation. Do it now. + */ + if (setup && !s->to_dst_file) { + migrate_set_state(&s->state, MIGRATION_STATUS_CANCELLING, + MIGRATION_STATUS_CANCELLED); + cpr_state_close(); + migrate_hup_delete(s); + } } void migration_add_notifier_mode(NotifierWithReturn *notify, @@ -1652,7 +1704,9 @@ bool migration_thread_is_self(void) bool migrate_mode_is_cpr(MigrationState *s) { - return s->parameters.mode == MIG_MODE_CPR_REBOOT; + MigMode mode = s->parameters.mode; + return mode == MIG_MODE_CPR_REBOOT || + mode == MIG_MODE_CPR_TRANSFER; } int migrate_init(MigrationState *s, Error **errp) @@ -1681,7 +1735,10 @@ int migrate_init(MigrationState *s, Error **errp) s->migration_thread_running = false; error_free(s->error); s->error = NULL; - s->vmdesc = NULL; + + if (should_send_vmdesc()) { + s->vmdesc = json_writer_new(false); + } migrate_set_state(&s->state, MIGRATION_STATUS_NONE, MIGRATION_STATUS_SETUP); @@ -2033,6 +2090,40 @@ static bool migrate_prepare(MigrationState *s, bool resume, Error **errp) return true; } +static void qmp_migrate_finish(MigrationAddress *addr, bool resume_requested, + Error **errp); + +static void migrate_hup_add(MigrationState *s, QIOChannel *ioc, GSourceFunc cb, + void *opaque) +{ + s->hup_source = qio_channel_create_watch(ioc, G_IO_HUP); + g_source_set_callback(s->hup_source, cb, opaque, NULL); + g_source_attach(s->hup_source, NULL); +} + +static void migrate_hup_delete(MigrationState *s) +{ + if (s->hup_source) { + g_source_destroy(s->hup_source); + g_source_unref(s->hup_source); + s->hup_source = NULL; + } +} + +static gboolean qmp_migrate_finish_cb(QIOChannel *channel, + GIOCondition cond, + void *opaque) +{ + MigrationAddress *addr = opaque; + + qmp_migrate_finish(addr, false, NULL); + + cpr_state_close(); + migrate_hup_delete(migrate_get_current()); + qapi_free_MigrationAddress(addr); + return G_SOURCE_REMOVE; +} + void qmp_migrate(const char *uri, bool has_channels, MigrationChannelList *channels, bool has_detach, bool detach, bool has_resume, bool resume, Error **errp) @@ -2042,6 +2133,8 @@ void qmp_migrate(const char *uri, bool has_channels, MigrationState *s = migrate_get_current(); g_autoptr(MigrationChannel) channel = NULL; MigrationAddress *addr = NULL; + MigrationChannel *channelv[MIGRATION_CHANNEL_TYPE__MAX] = { NULL }; + MigrationChannel *cpr_channel = NULL; /* * Having preliminary checks for uri and channel @@ -2052,12 +2145,22 @@ void qmp_migrate(const char *uri, bool has_channels, } if (channels) { - /* To verify that Migrate channel list has only item */ - if (channels->next) { - error_setg(errp, "Channel list has more than one entries"); + for ( ; channels; channels = channels->next) { + MigrationChannelType type = channels->value->channel_type; + + if (channelv[type]) { + error_setg(errp, "Channel list has more than one %s entry", + MigrationChannelType_str(type)); + return; + } + channelv[type] = channels->value; + } + cpr_channel = channelv[MIGRATION_CHANNEL_TYPE_CPR]; + addr = channelv[MIGRATION_CHANNEL_TYPE_MAIN]->addr; + if (!addr) { + error_setg(errp, "Channel list has no main entry"); return; } - addr = channels->value->addr; } if (uri) { @@ -2073,12 +2176,52 @@ void qmp_migrate(const char *uri, bool has_channels, return; } + if (s->parameters.mode == MIG_MODE_CPR_TRANSFER && !cpr_channel) { + error_setg(errp, "missing 'cpr' migration channel"); + return; + } + resume_requested = has_resume && resume; if (!migrate_prepare(s, resume_requested, errp)) { /* Error detected, put into errp */ return; } + if (cpr_state_save(cpr_channel, &local_err)) { + goto out; + } + + /* + * For cpr-transfer, the target may not be listening yet on the migration + * channel, because first it must finish cpr_load_state. The target tells + * us it is listening by closing the cpr-state socket. Wait for that HUP + * event before connecting in qmp_migrate_finish. + * + * The HUP could occur because the target fails while reading CPR state, + * in which case the target will not listen for the incoming migration + * connection, so qmp_migrate_finish will fail to connect, and then recover. + */ + if (s->parameters.mode == MIG_MODE_CPR_TRANSFER) { + migrate_hup_add(s, cpr_state_ioc(), (GSourceFunc)qmp_migrate_finish_cb, + QAPI_CLONE(MigrationAddress, addr)); + + } else { + qmp_migrate_finish(addr, resume_requested, errp); + } + +out: + if (local_err) { + migrate_fd_error(s, local_err); + error_propagate(errp, local_err); + } +} + +static void qmp_migrate_finish(MigrationAddress *addr, bool resume_requested, + Error **errp) +{ + MigrationState *s = migrate_get_current(); + Error *local_err = NULL; + if (!resume_requested) { if (!yank_register_instance(MIGRATION_YANK_INSTANCE, errp)) { return; @@ -2495,8 +2638,14 @@ static int postcopy_start(MigrationState *ms, Error **errp) int ret; QIOChannelBuffer *bioc; QEMUFile *fb; - uint64_t bandwidth = migrate_max_postcopy_bandwidth(); - int cur_state = MIGRATION_STATUS_ACTIVE; + + /* + * Now we're 100% sure to switch to postcopy, so JSON writer won't be + * useful anymore. Free the resources early if it is there. Clearing + * the vmdesc also means any follow up vmstate_save()s will start to + * skip all JSON operations, which can shrink postcopy downtime. + */ + migration_cleanup_json_writer(ms); if (migrate_postcopy_preempt()) { migration_wait_main_channel(ms); @@ -2508,11 +2657,6 @@ static int postcopy_start(MigrationState *ms, Error **errp) } } - if (!migrate_pause_before_switchover()) { - migrate_set_state(&ms->state, MIGRATION_STATUS_ACTIVE, - MIGRATION_STATUS_POSTCOPY_ACTIVE); - } - trace_postcopy_start(); bql_lock(); trace_postcopy_start_set_run(); @@ -2523,16 +2667,7 @@ static int postcopy_start(MigrationState *ms, Error **errp) goto fail; } - ret = migration_maybe_pause(ms, &cur_state, - MIGRATION_STATUS_POSTCOPY_ACTIVE); - if (ret < 0) { - error_setg_errno(errp, -ret, "%s: Failed in migration_maybe_pause()", - __func__); - goto fail; - } - - if (!migration_block_inactivate()) { - error_setg(errp, "%s: Failed in bdrv_inactivate_all()", __func__); + if (!migration_switchover_start(ms, errp)) { goto fail; } @@ -2540,7 +2675,11 @@ static int postcopy_start(MigrationState *ms, Error **errp) * Cause any non-postcopiable, but iterative devices to * send out their final data. */ - qemu_savevm_state_complete_precopy(ms->to_dst_file, true, false); + ret = qemu_savevm_state_complete_precopy_iterable(ms->to_dst_file, true); + if (ret) { + error_setg(errp, "Postcopy save non-postcopiable iterables failed"); + goto fail; + } /* * in Finish migrate and with the io-lock held everything should @@ -2552,12 +2691,6 @@ static int postcopy_start(MigrationState *ms, Error **errp) ram_postcopy_send_discard_bitmap(ms); } - /* - * send rest of state - note things that are doing postcopy - * will notice we're in POSTCOPY_ACTIVE and not actually - * wrap their state up here - */ - migration_rate_set(bandwidth); if (migrate_postcopy_ram()) { /* Ping just for debugging, helps line traces up */ qemu_savevm_send_ping(ms->to_dst_file, 2); @@ -2585,7 +2718,12 @@ static int postcopy_start(MigrationState *ms, Error **errp) */ qemu_savevm_send_postcopy_listen(fb); - qemu_savevm_state_complete_precopy(fb, false, false); + ret = qemu_savevm_state_complete_precopy_non_iterable(fb, true); + if (ret) { + error_setg(errp, "Postcopy save non-iterable device states failed"); + goto fail_closefb; + } + if (migrate_postcopy_ram()) { qemu_savevm_send_ping(fb, 3); } @@ -2619,8 +2757,6 @@ static int postcopy_start(MigrationState *ms, Error **errp) migration_downtime_end(ms); - bql_unlock(); - if (migrate_postcopy_ram()) { /* * Although this ping is just for debug, it could potentially be @@ -2636,11 +2772,22 @@ static int postcopy_start(MigrationState *ms, Error **errp) ret = qemu_file_get_error(ms->to_dst_file); if (ret) { error_setg_errno(errp, -ret, "postcopy_start: Migration stream error"); - bql_lock(); goto fail; } trace_postcopy_preempt_enabled(migrate_postcopy_preempt()); + /* + * Now postcopy officially started, switch to postcopy bandwidth that + * user specified. + */ + migration_rate_set(migrate_max_postcopy_bandwidth()); + + /* Now, switchover looks all fine, switching to postcopy-active */ + migrate_set_state(&ms->state, MIGRATION_STATUS_DEVICE, + MIGRATION_STATUS_POSTCOPY_ACTIVE); + + bql_unlock(); + return ret; fail_closefb: @@ -2655,16 +2802,39 @@ fail: } /** - * migration_maybe_pause: Pause if required to by - * migrate_pause_before_switchover called with the BQL locked - * Returns: 0 on success + * @migration_switchover_prepare: Start VM switchover procedure + * + * @s: The migration state object pointer + * + * Prepares for the switchover, depending on "pause-before-switchover" + * capability. + * + * If cap set, state machine goes like: + * [postcopy-]active -> pre-switchover -> device + * + * If cap not set: + * [postcopy-]active -> device + * + * Returns: true on success, false on interruptions. */ -static int migration_maybe_pause(MigrationState *s, - int *current_active_state, - int new_state) +static bool migration_switchover_prepare(MigrationState *s) { + /* Concurrent cancellation? Quit */ + if (s->state == MIGRATION_STATUS_CANCELLING) { + return false; + } + + /* + * No matter precopy or postcopy, since we still hold BQL it must not + * change concurrently to CANCELLING, so it must be either ACTIVE or + * POSTCOPY_ACTIVE. + */ + assert(migration_is_active()); + + /* If the pre stage not requested, directly switch to DEVICE */ if (!migrate_pause_before_switchover()) { - return 0; + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_DEVICE); + return true; } /* Since leaving this state is not atomic with posting the semaphore @@ -2677,28 +2847,53 @@ static int migration_maybe_pause(MigrationState *s, /* This block intentionally left blank */ } + /* Update [POSTCOPY_]ACTIVE to PRE_SWITCHOVER */ + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_PRE_SWITCHOVER); + bql_unlock(); + + qemu_sem_wait(&s->pause_sem); + + bql_lock(); /* - * If the migration is cancelled when it is in the completion phase, - * the migration state is set to MIGRATION_STATUS_CANCELLING. - * So we don't need to wait a semaphore, otherwise we would always - * wait for the 'pause_sem' semaphore. + * After BQL released and retaken, the state can be CANCELLING if it + * happend during sem_wait().. Only change the state if it's still + * pre-switchover. */ - if (s->state != MIGRATION_STATUS_CANCELLING) { - bql_unlock(); - migrate_set_state(&s->state, *current_active_state, - MIGRATION_STATUS_PRE_SWITCHOVER); - qemu_sem_wait(&s->pause_sem); - migrate_set_state(&s->state, MIGRATION_STATUS_PRE_SWITCHOVER, - new_state); - *current_active_state = new_state; - bql_lock(); + migrate_set_state(&s->state, MIGRATION_STATUS_PRE_SWITCHOVER, + MIGRATION_STATUS_DEVICE); + + return s->state == MIGRATION_STATUS_DEVICE; +} + +static bool migration_switchover_start(MigrationState *s, Error **errp) +{ + ERRP_GUARD(); + + if (!migration_switchover_prepare(s)) { + error_setg(errp, "Switchover is interrupted"); + return false; } - return s->state == new_state ? 0 : -EINVAL; + /* Inactivate disks except in COLO */ + if (!migrate_colo()) { + /* + * Inactivate before sending QEMU_VM_EOF so that the + * bdrv_activate_all() on the other end won't fail. + */ + if (!migration_block_inactivate()) { + error_setg(errp, "Block inactivate failed during switchover"); + return false; + } + } + + migration_rate_set(RATE_LIMIT_DISABLED); + + precopy_notify_complete(); + + return true; } -static int migration_completion_precopy(MigrationState *s, - int *current_active_state) +static int migration_completion_precopy(MigrationState *s) { int ret; @@ -2711,17 +2906,12 @@ static int migration_completion_precopy(MigrationState *s, } } - ret = migration_maybe_pause(s, current_active_state, - MIGRATION_STATUS_DEVICE); - if (ret < 0) { + if (!migration_switchover_start(s, NULL)) { + ret = -EFAULT; goto out_unlock; } - migration_rate_set(RATE_LIMIT_DISABLED); - - /* Inactivate disks except in COLO */ - ret = qemu_savevm_state_complete_precopy(s->to_dst_file, false, - !migrate_colo()); + ret = qemu_savevm_state_complete_precopy(s->to_dst_file, false); out_unlock: bql_unlock(); return ret; @@ -2755,11 +2945,10 @@ static void migration_completion_postcopy(MigrationState *s) static void migration_completion(MigrationState *s) { int ret = 0; - int current_active_state = s->state; Error *local_err = NULL; if (s->state == MIGRATION_STATUS_ACTIVE) { - ret = migration_completion_precopy(s, ¤t_active_state); + ret = migration_completion_precopy(s); } else if (s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) { migration_completion_postcopy(s); } else { @@ -2799,8 +2988,7 @@ fail: error_free(local_err); } - migrate_set_state(&s->state, current_active_state, - MIGRATION_STATUS_FAILED); + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); } /** @@ -3597,12 +3785,8 @@ static void *bg_migration_thread(void *opaque) if (migration_stop_vm(s, RUN_STATE_PAUSED)) { goto fail; } - /* - * Put vCPUs in sync with shadow context structures, then - * save their state to channel-buffer along with devices. - */ - cpu_synchronize_all_states(); - if (qemu_savevm_state_complete_precopy_non_iterable(fb, false, false)) { + + if (qemu_savevm_state_complete_precopy_non_iterable(fb, false)) { goto fail; } /* diff --git a/migration/migration.h b/migration/migration.h index 0df2a187af..4c1fafc2b5 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -468,6 +468,8 @@ struct MigrationState { bool switchover_acked; /* Is this a rdma migration */ bool rdma_migration; + + GSource *hup_source; }; void migrate_set_state(MigrationStatus *state, MigrationStatus old_state, @@ -519,8 +521,6 @@ bool check_dirty_bitmap_mig_alias_map(const BitmapMigrationNodeAliasList *bbm, Error **errp); void migrate_add_address(SocketAddress *address); -bool migrate_uri_parse(const char *uri, MigrationChannel **channel, - Error **errp); int foreach_not_ignored_block(RAMBlockIterFunc func, void *opaque); #define qemu_ram_foreach_block \ @@ -552,6 +552,7 @@ void migration_bitmap_sync_precopy(bool last_stage); /* migration/block-dirty-bitmap.c */ void dirty_bitmap_mig_init(void); +bool should_send_vmdesc(void); /* migration/block-active.c */ void migration_block_active_setup(bool active); diff --git a/migration/options.c b/migration/options.c index b8d5300326..1ad950e397 100644 --- a/migration/options.c +++ b/migration/options.c @@ -22,6 +22,7 @@ #include "qapi/qmp/qnull.h" #include "system/runstate.h" #include "migration/colo.h" +#include "migration/cpr.h" #include "migration/misc.h" #include "migration.h" #include "migration-stats.h" @@ -745,8 +746,11 @@ uint64_t migrate_max_postcopy_bandwidth(void) MigMode migrate_mode(void) { - MigrationState *s = migrate_get_current(); - MigMode mode = s->parameters.mode; + MigMode mode = cpr_get_incoming_mode(); + + if (mode == MIG_MODE_NONE) { + mode = migrate_get_current()->parameters.mode; + } assert(mode >= 0 && mode < MIG_MODE__MAX); return mode; diff --git a/migration/qemu-file.c b/migration/qemu-file.c index b6d2f588bd..1303a5bf58 100644 --- a/migration/qemu-file.c +++ b/migration/qemu-file.c @@ -37,6 +37,11 @@ #define IO_BUF_SIZE 32768 #define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 64) +typedef struct FdEntry { + QTAILQ_ENTRY(FdEntry) entry; + int fd; +} FdEntry; + struct QEMUFile { QIOChannel *ioc; bool is_writable; @@ -51,6 +56,9 @@ struct QEMUFile { int last_error; Error *last_error_obj; + + bool can_pass_fd; + QTAILQ_HEAD(, FdEntry) fds; }; /* @@ -109,6 +117,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) object_ref(ioc); f->ioc = ioc; f->is_writable = is_writable; + f->can_pass_fd = qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_FD_PASS); + QTAILQ_INIT(&f->fds); return f; } @@ -310,6 +320,10 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) int len; int pending; Error *local_error = NULL; + g_autofree int *fds = NULL; + size_t nfd = 0; + int **pfds = f->can_pass_fd ? &fds : NULL; + size_t *pnfd = f->can_pass_fd ? &nfd : NULL; assert(!qemu_file_is_writable(f)); @@ -325,10 +339,9 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) } do { - len = qio_channel_read(f->ioc, - (char *)f->buf + pending, - IO_BUF_SIZE - pending, - &local_error); + struct iovec iov = { f->buf + pending, IO_BUF_SIZE - pending }; + len = qio_channel_readv_full(f->ioc, &iov, 1, pfds, pnfd, 0, + &local_error); if (len == QIO_CHANNEL_ERR_BLOCK) { if (qemu_in_coroutine()) { qio_channel_yield(f->ioc, G_IO_IN); @@ -348,9 +361,66 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) qemu_file_set_error_obj(f, len, local_error); } + for (int i = 0; i < nfd; i++) { + FdEntry *fde = g_new0(FdEntry, 1); + fde->fd = fds[i]; + QTAILQ_INSERT_TAIL(&f->fds, fde, entry); + } + return len; } +int qemu_file_put_fd(QEMUFile *f, int fd) +{ + int ret = 0; + QIOChannel *ioc = qemu_file_get_ioc(f); + Error *err = NULL; + struct iovec iov = { (void *)" ", 1 }; + + /* + * Send a dummy byte so qemu_fill_buffer on the receiving side does not + * fail with a len=0 error. Flush first to maintain ordering wrt other + * data. + */ + + qemu_fflush(f); + if (qio_channel_writev_full(ioc, &iov, 1, &fd, 1, 0, &err) < 1) { + error_report_err(error_copy(err)); + qemu_file_set_error_obj(f, -EIO, err); + ret = -1; + } + trace_qemu_file_put_fd(f->ioc->name, fd, ret); + return ret; +} + +int qemu_file_get_fd(QEMUFile *f) +{ + int fd = -1; + FdEntry *fde; + + if (!f->can_pass_fd) { + Error *err = NULL; + error_setg(&err, "%s does not support fd passing", f->ioc->name); + error_report_err(error_copy(err)); + qemu_file_set_error_obj(f, -EIO, err); + goto out; + } + + /* Force the dummy byte and its fd passenger to appear. */ + qemu_peek_byte(f, 0); + + fde = QTAILQ_FIRST(&f->fds); + if (fde) { + qemu_get_byte(f); /* Drop the dummy byte */ + fd = fde->fd; + QTAILQ_REMOVE(&f->fds, fde, entry); + g_free(fde); + } +out: + trace_qemu_file_get_fd(f->ioc->name, fd); + return fd; +} + /** Closes the file * * Returns negative error value if any error happened on previous operations or @@ -361,11 +431,17 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) */ int qemu_fclose(QEMUFile *f) { + FdEntry *fde, *next; int ret = qemu_fflush(f); int ret2 = qio_channel_close(f->ioc, NULL); if (ret >= 0) { ret = ret2; } + QTAILQ_FOREACH_SAFE(fde, &f->fds, entry, next) { + warn_report("qemu_fclose: received fd %d was never claimed", fde->fd); + close(fde->fd); + g_free(fde); + } g_clear_pointer(&f->ioc, object_unref); error_free(f->last_error_obj); g_free(f); diff --git a/migration/qemu-file.h b/migration/qemu-file.h index 11c2120edd..3e47a20621 100644 --- a/migration/qemu-file.h +++ b/migration/qemu-file.h @@ -79,5 +79,7 @@ size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen, off_t pos); QIOChannel *qemu_file_get_ioc(QEMUFile *file); +int qemu_file_put_fd(QEMUFile *f, int fd); +int qemu_file_get_fd(QEMUFile *f); #endif diff --git a/migration/ram.c b/migration/ram.c index ce28328141..6f460fd22d 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -195,7 +195,9 @@ static bool postcopy_preempt_active(void) bool migrate_ram_is_ignored(RAMBlock *block) { + MigMode mode = migrate_mode(); return !qemu_ram_is_migratable(block) || + mode == MIG_MODE_CPR_TRANSFER || (migrate_ignore_shared() && qemu_ram_is_shared(block) && qemu_ram_is_named_file(block)); } @@ -446,13 +448,6 @@ void ram_transferred_add(uint64_t bytes) } } -struct MigrationOps { - int (*ram_save_target_page)(RAMState *rs, PageSearchStatus *pss); -}; -typedef struct MigrationOps MigrationOps; - -MigrationOps *migration_ops; - static int ram_save_host_page_urgent(PageSearchStatus *pss); /* NOTE: page is the PFN not real ram_addr_t. */ @@ -1958,53 +1953,34 @@ int ram_save_queue_pages(const char *rbname, ram_addr_t start, ram_addr_t len, } /** - * ram_save_target_page_legacy: save one target page - * - * Returns the number of pages written + * ram_save_target_page: save one target page to the precopy thread + * OR to multifd workers. * * @rs: current RAM state * @pss: data about the page we want to send */ -static int ram_save_target_page_legacy(RAMState *rs, PageSearchStatus *pss) +static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss) { ram_addr_t offset = ((ram_addr_t)pss->page) << TARGET_PAGE_BITS; int res; - if (control_save_page(pss, offset, &res)) { - return res; + if (!migrate_multifd() + || migrate_zero_page_detection() == ZERO_PAGE_DETECTION_LEGACY) { + if (save_zero_page(rs, pss, offset)) { + return 1; + } } - if (save_zero_page(rs, pss, offset)) { - return 1; + if (migrate_multifd()) { + RAMBlock *block = pss->block; + return ram_save_multifd_page(block, offset); } - return ram_save_page(rs, pss); -} - -/** - * ram_save_target_page_multifd: send one target page to multifd workers - * - * Returns 1 if the page was queued, -1 otherwise. - * - * @rs: current RAM state - * @pss: data about the page we want to send - */ -static int ram_save_target_page_multifd(RAMState *rs, PageSearchStatus *pss) -{ - RAMBlock *block = pss->block; - ram_addr_t offset = ((ram_addr_t)pss->page) << TARGET_PAGE_BITS; - - /* - * While using multifd live migration, we still need to handle zero - * page checking on the migration main thread. - */ - if (migrate_zero_page_detection() == ZERO_PAGE_DETECTION_LEGACY) { - if (save_zero_page(rs, pss, offset)) { - return 1; - } + if (control_save_page(pss, offset, &res)) { + return res; } - return ram_save_multifd_page(block, offset); + return ram_save_page(rs, pss); } /* Should be called before sending a host page */ @@ -2093,7 +2069,7 @@ static int ram_save_host_page_urgent(PageSearchStatus *pss) if (page_dirty) { /* Be strict to return code; it must be 1, or what else? */ - if (migration_ops->ram_save_target_page(rs, pss) != 1) { + if (ram_save_target_page(rs, pss) != 1) { error_report_once("%s: ram_save_target_page failed", __func__); ret = -1; goto out; @@ -2162,7 +2138,7 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss) if (preempt_active) { qemu_mutex_unlock(&rs->bitmap_mutex); } - tmppages = migration_ops->ram_save_target_page(rs, pss); + tmppages = ram_save_target_page(rs, pss); if (tmppages >= 0) { pages += tmppages; /* @@ -2360,8 +2336,6 @@ static void ram_save_cleanup(void *opaque) xbzrle_cleanup(); multifd_ram_save_cleanup(); ram_state_cleanup(rsp); - g_free(migration_ops); - migration_ops = NULL; } static void ram_state_reset(RAMState *rs) @@ -3027,13 +3001,8 @@ static int ram_save_setup(QEMUFile *f, void *opaque, Error **errp) return ret; } - migration_ops = g_malloc0(sizeof(MigrationOps)); - if (migrate_multifd()) { multifd_ram_save_setup(); - migration_ops->ram_save_target_page = ram_save_target_page_multifd; - } else { - migration_ops->ram_save_target_page = ram_save_target_page_legacy; } /* diff --git a/migration/savevm.c b/migration/savevm.c index c929da1ca5..bc375db282 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -1231,8 +1231,7 @@ void qemu_savevm_non_migratable_list(strList **reasons) void qemu_savevm_state_header(QEMUFile *f) { MigrationState *s = migrate_get_current(); - - s->vmdesc = json_writer_new(false); + JSONWriter *vmdesc = s->vmdesc; trace_savevm_state_header(); qemu_put_be32(f, QEMU_VM_FILE_MAGIC); @@ -1241,16 +1240,21 @@ void qemu_savevm_state_header(QEMUFile *f) if (s->send_configuration) { qemu_put_byte(f, QEMU_VM_CONFIGURATION); - /* - * This starts the main json object and is paired with the - * json_writer_end_object in - * qemu_savevm_state_complete_precopy_non_iterable - */ - json_writer_start_object(s->vmdesc, NULL); + if (vmdesc) { + /* + * This starts the main json object and is paired with the + * json_writer_end_object in + * qemu_savevm_state_complete_precopy_non_iterable + */ + json_writer_start_object(vmdesc, NULL); + json_writer_start_object(vmdesc, "configuration"); + } + + vmstate_save_state(f, &vmstate_configuration, &savevm_state, vmdesc); - json_writer_start_object(s->vmdesc, "configuration"); - vmstate_save_state(f, &vmstate_configuration, &savevm_state, s->vmdesc); - json_writer_end_object(s->vmdesc); + if (vmdesc) { + json_writer_end_object(vmdesc); + } } } @@ -1296,16 +1300,19 @@ int qemu_savevm_state_setup(QEMUFile *f, Error **errp) { ERRP_GUARD(); MigrationState *ms = migrate_get_current(); + JSONWriter *vmdesc = ms->vmdesc; SaveStateEntry *se; int ret = 0; - json_writer_int64(ms->vmdesc, "page_size", qemu_target_page_size()); - json_writer_start_array(ms->vmdesc, "devices"); + if (vmdesc) { + json_writer_int64(vmdesc, "page_size", qemu_target_page_size()); + json_writer_start_array(vmdesc, "devices"); + } trace_savevm_state_setup(); QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { if (se->vmsd && se->vmsd->early_setup) { - ret = vmstate_save(f, se, ms->vmdesc, errp); + ret = vmstate_save(f, se, vmdesc, errp); if (ret) { migrate_set_error(ms, *errp); qemu_file_set_error(f, ret); @@ -1424,11 +1431,11 @@ int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy) return all_finished; } -static bool should_send_vmdesc(void) +bool should_send_vmdesc(void) { MachineState *machine = MACHINE(qdev_get_machine()); - bool in_postcopy = migration_in_postcopy(); - return !machine->suppress_vmdesc && !in_postcopy; + + return !machine->suppress_vmdesc; } /* @@ -1470,7 +1477,6 @@ void qemu_savevm_state_complete_postcopy(QEMUFile *f) qemu_fflush(f); } -static int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy) { int64_t start_ts_each, end_ts_each; @@ -1514,8 +1520,7 @@ int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy) } int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, - bool in_postcopy, - bool inactivate_disks) + bool in_postcopy) { MigrationState *ms = migrate_get_current(); int64_t start_ts_each, end_ts_each; @@ -1525,6 +1530,9 @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, Error *local_err = NULL; int ret; + /* Making sure cpu states are synchronized before saving non-iterable */ + cpu_synchronize_all_states(); + QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { if (se->vmsd && se->vmsd->early_setup) { /* Already saved during qemu_savevm_state_setup(). */ @@ -1546,77 +1554,42 @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, end_ts_each - start_ts_each); } - if (inactivate_disks) { - /* - * Inactivate before sending QEMU_VM_EOF so that the - * bdrv_activate_all() on the other end won't fail. - */ - if (!migration_block_inactivate()) { - error_setg(&local_err, "%s: bdrv_inactivate_all() failed", - __func__); - migrate_set_error(ms, local_err); - error_report_err(local_err); - qemu_file_set_error(f, -EFAULT); - return ret; - } - } if (!in_postcopy) { /* Postcopy stream will still be going */ qemu_put_byte(f, QEMU_VM_EOF); - } - json_writer_end_array(vmdesc); - json_writer_end_object(vmdesc); - vmdesc_len = strlen(json_writer_get(vmdesc)); + if (vmdesc) { + json_writer_end_array(vmdesc); + json_writer_end_object(vmdesc); + vmdesc_len = strlen(json_writer_get(vmdesc)); - if (should_send_vmdesc()) { - qemu_put_byte(f, QEMU_VM_VMDESCRIPTION); - qemu_put_be32(f, vmdesc_len); - qemu_put_buffer(f, (uint8_t *)json_writer_get(vmdesc), vmdesc_len); + qemu_put_byte(f, QEMU_VM_VMDESCRIPTION); + qemu_put_be32(f, vmdesc_len); + qemu_put_buffer(f, (uint8_t *)json_writer_get(vmdesc), vmdesc_len); + } } - /* Free it now to detect any inconsistencies. */ - json_writer_free(vmdesc); - ms->vmdesc = NULL; - trace_vmstate_downtime_checkpoint("src-non-iterable-saved"); return 0; } -int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only, - bool inactivate_disks) +int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only) { int ret; - Error *local_err = NULL; - bool in_postcopy = migration_in_postcopy(); - if (precopy_notify(PRECOPY_NOTIFY_COMPLETE, &local_err)) { - error_report_err(local_err); + ret = qemu_savevm_state_complete_precopy_iterable(f, false); + if (ret) { + return ret; } - trace_savevm_state_complete_precopy(); - - cpu_synchronize_all_states(); - - if (!in_postcopy || iterable_only) { - ret = qemu_savevm_state_complete_precopy_iterable(f, in_postcopy); + if (!iterable_only) { + ret = qemu_savevm_state_complete_precopy_non_iterable(f, false); if (ret) { return ret; } } - if (iterable_only) { - goto flush; - } - - ret = qemu_savevm_state_complete_precopy_non_iterable(f, in_postcopy, - inactivate_disks); - if (ret) { - return ret; - } - -flush: return qemu_fflush(f); } @@ -1714,7 +1687,7 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) ret = qemu_file_get_error(f); if (ret == 0) { - qemu_savevm_state_complete_precopy(f, false, false); + qemu_savevm_state_complete_precopy(f, false); ret = qemu_file_get_error(f); } if (ret != 0) { @@ -1740,7 +1713,7 @@ cleanup: void qemu_savevm_live_state(QEMUFile *f) { /* save QEMU_VM_SECTION_END section */ - qemu_savevm_state_complete_precopy(f, true, false); + qemu_savevm_state_complete_precopy(f, true); qemu_put_byte(f, QEMU_VM_EOF); } @@ -2965,6 +2938,7 @@ int qemu_loadvm_state(QEMUFile *f) return ret; } + /* When reaching here, it must be precopy */ if (ret == 0) { ret = qemu_file_get_error(f); } diff --git a/migration/savevm.h b/migration/savevm.h index 9ec96a995c..7957460062 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -39,12 +39,12 @@ void qemu_savevm_state_header(QEMUFile *f); int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy); void qemu_savevm_state_cleanup(void); void qemu_savevm_state_complete_postcopy(QEMUFile *f); -int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only, - bool inactivate_disks); +int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only); void qemu_savevm_state_pending_exact(uint64_t *must_precopy, uint64_t *can_postcopy); void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, uint64_t *can_postcopy); +int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy); void qemu_savevm_send_ping(QEMUFile *f, uint32_t value); void qemu_savevm_send_open_return_path(QEMUFile *f); int qemu_savevm_send_packaged(QEMUFile *f, const uint8_t *buf, size_t len); @@ -68,6 +68,6 @@ int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis); int qemu_load_device_state(QEMUFile *f); int qemu_loadvm_approve_switchover(void); int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, - bool in_postcopy, bool inactivate_disks); + bool in_postcopy); #endif diff --git a/migration/trace-events b/migration/trace-events index b82a1c5e40..12b262f8ee 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -44,7 +44,6 @@ savevm_state_resume_prepare(void) "" savevm_state_header(void) "" savevm_state_iterate(void) "" savevm_state_cleanup(void) "" -savevm_state_complete_precopy(void) "" vmstate_save(const char *idstr, const char *vmsd_name) "%s, %s" vmstate_load(const char *idstr, const char *vmsd_name) "%s, %s" vmstate_downtime_save(const char *type, const char *idstr, uint32_t instance_id, int64_t downtime) "type=%s idstr=%s instance_id=%d downtime=%"PRIi64 @@ -88,6 +87,8 @@ put_qlist_end(const char *field_name, const char *vmsd_name) "%s(%s)" # qemu-file.c qemu_file_fclose(void) "" +qemu_file_put_fd(const char *name, int fd, int ret) "ioc %s, fd %d -> status %d" +qemu_file_get_fd(const char *name, int fd) "ioc %s -> fd %d" # ram.c get_queued_page(const char *block_name, uint64_t tmp_offset, unsigned long page_abs) "%s/0x%" PRIx64 " page_abs=0x%lx" @@ -193,6 +194,7 @@ migrate_transferred(uint64_t transferred, uint64_t time_spent, uint64_t bandwidt process_incoming_migration_co_end(int ret, int ps) "ret=%d postcopy-state=%d" process_incoming_migration_co_postcopy_end_main(void) "" postcopy_preempt_enabled(bool value) "%d" +migration_precopy_complete(void) "" # migration-stats migration_transferred_bytes(uint64_t qemu_file, uint64_t multifd, uint64_t rdma) "qemu_file %" PRIu64 " multifd %" PRIu64 " RDMA %" PRIu64 @@ -342,6 +344,15 @@ colo_receive_message(const char *msg) "Receive '%s' message" # colo-failover.c colo_failover_set_state(const char *new_state) "new state %s" +# cpr.c +cpr_save_fd(const char *name, int id, int fd) "%s, id %d, fd %d" +cpr_delete_fd(const char *name, int id) "%s, id %d" +cpr_find_fd(const char *name, int id, int fd) "%s, id %d returns %d" +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" + # block-dirty-bitmap.c send_bitmap_header_enter(void) "" send_bitmap_bits(uint32_t flags, uint64_t start_sector, uint32_t nr_sectors, uint64_t data_size) "flags: 0x%x, start_sector: %" PRIu64 ", nr_sectors: %" PRIu32 ", data_size: %" PRIu64 diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c index d70d573dbd..741a588b7e 100644 --- a/migration/vmstate-types.c +++ b/migration/vmstate-types.c @@ -15,6 +15,7 @@ #include "qemu-file.h" #include "migration.h" #include "migration/vmstate.h" +#include "migration/client-options.h" #include "qemu/error-report.h" #include "qemu/queue.h" #include "trace.h" @@ -314,6 +315,29 @@ const VMStateInfo vmstate_info_uint64 = { .put = put_uint64, }; +/* File descriptor communicated via SCM_RIGHTS */ + +static int get_fd(QEMUFile *f, void *pv, size_t size, + const VMStateField *field) +{ + int32_t *v = pv; + *v = qemu_file_get_fd(f); + return 0; +} + +static int put_fd(QEMUFile *f, void *pv, size_t size, + const VMStateField *field, JSONWriter *vmdesc) +{ + int32_t *v = pv; + return qemu_file_put_fd(f, *v); +} + +const VMStateInfo vmstate_info_fd = { + .name = "fd", + .get = get_fd, + .put = put_fd, +}; + static int get_nullptr(QEMUFile *f, void *pv, size_t size, const VMStateField *field) diff --git a/migration/vmstate.c b/migration/vmstate.c index 82bd005a83..047a52af89 100644 --- a/migration/vmstate.c +++ b/migration/vmstate.c @@ -459,6 +459,8 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd, } /* + * This logic only matters when dumping VM Desc. + * * Due to the fake nullptr handling above, if there's mixed * null/non-null data, it doesn't make sense to emit a * compressed array representation spanning the entire array @@ -466,7 +468,7 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd, * vs. nullptr). Search ahead for the next null/non-null element * and start a new compressed array if found. */ - if (field->flags & VMS_ARRAY_OF_POINTER && + if (vmdesc && (field->flags & VMS_ARRAY_OF_POINTER) && is_null != is_prev_null) { is_prev_null = is_null; @@ -504,7 +506,7 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd, written_bytes); /* If we used a fake temp field.. free it now */ - if (inner_field != field) { + if (is_null) { g_clear_pointer((gpointer *)&inner_field, g_free); } diff --git a/net/dump.c b/net/dump.c index d7dd2ce461..140215aa10 100644 --- a/net/dump.c +++ b/net/dump.c @@ -155,7 +155,8 @@ static ssize_t filter_dump_receive_iov(NetFilterState *nf, NetClientState *sndr, { NetFilterDumpState *nfds = FILTER_DUMP(nf); - dump_receive_iov(&nfds->ds, iov, iovcnt, qemu_get_vnet_hdr_len(nf->netdev)); + dump_receive_iov(&nfds->ds, iov, iovcnt, flags & QEMU_NET_PACKET_FLAG_RAW ? + 0 : qemu_get_vnet_hdr_len(nf->netdev)); return 0; } diff --git a/net/net.c b/net/net.c index c1bb19a523..9cded70dde 100644 --- a/net/net.c +++ b/net/net.c @@ -822,6 +822,7 @@ static ssize_t qemu_deliver_packet_iov(NetClientState *sender, iov_copy[0].iov_len = nc->vnet_hdr_len; memcpy(&iov_copy[1], iov, iovcnt * sizeof(*iov)); iov = iov_copy; + iovcnt++; } if (nc->info->receive_iov) { diff --git a/net/slirp.c b/net/slirp.c index 49dc62f776..97d08ed1fb 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -247,7 +247,14 @@ static void net_slirp_timer_mod(void *timer, int64_t expire_timer, timer_mod(&t->timer, expire_timer); } -static void net_slirp_register_poll_fd(int fd, void *opaque) +#if !SLIRP_CHECK_VERSION(4, 9, 0) +# define slirp_os_socket int +# define slirp_pollfds_fill_socket slirp_pollfds_fill +# define register_poll_socket register_poll_fd +# define unregister_poll_socket unregister_poll_fd +#endif + +static void net_slirp_register_poll_sock(slirp_os_socket fd, void *opaque) { #ifdef WIN32 AioContext *ctxt = qemu_get_aio_context(); @@ -260,7 +267,7 @@ static void net_slirp_register_poll_fd(int fd, void *opaque) #endif } -static void net_slirp_unregister_poll_fd(int fd, void *opaque) +static void net_slirp_unregister_poll_sock(slirp_os_socket fd, void *opaque) { #ifdef WIN32 if (WSAEventSelect(fd, NULL, 0) != 0) { @@ -286,8 +293,8 @@ static const SlirpCb slirp_cb = { #endif .timer_free = net_slirp_timer_free, .timer_mod = net_slirp_timer_mod, - .register_poll_fd = net_slirp_register_poll_fd, - .unregister_poll_fd = net_slirp_unregister_poll_fd, + .register_poll_socket = net_slirp_register_poll_sock, + .unregister_poll_socket = net_slirp_unregister_poll_sock, .notify = net_slirp_notify, }; @@ -314,7 +321,7 @@ static int slirp_poll_to_gio(int events) return ret; } -static int net_slirp_add_poll(int fd, int events, void *opaque) +static int net_slirp_add_poll(slirp_os_socket fd, int events, void *opaque) { GArray *pollfds = opaque; GPollFD pfd = { @@ -363,8 +370,8 @@ static void net_slirp_poll_notify(Notifier *notifier, void *data) switch (poll->state) { case MAIN_LOOP_POLL_FILL: - slirp_pollfds_fill(s->slirp, &poll->timeout, - net_slirp_add_poll, poll->pollfds); + slirp_pollfds_fill_socket(s->slirp, &poll->timeout, + net_slirp_add_poll, poll->pollfds); break; case MAIN_LOOP_POLL_OK: case MAIN_LOOP_POLL_ERR: @@ -629,7 +636,9 @@ static int net_slirp_init(NetClientState *peer, const char *model, s = DO_UPCAST(SlirpState, nc, nc); - cfg.version = SLIRP_CHECK_VERSION(4,7,0) ? 4 : 1; + cfg.version = + SLIRP_CHECK_VERSION(4, 9, 0) ? 6 : + SLIRP_CHECK_VERSION(4, 7, 0) ? 4 : 1; cfg.restricted = restricted; cfg.in_enabled = ipv4; cfg.vnetwork = net; diff --git a/qapi/migration.json b/qapi/migration.json index a605dc26db..43babd1df4 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -158,8 +158,11 @@ # # @pre-switchover: Paused before device serialisation. (since 2.11) # -# @device: During device serialisation when pause-before-switchover is -# enabled (since 2.11) +# @device: During device serialisation (also known as switchover phase). +# Before 9.2, this is only used when (1) in precopy, and (2) when +# pre-switchover capability is enabled. After 10.0, this state will +# always be present for every migration procedure as the switchover +# phase. (since 2.11) # # @wait-unplug: wait for device unplug request by guest OS to be # completed. (since 4.2) @@ -614,9 +617,48 @@ # or COLO. # # (since 8.2) +# +# @cpr-transfer: This mode allows the user to transfer a guest to a +# new QEMU instance on the same host with minimal guest pause +# time by preserving guest RAM in place. Devices and their pinned +# pages will also be preserved in a future QEMU release. +# +# The user starts new QEMU on the same host as old QEMU, with +# command-line arguments to create the same machine, plus the +# -incoming option for the main migration channel, like normal +# live migration. In addition, the user adds a second -incoming +# option with channel type "cpr". This CPR channel must support +# file descriptor transfer with SCM_RIGHTS, i.e. it must be a +# UNIX domain socket. +# +# To initiate CPR, the user issues a migrate command to old QEMU, +# adding a second migration channel of type "cpr" in the channels +# argument. Old QEMU stops the VM, saves state to the migration +# channels, and enters the postmigrate state. Execution resumes +# in new QEMU. +# +# New QEMU reads the CPR channel before opening a monitor, hence +# the CPR channel cannot be specified in the list of channels for +# a migrate-incoming command. It may only be specified on the +# command line. +# +# The main channel address cannot be a file type, and for an +# inet socket, the port cannot be 0 (meaning dynamically choose +# a port). +# +# 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. +# +# When using -incoming defer, you must issue the migrate command +# to old QEMU before issuing any monitor commands to new QEMU. +# However, new QEMU does not open and read the migration stream +# until you issue the migrate incoming command. +# +# (since 10.0) ## { 'enum': 'MigMode', - 'data': [ 'normal', 'cpr-reboot' ] } + 'data': [ 'normal', 'cpr-reboot', 'cpr-transfer' ] } ## # @ZeroPageDetection: @@ -1578,11 +1620,12 @@ # The migration channel-type request options. # # @main: Main outbound migration channel. +# @cpr: Checkpoint and restart state channel. # # Since: 8.1 ## { 'enum': 'MigrationChannelType', - 'data': [ 'main' ] } + 'data': [ 'main', 'cpr' ] } ## # @MigrationChannel: diff --git a/qemu-options.hx b/qemu-options.hx index 7090d59f6f..d19bf533d6 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -38,6 +38,9 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " nvdimm=on|off controls NVDIMM support (default=off)\n" " memory-encryption=@var{} memory encryption object to use (default=none)\n" " hmat=on|off controls ACPI HMAT support (default=off)\n" +#ifdef CONFIG_POSIX + " aux-ram-share=on|off allocate auxiliary guest RAM as shared (default: off)\n" +#endif " memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n" " cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n", QEMU_ARCH_ALL) @@ -101,6 +104,16 @@ SRST Enables or disables ACPI Heterogeneous Memory Attribute Table (HMAT) support. The default is off. + ``aux-ram-share=on|off`` + Allocate auxiliary guest RAM as an anonymous file that is + shareable with an external process. This option applies to + memory allocated as a side effect of creating various devices. + It does not apply to memory-backend-objects, whether explicitly + specified on the command line, or implicitly created by the -m + command line option. The default is off. + + To use the cpr-transfer migration mode, you must set aux-ram-share=on. + ``memory-backend='id'`` An alternative to legacy ``-mem-path`` and ``mem-prealloc`` options. Allows to use a memory backend as main RAM. @@ -4929,10 +4942,18 @@ DEF("incoming", HAS_ARG, QEMU_OPTION_incoming, \ "-incoming exec:cmdline\n" \ " accept incoming migration on given file descriptor\n" \ " or from given external command\n" \ + "-incoming <channel>\n" \ + " accept incoming migration on the migration channel\n" \ "-incoming defer\n" \ " wait for the URI to be specified via migrate_incoming\n", QEMU_ARCH_ALL) SRST +The -incoming option specifies the migration channel for an incoming +migration. It may be used multiple times to specify multiple +migration channel types. The channel type is specified in <channel>, +or is 'main' for all other forms of -incoming. If multiple -incoming +options are specified for a channel type, the last one takes precedence. + ``-incoming tcp:[host]:port[,to=maxport][,ipv4=on|off][,ipv6=on|off]`` \ ``-incoming rdma:host:port[,ipv4=on|off][,ipv6=on|off]`` @@ -4952,6 +4973,19 @@ SRST Accept incoming migration as an output from specified external command. +``-incoming <channel>`` + Accept incoming migration on the migration channel. For the syntax + of <channel>, see the QAPI documentation of ``MigrationChannel``. + Examples: + :: + + -incoming '{"channel-type": "main", + "addr": { "transport": "socket", + "type": "unix", + "path": "my.sock" }}' + + -incoming main,addr.transport=socket,addr.type=unix,addr.path=my.sock + ``-incoming defer`` Wait for the URI to be specified via migrate\_incoming. The monitor can be used to change settings (such as migration parameters) prior diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 994c2fc059..8050ede9c8 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,19 +2,27 @@ // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // SPDX-License-Identifier: GPL-2.0-or-later -use core::ptr::{addr_of_mut, NonNull}; +use core::ptr::{addr_of, addr_of_mut, NonNull}; use std::{ ffi::CStr, - os::raw::{c_int, c_uint, c_void}, + os::raw::{c_int, c_void}, }; use qemu_api::{ - bindings::{self, *}, - c_str, + bindings::{ + error_fatal, hwaddr, memory_region_init_io, qdev_init_clock_in, qdev_new, + qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, + qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, sysbus_mmio_map, + sysbus_realize_and_unref, CharBackend, Chardev, Clock, ClockEvent, MemoryRegion, + QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, + }, + c_str, impl_vmstate_forward, irq::InterruptSource, prelude::*, - qdev::DeviceImpl, + qdev::{DeviceImpl, DeviceState, Property}, qom::{ClassInitImpl, ObjectImpl, ParentField}, + sysbus::{SysBusDevice, SysBusDeviceClass}, + vmstate::VMStateDescription, }; use crate::{ @@ -54,6 +62,7 @@ impl DeviceId { #[repr(transparent)] #[derive(Debug, Default)] pub struct Fifo([registers::Data; PL011_FIFO_DEPTH as usize]); +impl_vmstate_forward!(Fifo); impl Fifo { const fn len(&self) -> u32 { @@ -76,11 +85,8 @@ impl std::ops::Index<u32> for Fifo { } #[repr(C)] -#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)] -/// PL011 Device Model in QEMU -pub struct PL011State { - pub parent_obj: ParentField<SysBusDevice>, - pub iomem: MemoryRegion, +#[derive(Debug, Default, qemu_api_macros::offsets)] +pub struct PL011Registers { #[doc(alias = "fr")] pub flags: registers::Flags, #[doc(alias = "lcr")] @@ -100,8 +106,17 @@ pub struct PL011State { pub read_pos: u32, pub read_count: u32, pub read_trigger: u32, +} + +#[repr(C)] +#[derive(qemu_api_macros::Object, qemu_api_macros::offsets)] +/// PL011 Device Model in QEMU +pub struct PL011State { + pub parent_obj: ParentField<SysBusDevice>, + pub iomem: MemoryRegion, #[doc(alias = "chr")] pub char_backend: CharBackend, + pub regs: BqlRefCell<PL011Registers>, /// QEMU interrupts /// /// ```text @@ -123,6 +138,7 @@ pub struct PL011State { qom_isa!(PL011State : SysBusDevice, DeviceState, Object); +#[repr(C)] pub struct PL011Class { parent_class: <SysBusDevice as ObjectType>::Class, /// The byte string that identifies the device. @@ -155,77 +171,17 @@ impl DeviceImpl for PL011State { fn vmsd() -> Option<&'static VMStateDescription> { Some(&device_class::VMSTATE_PL011) } - const REALIZE: Option<fn(&mut Self)> = Some(Self::realize); - const RESET: Option<fn(&mut Self)> = Some(Self::reset); + const REALIZE: Option<fn(&Self)> = Some(Self::realize); + const RESET: Option<fn(&Self)> = Some(Self::reset); } -impl PL011State { - /// Initializes a pre-allocated, unitialized instance of `PL011State`. - /// - /// # Safety - /// - /// `self` must point to a correctly sized and aligned location for the - /// `PL011State` type. It must not be called more than once on the same - /// location/instance. All its fields are expected to hold unitialized - /// values with the sole exception of `parent_obj`. - unsafe fn init(&mut self) { - const CLK_NAME: &CStr = c_str!("clk"); - - // SAFETY: - // - // self and self.iomem are guaranteed to be valid at this point since callers - // must make sure the `self` reference is valid. - unsafe { - memory_region_init_io( - addr_of_mut!(self.iomem), - addr_of_mut!(*self).cast::<Object>(), - &PL011_OPS, - addr_of_mut!(*self).cast::<c_void>(), - Self::TYPE_NAME.as_ptr(), - 0x1000, - ); - } - - // SAFETY: - // - // self.clock is not initialized at this point; but since `NonNull<_>` is Copy, - // we can overwrite the undefined value without side effects. This is - // safe since all PL011State instances are created by QOM code which - // calls this function to initialize the fields; therefore no code is - // able to access an invalid self.clock value. - unsafe { - let dev: &mut DeviceState = self.upcast_mut(); - self.clock = NonNull::new(qdev_init_clock_in( - dev, - CLK_NAME.as_ptr(), - None, /* pl011_clock_update */ - addr_of_mut!(*self).cast::<c_void>(), - ClockEvent::ClockUpdate.0, - )) - .unwrap(); - } - } - - fn post_init(&self) { - self.init_mmio(&self.iomem); - for irq in self.interrupts.iter() { - self.init_irq(irq); - } - } - - pub fn read(&mut self, offset: hwaddr, _size: c_uint) -> std::ops::ControlFlow<u64, u64> { +impl PL011Registers { + pub(self) fn read(&mut self, offset: RegisterOffset) -> (bool, u32) { use RegisterOffset::*; - let value = match RegisterOffset::try_from(offset) { - Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { - let device_id = self.get_class().device_id; - u32::from(device_id[(offset - 0xfe0) >> 2]) - } - Err(_) => { - // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset); - 0 - } - Ok(DR) => { + let mut update = false; + let result = match offset { + DR => { self.flags.set_receive_fifo_full(false); let c = self.read_fifo[self.read_pos]; if self.read_count > 0 { @@ -236,117 +192,109 @@ impl PL011State { self.flags.set_receive_fifo_empty(true); } if self.read_count + 1 == self.read_trigger { - self.int_level &= !registers::INT_RX; + self.int_level &= !Interrupt::RX.0; } // Update error bits. self.receive_status_error_clear.set_from_data(c); - self.update(); - // Must call qemu_chr_fe_accept_input, so return Continue: - let c = u32::from(c); - return std::ops::ControlFlow::Continue(u64::from(c)); + // Must call qemu_chr_fe_accept_input + update = true; + u32::from(c) } - Ok(RSR) => u32::from(self.receive_status_error_clear), - Ok(FR) => u32::from(self.flags), - Ok(FBRD) => self.fbrd, - Ok(ILPR) => self.ilpr, - Ok(IBRD) => self.ibrd, - Ok(LCR_H) => u32::from(self.line_control), - Ok(CR) => u32::from(self.control), - Ok(FLS) => self.ifl, - Ok(IMSC) => self.int_enabled, - Ok(RIS) => self.int_level, - Ok(MIS) => self.int_level & self.int_enabled, - Ok(ICR) => { + RSR => u32::from(self.receive_status_error_clear), + FR => u32::from(self.flags), + FBRD => self.fbrd, + ILPR => self.ilpr, + IBRD => self.ibrd, + LCR_H => u32::from(self.line_control), + CR => u32::from(self.control), + FLS => self.ifl, + IMSC => self.int_enabled, + RIS => self.int_level, + MIS => self.int_level & self.int_enabled, + ICR => { // "The UARTICR Register is the interrupt clear register and is write-only" // Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR 0 } - Ok(DMACR) => self.dmacr, + DMACR => self.dmacr, }; - std::ops::ControlFlow::Break(value.into()) + (update, result) } - pub fn write(&mut self, offset: hwaddr, value: u64) { + pub(self) fn write( + &mut self, + offset: RegisterOffset, + value: u32, + char_backend: *mut CharBackend, + ) -> bool { // eprintln!("write offset {offset} value {value}"); use RegisterOffset::*; - let value: u32 = value as u32; - match RegisterOffset::try_from(offset) { - Err(_bad_offset) => { - eprintln!("write bad offset {offset} value {value}"); + match offset { + DR => { + // interrupts always checked + let _ = self.loopback_tx(value); + self.int_level |= Interrupt::TX.0; + return true; } - Ok(DR) => { - // ??? Check if transmitter is enabled. - let ch: u8 = value as u8; - // XXX this blocks entire thread. Rewrite to use - // qemu_chr_fe_write and background I/O callbacks - - // SAFETY: self.char_backend is a valid CharBackend instance after it's been - // initialized in realize(). - unsafe { - qemu_chr_fe_write_all(addr_of_mut!(self.char_backend), &ch, 1); - } - self.loopback_tx(value); - self.int_level |= registers::INT_TX; - self.update(); - } - Ok(RSR) => { - self.receive_status_error_clear.reset(); + RSR => { + self.receive_status_error_clear = 0.into(); } - Ok(FR) => { + FR => { // flag writes are ignored } - Ok(ILPR) => { + ILPR => { self.ilpr = value; } - Ok(IBRD) => { + IBRD => { self.ibrd = value; } - Ok(FBRD) => { + FBRD => { self.fbrd = value; } - Ok(LCR_H) => { + LCR_H => { let new_val: registers::LineControl = value.into(); // Reset the FIFO state on FIFO enable or disable if self.line_control.fifos_enabled() != new_val.fifos_enabled() { self.reset_rx_fifo(); self.reset_tx_fifo(); } - if self.line_control.send_break() ^ new_val.send_break() { + let update = (self.line_control.send_break() != new_val.send_break()) && { let mut break_enable: c_int = new_val.send_break().into(); // SAFETY: self.char_backend is a valid CharBackend instance after it's been // initialized in realize(). unsafe { qemu_chr_fe_ioctl( - addr_of_mut!(self.char_backend), + char_backend, CHR_IOCTL_SERIAL_SET_BREAK as i32, addr_of_mut!(break_enable).cast::<c_void>(), ); } - self.loopback_break(break_enable > 0); - } + self.loopback_break(break_enable > 0) + }; self.line_control = new_val; self.set_read_trigger(); + return update; } - Ok(CR) => { + CR => { // ??? Need to implement the enable bit. self.control = value.into(); - self.loopback_mdmctrl(); + return self.loopback_mdmctrl(); } - Ok(FLS) => { + FLS => { self.ifl = value; self.set_read_trigger(); } - Ok(IMSC) => { + IMSC => { self.int_enabled = value; - self.update(); + return true; } - Ok(RIS) => {} - Ok(MIS) => {} - Ok(ICR) => { + RIS => {} + MIS => {} + ICR => { self.int_level &= !value; - self.update(); + return true; } - Ok(DMACR) => { + DMACR => { self.dmacr = value; if value & 3 > 0 { // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n"); @@ -354,14 +302,12 @@ impl PL011State { } } } + false } #[inline] - fn loopback_tx(&mut self, value: u32) { - if !self.loopback_enabled() { - return; - } - + #[must_use] + fn loopback_tx(&mut self, value: u32) -> bool { // Caveat: // // In real hardware, TX loopback happens at the serial-bit level @@ -379,12 +325,13 @@ impl PL011State { // hardware flow-control is enabled. // // For simplicity, the above described is not emulated. - self.put_fifo(value); + self.loopback_enabled() && self.put_fifo(value) } - fn loopback_mdmctrl(&mut self) { + #[must_use] + fn loopback_mdmctrl(&mut self) -> bool { if !self.loopback_enabled() { - return; + return false; } /* @@ -410,51 +357,32 @@ impl PL011State { // Change interrupts based on updated FR let mut il = self.int_level; - il &= !Interrupt::MS; + il &= !Interrupt::MS.0; if self.flags.data_set_ready() { - il |= Interrupt::DSR as u32; + il |= Interrupt::DSR.0; } if self.flags.data_carrier_detect() { - il |= Interrupt::DCD as u32; + il |= Interrupt::DCD.0; } if self.flags.clear_to_send() { - il |= Interrupt::CTS as u32; + il |= Interrupt::CTS.0; } if self.flags.ring_indicator() { - il |= Interrupt::RI as u32; + il |= Interrupt::RI.0; } self.int_level = il; - self.update(); + true } - fn loopback_break(&mut self, enable: bool) { - if enable { - self.loopback_tx(registers::Data::BREAK.into()); - } + fn loopback_break(&mut self, enable: bool) -> bool { + enable && self.loopback_tx(registers::Data::BREAK.into()) } fn set_read_trigger(&mut self) { self.read_trigger = 1; } - pub fn realize(&mut self) { - // SAFETY: self.char_backend has the correct size and alignment for a - // CharBackend object, and its callbacks are of the correct types. - unsafe { - qemu_chr_fe_set_handlers( - addr_of_mut!(self.char_backend), - Some(pl011_can_receive), - Some(pl011_receive), - Some(pl011_event), - None, - addr_of_mut!(*self).cast::<c_void>(), - core::ptr::null_mut(), - true, - ); - } - } - pub fn reset(&mut self) { self.line_control.reset(); self.receive_status_error_clear.reset(); @@ -487,17 +415,6 @@ impl PL011State { self.flags.set_transmit_fifo_empty(true); } - pub fn can_receive(&self) -> bool { - // trace_pl011_can_receive(s->lcr, s->read_count, r); - self.read_count < self.fifo_depth() - } - - pub fn event(&mut self, event: QEMUChrEvent) { - if event == bindings::QEMUChrEvent::CHR_EVENT_BREAK && !self.loopback_enabled() { - self.put_fifo(registers::Data::BREAK.into()); - } - } - #[inline] pub fn fifo_enabled(&self) -> bool { self.line_control.fifos_enabled() == registers::Mode::FIFO @@ -517,7 +434,8 @@ impl PL011State { 1 } - pub fn put_fifo(&mut self, value: c_uint) { + #[must_use] + pub fn put_fifo(&mut self, value: u32) -> bool { let depth = self.fifo_depth(); assert!(depth > 0); let slot = (self.read_pos + self.read_count) & (depth - 1); @@ -529,19 +447,13 @@ impl PL011State { } if self.read_count == self.read_trigger { - self.int_level |= registers::INT_RX; - self.update(); + self.int_level |= Interrupt::RX.0; + return true; } + false } - pub fn update(&self) { - let flags = self.int_level & self.int_enabled; - for (irq, i) in self.interrupts.iter().zip(IRQMASK) { - irq.set(flags & i != 0); - } - } - - pub fn post_load(&mut self, _version_id: u32) -> Result<(), ()> { + pub fn post_load(&mut self) -> Result<(), ()> { /* Sanity-check input state */ if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() { return Err(()); @@ -563,19 +475,188 @@ impl PL011State { } } +impl PL011State { + /// Initializes a pre-allocated, unitialized instance of `PL011State`. + /// + /// # Safety + /// + /// `self` must point to a correctly sized and aligned location for the + /// `PL011State` type. It must not be called more than once on the same + /// location/instance. All its fields are expected to hold unitialized + /// values with the sole exception of `parent_obj`. + unsafe fn init(&mut self) { + const CLK_NAME: &CStr = c_str!("clk"); + + // SAFETY: + // + // self and self.iomem are guaranteed to be valid at this point since callers + // must make sure the `self` reference is valid. + unsafe { + memory_region_init_io( + addr_of_mut!(self.iomem), + addr_of_mut!(*self).cast::<Object>(), + &PL011_OPS, + addr_of_mut!(*self).cast::<c_void>(), + Self::TYPE_NAME.as_ptr(), + 0x1000, + ); + } + + self.regs = Default::default(); + + // SAFETY: + // + // self.clock is not initialized at this point; but since `NonNull<_>` is Copy, + // we can overwrite the undefined value without side effects. This is + // safe since all PL011State instances are created by QOM code which + // calls this function to initialize the fields; therefore no code is + // able to access an invalid self.clock value. + unsafe { + let dev: &mut DeviceState = self.upcast_mut(); + self.clock = NonNull::new(qdev_init_clock_in( + dev, + CLK_NAME.as_ptr(), + None, /* pl011_clock_update */ + addr_of_mut!(*self).cast::<c_void>(), + ClockEvent::ClockUpdate.0, + )) + .unwrap(); + } + } + + fn post_init(&self) { + self.init_mmio(&self.iomem); + for irq in self.interrupts.iter() { + self.init_irq(irq); + } + } + + pub fn read(&mut self, offset: hwaddr, _size: u32) -> u64 { + match RegisterOffset::try_from(offset) { + Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { + let device_id = self.get_class().device_id; + u64::from(device_id[(offset - 0xfe0) >> 2]) + } + Err(_) => { + // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset); + 0 + } + Ok(field) => { + let (update_irq, result) = self.regs.borrow_mut().read(field); + if update_irq { + self.update(); + unsafe { + qemu_chr_fe_accept_input(&mut self.char_backend); + } + } + result.into() + } + } + } + + pub fn write(&mut self, offset: hwaddr, value: u64) { + let mut update_irq = false; + 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. + if field == RegisterOffset::DR { + // ??? Check if transmitter is enabled. + let ch: u8 = value as u8; + // SAFETY: char_backend is a valid CharBackend instance after it's been + // initialized in realize(). + // XXX this blocks entire thread. Rewrite to use + // qemu_chr_fe_write and background I/O callbacks + unsafe { + qemu_chr_fe_write_all(&mut self.char_backend, &ch, 1); + } + } + + update_irq = self + .regs + .borrow_mut() + .write(field, value as u32, &mut self.char_backend); + } else { + eprintln!("write bad offset {offset} value {value}"); + } + if update_irq { + self.update(); + } + } + + pub fn can_receive(&self) -> bool { + // trace_pl011_can_receive(s->lcr, s->read_count, r); + let regs = self.regs.borrow(); + regs.read_count < regs.fifo_depth() + } + + pub fn receive(&self, ch: u32) { + let mut regs = self.regs.borrow_mut(); + let update_irq = !regs.loopback_enabled() && regs.put_fifo(ch); + // Release the BqlRefCell before calling self.update() + drop(regs); + + if update_irq { + self.update(); + } + } + + pub fn event(&self, event: QEMUChrEvent) { + let mut update_irq = false; + let mut regs = self.regs.borrow_mut(); + if event == QEMUChrEvent::CHR_EVENT_BREAK && !regs.loopback_enabled() { + update_irq = regs.put_fifo(registers::Data::BREAK.into()); + } + // Release the BqlRefCell before calling self.update() + drop(regs); + + if update_irq { + self.update() + } + } + + pub fn realize(&self) { + // SAFETY: self.char_backend has the correct size and alignment for a + // CharBackend object, and its callbacks are of the correct types. + unsafe { + qemu_chr_fe_set_handlers( + addr_of!(self.char_backend) as *mut CharBackend, + Some(pl011_can_receive), + Some(pl011_receive), + Some(pl011_event), + None, + addr_of!(*self).cast::<c_void>() as *mut c_void, + core::ptr::null_mut(), + true, + ); + } + } + + pub fn reset(&self) { + self.regs.borrow_mut().reset(); + } + + pub fn update(&self) { + let regs = self.regs.borrow(); + let flags = regs.int_level & regs.int_enabled; + for (irq, i) in self.interrupts.iter().zip(IRQMASK) { + irq.set(flags & i != 0); + } + } + + pub fn post_load(&self, _version_id: u32) -> Result<(), ()> { + self.regs.borrow_mut().post_load() + } +} + /// Which bits in the interrupt status matter for each outbound IRQ line ? -pub const IRQMASK: [u32; 6] = [ +const IRQMASK: [u32; 6] = [ /* combined IRQ */ - Interrupt::E - | Interrupt::MS - | Interrupt::RT as u32 - | Interrupt::TX as u32 - | Interrupt::RX as u32, - Interrupt::RX as u32, - Interrupt::TX as u32, - Interrupt::RT as u32, - Interrupt::MS, - Interrupt::E, + Interrupt::E.0 | Interrupt::MS.0 | Interrupt::RT.0 | Interrupt::TX.0 | Interrupt::RX.0, + Interrupt::RX.0, + Interrupt::TX.0, + Interrupt::RT.0, + Interrupt::MS.0, + Interrupt::E.0, ]; /// # Safety @@ -584,11 +665,8 @@ pub const IRQMASK: [u32; 6] = [ /// the same size as [`PL011State`]. We also expect the device is /// readable/writeable from one thread at any time. pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int { - unsafe { - debug_assert!(!opaque.is_null()); - let state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - state.as_ref().can_receive().into() - } + let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + unsafe { state.as_ref().can_receive().into() } } /// # Safety @@ -599,15 +677,11 @@ pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int { /// /// The buffer and size arguments must also be valid. pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size: c_int) { + let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); unsafe { - debug_assert!(!opaque.is_null()); - let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - if state.as_ref().loopback_enabled() { - return; - } if size > 0 { debug_assert!(!buf.is_null()); - state.as_mut().put_fifo(c_uint::from(buf.read_volatile())) + state.as_ref().receive(u32::from(buf.read_volatile())); } } } @@ -618,11 +692,8 @@ pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size /// the same size as [`PL011State`]. We also expect the device is /// readable/writeable from one thread at any time. pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) { - unsafe { - debug_assert!(!opaque.is_null()); - let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - state.as_mut().event(event) - } + let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + unsafe { state.as_ref().event(event) } } /// # Safety @@ -647,7 +718,7 @@ pub unsafe extern "C" fn pl011_create( } #[repr(C)] -#[derive(Debug, qemu_api_macros::Object)] +#[derive(qemu_api_macros::Object)] /// PL011 Luminary device model. pub struct PL011Luminary { parent_obj: ParentField<PL011State>, diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs index 7f3ca89507..8a157a663f 100644 --- a/rust/hw/char/pl011/src/device_class.rs +++ b/rust/hw/char/pl011/src/device_class.rs @@ -6,45 +6,64 @@ use core::ptr::NonNull; use std::os::raw::{c_int, c_void}; use qemu_api::{ - bindings::*, c_str, vmstate_clock, vmstate_fields, vmstate_subsections, vmstate_uint32, - vmstate_uint32_array, vmstate_unused, zeroable::Zeroable, + bindings::*, c_str, prelude::*, vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, + vmstate_subsections, vmstate_unused, zeroable::Zeroable, }; -use crate::device::{PL011State, PL011_FIFO_DEPTH}; +use crate::device::{PL011Registers, PL011State}; +#[allow(clippy::missing_const_for_fn)] extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool { - unsafe { - debug_assert!(!opaque.is_null()); - let state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - state.as_ref().migrate_clock - } + let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + unsafe { state.as_ref().migrate_clock } } /// Migration subsection for [`PL011State`] clock. -pub static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription { +static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription { name: c_str!("pl011/clock").as_ptr(), version_id: 1, minimum_version_id: 1, needed: Some(pl011_clock_needed), fields: vmstate_fields! { - vmstate_clock!(clock, PL011State), + vmstate_clock!(PL011State, clock), }, ..Zeroable::ZERO }; extern "C" fn pl011_post_load(opaque: *mut c_void, version_id: c_int) -> c_int { - unsafe { - debug_assert!(!opaque.is_null()); - let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - let result = state.as_mut().post_load(version_id as u32); - if result.is_err() { - -1 - } else { - 0 - } + let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + let result = unsafe { state.as_ref().post_load(version_id as u32) }; + if result.is_err() { + -1 + } else { + 0 } } +static VMSTATE_PL011_REGS: VMStateDescription = VMStateDescription { + name: c_str!("pl011/regs").as_ptr(), + version_id: 2, + minimum_version_id: 2, + fields: vmstate_fields! { + vmstate_of!(PL011Registers, flags), + vmstate_of!(PL011Registers, line_control), + vmstate_of!(PL011Registers, receive_status_error_clear), + vmstate_of!(PL011Registers, control), + vmstate_of!(PL011Registers, dmacr), + vmstate_of!(PL011Registers, int_enabled), + vmstate_of!(PL011Registers, int_level), + vmstate_of!(PL011Registers, read_fifo), + vmstate_of!(PL011Registers, ilpr), + vmstate_of!(PL011Registers, ibrd), + vmstate_of!(PL011Registers, fbrd), + vmstate_of!(PL011Registers, ifl), + vmstate_of!(PL011Registers, read_pos), + vmstate_of!(PL011Registers, read_count), + vmstate_of!(PL011Registers, read_trigger), + }, + ..Zeroable::ZERO +}; + pub static VMSTATE_PL011: VMStateDescription = VMStateDescription { name: c_str!("pl011").as_ptr(), version_id: 2, @@ -52,21 +71,7 @@ pub static VMSTATE_PL011: VMStateDescription = VMStateDescription { post_load: Some(pl011_post_load), fields: vmstate_fields! { vmstate_unused!(core::mem::size_of::<u32>()), - vmstate_uint32!(flags, PL011State), - vmstate_uint32!(line_control, PL011State), - vmstate_uint32!(receive_status_error_clear, PL011State), - vmstate_uint32!(control, PL011State), - vmstate_uint32!(dmacr, PL011State), - vmstate_uint32!(int_enabled, PL011State), - vmstate_uint32!(int_level, PL011State), - vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH), - vmstate_uint32!(ilpr, PL011State), - vmstate_uint32!(ibrd, PL011State), - vmstate_uint32!(fbrd, PL011State), - vmstate_uint32!(ifl, PL011State), - vmstate_uint32!(read_pos, PL011State), - vmstate_uint32!(read_count, PL011State), - vmstate_uint32!(read_trigger, PL011State), + vmstate_struct!(PL011State, regs, &VMSTATE_PL011_REGS, BqlRefCell<PL011Registers>), }, subsections: vmstate_subsections! { VMSTATE_PL011_CLOCK diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 0a89d393e0..e2df4586bc 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -25,15 +25,13 @@ #![allow(clippy::upper_case_acronyms)] #![allow(clippy::result_unit_err)] -extern crate bilge; -extern crate bilge_impl; -extern crate qemu_api; - use qemu_api::c_str; -pub mod device; -pub mod device_class; -pub mod memory_ops; +mod device; +mod device_class; +mod memory_ops; + +pub use device::pl011_create; pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011"); pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); @@ -45,8 +43,8 @@ pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); #[doc(alias = "offset")] #[allow(non_camel_case_types)] #[repr(u64)] -#[derive(Debug, qemu_api_macros::TryInto)] -pub enum RegisterOffset { +#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] +enum RegisterOffset { /// Data Register /// /// A write to this register initiates the actual data transmission @@ -102,10 +100,11 @@ pub enum RegisterOffset { //Reserved = 0x04C, } -pub mod registers { +mod registers { //! Device registers exposed as typed structs which are backed by arbitrary //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. use bilge::prelude::*; + use qemu_api::impl_vmstate_bitsized; /// Receive Status Register / Data Register common error bits /// @@ -172,6 +171,7 @@ pub mod registers { pub errors: Errors, _reserved: u16, } + impl_vmstate_bitsized!(Data); impl Data { // bilge is not very const-friendly, unfortunately @@ -208,6 +208,7 @@ pub mod registers { pub errors: Errors, _reserved_unpredictable: u24, } + impl_vmstate_bitsized!(ReceiveStatusErrorClear); impl ReceiveStatusErrorClear { pub fn set_from_data(&mut self, data: Data) { @@ -280,6 +281,7 @@ pub mod registers { pub ring_indicator: bool, _reserved_zero_no_modify: u23, } + impl_vmstate_bitsized!(Flags); impl Flags { pub fn reset(&mut self) { @@ -354,6 +356,7 @@ pub mod registers { /// 31:8 - Reserved, do not modify, read as zero. _reserved_zero_no_modify: u24, } + impl_vmstate_bitsized!(LineControl); impl LineControl { pub fn reset(&mut self) { @@ -498,6 +501,7 @@ pub mod registers { /// 31:16 - Reserved, do not modify, read as zero. _reserved_zero_no_modify2: u16, } + impl_vmstate_bitsized!(Control); impl Control { pub fn reset(&mut self) { @@ -516,38 +520,23 @@ pub mod registers { } /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC - pub const INT_OE: u32 = 1 << 10; - pub const INT_BE: u32 = 1 << 9; - pub const INT_PE: u32 = 1 << 8; - pub const INT_FE: u32 = 1 << 7; - pub const INT_RT: u32 = 1 << 6; - pub const INT_TX: u32 = 1 << 5; - pub const INT_RX: u32 = 1 << 4; - pub const INT_DSR: u32 = 1 << 3; - pub const INT_DCD: u32 = 1 << 2; - pub const INT_CTS: u32 = 1 << 1; - pub const INT_RI: u32 = 1 << 0; - pub const INT_E: u32 = INT_OE | INT_BE | INT_PE | INT_FE; - pub const INT_MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS; - - #[repr(u32)] - pub enum Interrupt { - OE = 1 << 10, - BE = 1 << 9, - PE = 1 << 8, - FE = 1 << 7, - RT = 1 << 6, - TX = 1 << 5, - RX = 1 << 4, - DSR = 1 << 3, - DCD = 1 << 2, - CTS = 1 << 1, - RI = 1 << 0, - } + pub struct Interrupt(pub u32); impl Interrupt { - pub const E: u32 = INT_OE | INT_BE | INT_PE | INT_FE; - pub const MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS; + pub const OE: Self = Self(1 << 10); + pub const BE: Self = Self(1 << 9); + pub const PE: Self = Self(1 << 8); + pub const FE: Self = Self(1 << 7); + pub const RT: Self = Self(1 << 6); + pub const TX: Self = Self(1 << 5); + pub const RX: Self = Self(1 << 4); + pub const DSR: Self = Self(1 << 3); + pub const DCD: Self = Self(1 << 2); + pub const CTS: Self = Self(1 << 1); + pub const RI: Self = Self(1 << 0); + + pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); + pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); } } diff --git a/rust/hw/char/pl011/src/memory_ops.rs b/rust/hw/char/pl011/src/memory_ops.rs index c4e8599ba4..432d326389 100644 --- a/rust/hw/char/pl011/src/memory_ops.rs +++ b/rust/hw/char/pl011/src/memory_ops.rs @@ -24,28 +24,11 @@ pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps { }; unsafe extern "C" fn pl011_read(opaque: *mut c_void, addr: hwaddr, size: c_uint) -> u64 { - assert!(!opaque.is_null()); - let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) }; - let val = unsafe { state.as_mut().read(addr, size) }; - match val { - std::ops::ControlFlow::Break(val) => val, - std::ops::ControlFlow::Continue(val) => { - // SAFETY: self.char_backend is a valid CharBackend instance after it's been - // initialized in realize(). - let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) }; - unsafe { - qemu_chr_fe_accept_input(cb_ptr); - } - - val - } - } + let mut state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + unsafe { state.as_mut() }.read(addr, size) } unsafe extern "C" fn pl011_write(opaque: *mut c_void, addr: hwaddr, data: u64, _size: c_uint) { - unsafe { - assert!(!opaque.is_null()); - let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>()); - state.as_mut().write(addr, data) - } + let mut state = NonNull::new(opaque).unwrap().cast::<PL011State>(); + unsafe { state.as_mut() }.write(addr, data); } diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 4ea70b9c82..2dc86e19b2 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -18,3 +18,5 @@ pub use crate::qom::ObjectType; pub use crate::qom_isa; pub use crate::sysbus::SysBusDeviceMethods; + +pub use crate::vmstate::VMState; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 686054e737..f4c75c752f 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -4,7 +4,7 @@ //! Bindings to create devices and access device functionality from Rust. -use std::ffi::CStr; +use std::{ffi::CStr, ptr::NonNull}; pub use bindings::{DeviceClass, DeviceState, Property}; @@ -23,14 +23,14 @@ pub trait DeviceImpl { /// /// If not `None`, the parent class's `realize` method is overridden /// with the function pointed to by `REALIZE`. - const REALIZE: Option<fn(&mut Self)> = None; + const REALIZE: Option<fn(&Self)> = None; /// If not `None`, the parent class's `reset` method is overridden /// with the function pointed to by `RESET`. /// /// Rust does not yet support the three-phase reset protocol; this is /// usually okay for leaf classes. - const RESET: Option<fn(&mut Self)> = None; + const RESET: Option<fn(&Self)> = None; /// An array providing the properties that the user can set on the /// device. Not a `const` because referencing statics in constants @@ -55,9 +55,8 @@ pub trait DeviceImpl { /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>(dev: *mut DeviceState, _errp: *mut *mut Error) { - assert!(!dev.is_null()); - let state = dev.cast::<T>(); - T::REALIZE.unwrap()(unsafe { &mut *state }); + let state = NonNull::new(dev).unwrap().cast::<T>(); + T::REALIZE.unwrap()(unsafe { state.as_ref() }); } /// # Safety @@ -66,9 +65,8 @@ unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>(dev: *mut DeviceState, _errp /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_reset_fn<T: DeviceImpl>(dev: *mut DeviceState) { - assert!(!dev.is_null()); - let state = dev.cast::<T>(); - T::RESET.unwrap()(unsafe { &mut *state }); + let mut state = NonNull::new(dev).unwrap().cast::<T>(); + T::RESET.unwrap()(unsafe { state.as_mut() }); } impl<T> ClassInitImpl<DeviceClass> for T diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 97901fb908..f50ee371aa 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -58,6 +58,7 @@ use std::{ fmt, ops::{Deref, DerefMut}, os::raw::c_void, + ptr::NonNull, }; pub use bindings::{Object, ObjectClass}; @@ -153,27 +154,34 @@ impl<T: fmt::Display + ObjectType> fmt::Display for ParentField<T> { } unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut Object) { + let mut state = NonNull::new(obj).unwrap().cast::<T>(); // SAFETY: obj is an instance of T, since rust_instance_init<T> // is called from QOM core as the instance_init function // for class T - unsafe { T::INSTANCE_INIT.unwrap()(&mut *obj.cast::<T>()) } + unsafe { + T::INSTANCE_INIT.unwrap()(state.as_mut()); + } } unsafe extern "C" fn rust_instance_post_init<T: ObjectImpl>(obj: *mut Object) { + let state = NonNull::new(obj).unwrap().cast::<T>(); // SAFETY: obj is an instance of T, since rust_instance_post_init<T> // is called from QOM core as the instance_post_init function // for class T - T::INSTANCE_POST_INIT.unwrap()(unsafe { &*obj.cast::<T>() }) + T::INSTANCE_POST_INIT.unwrap()(unsafe { state.as_ref() }); } unsafe extern "C" fn rust_class_init<T: ObjectType + ClassInitImpl<T::Class>>( klass: *mut ObjectClass, _data: *mut c_void, ) { + let mut klass = NonNull::new(klass) + .unwrap() + .cast::<<T as ObjectType>::Class>(); // SAFETY: klass is a T::Class, since rust_class_init<T> // is called from QOM core as the class_init function // for class T - T::class_init(unsafe { &mut *klass.cast::<T::Class>() }) + T::class_init(unsafe { klass.as_mut() }) } unsafe extern "C" fn drop_object<T: ObjectImpl>(obj: *mut Object) { @@ -581,11 +589,8 @@ pub trait ClassInitImpl<T> { /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_unparent_fn<T: ObjectImpl>(dev: *mut Object) { - unsafe { - assert!(!dev.is_null()); - let state = core::ptr::NonNull::new_unchecked(dev.cast::<T>()); - T::UNPARENT.unwrap()(state.as_ref()); - } + let state = NonNull::new(dev).unwrap().cast::<T>(); + T::UNPARENT.unwrap()(unsafe { state.as_ref() }); } impl<T> ClassInitImpl<ObjectClass> for T diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 63c897abcd..6ac432cf52 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -4,277 +4,477 @@ //! Helper macros to declare migration state for device models. //! -//! Some macros are direct equivalents to the C macros declared in -//! `include/migration/vmstate.h` while -//! [`vmstate_subsections`](crate::vmstate_subsections) and -//! [`vmstate_fields`](crate::vmstate_fields) are meant to be used when -//! declaring a device model state struct. +//! This module includes four families of macros: +//! +//! * [`vmstate_unused!`](crate::vmstate_unused) and +//! [`vmstate_of!`](crate::vmstate_of), which are used to express the +//! migration format for a struct. This is based on the [`VMState`] trait, +//! which is defined by all migrateable types. +//! +//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward) and +//! [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), which help with +//! the definition of the [`VMState`] trait (respectively for transparent +//! structs and for `bilge`-defined types) +//! +//! * helper macros to declare a device model state struct, in particular +//! [`vmstate_subsections`](crate::vmstate_subsections) and +//! [`vmstate_fields`](crate::vmstate_fields). +//! +//! * direct equivalents to the C macros declared in +//! `include/migration/vmstate.h`. These are not type-safe and only provide +//! functionality that is missing from `vmstate_of!`. -pub use crate::bindings::VMStateDescription; +use core::{marker::PhantomData, mem, ptr::NonNull}; -#[doc(alias = "VMSTATE_UNUSED_BUFFER")] +pub use crate::bindings::{VMStateDescription, VMStateField}; +use crate::{ + bindings::{self, VMStateFlags}, + zeroable::Zeroable, +}; + +/// This macro is used to call a function with a generic argument bound +/// to the type of a field. The function must take a +/// [`PhantomData`]`<T>` argument; `T` is the type of +/// field `$field` in the `$typ` type. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::call_func_with_field; +/// # use core::marker::PhantomData; +/// const fn size_of_field<T>(_: PhantomData<T>) -> usize { +/// std::mem::size_of::<T>() +/// } +/// +/// struct Foo { +/// x: u16, +/// }; +/// // calls size_of_field::<u16>() +/// assert_eq!(call_func_with_field!(size_of_field, Foo, x), 2); +/// ``` #[macro_export] -macro_rules! vmstate_unused_buffer { - ($field_exists_fn:expr, $version_id:expr, $size:expr) => {{ - $crate::bindings::VMStateField { - name: c_str!("unused").as_ptr(), - err_hint: ::core::ptr::null(), - offset: 0, - size: $size, - start: 0, - num: 0, - num_offset: 0, - size_offset: 0, - info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) }, - flags: VMStateFlags::VMS_BUFFER, - vmsd: ::core::ptr::null(), - version_id: $version_id, - struct_version_id: 0, - field_exists: $field_exists_fn, - } - }}; +macro_rules! call_func_with_field { + // Based on the answer by user steffahn (Frank Steffahn) at + // https://users.rust-lang.org/t/inferring-type-of-field/122857 + // and used under MIT license + ($func:expr, $typ:ty, $($field:tt).+) => { + $func(loop { + #![allow(unreachable_code)] + const fn phantom__<T>(_: &T) -> ::core::marker::PhantomData<T> { ::core::marker::PhantomData } + // Unreachable code is exempt from checks on uninitialized values. + // Use that trick to infer the type of this PhantomData. + break ::core::marker::PhantomData; + break phantom__(&{ let value__: $typ; value__.$($field).+ }); + }) + }; } -#[doc(alias = "VMSTATE_UNUSED_V")] -#[macro_export] -macro_rules! vmstate_unused_v { - ($version_id:expr, $size:expr) => {{ - $crate::vmstate_unused_buffer!(None, $version_id, $size) - }}; +/// Workaround for lack of `const_refs_static`: references to global variables +/// can be included in a `static`, but not in a `const`; unfortunately, this +/// is exactly what would go in the `VMStateField`'s `info` member. +/// +/// This enum contains the contents of the `VMStateField`'s `info` member, +/// but as an `enum` instead of a pointer. +#[allow(non_camel_case_types)] +pub enum VMStateFieldType { + null, + vmstate_info_bool, + vmstate_info_int8, + vmstate_info_int16, + vmstate_info_int32, + vmstate_info_int64, + vmstate_info_uint8, + vmstate_info_uint16, + vmstate_info_uint32, + vmstate_info_uint64, + vmstate_info_timer, } -#[doc(alias = "VMSTATE_UNUSED")] +/// Workaround for lack of `const_refs_static`. Converts a `VMStateFieldType` +/// to a `*const VMStateInfo`, for inclusion in a `VMStateField`. #[macro_export] -macro_rules! vmstate_unused { - ($size:expr) => {{ - $crate::vmstate_unused_v!(0, $size) - }}; +macro_rules! info_enum_to_ref { + ($e:expr) => { + unsafe { + match $e { + $crate::vmstate::VMStateFieldType::null => ::core::ptr::null(), + $crate::vmstate::VMStateFieldType::vmstate_info_bool => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_bool) + } + $crate::vmstate::VMStateFieldType::vmstate_info_int8 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_int8) + } + $crate::vmstate::VMStateFieldType::vmstate_info_int16 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_int16) + } + $crate::vmstate::VMStateFieldType::vmstate_info_int32 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_int32) + } + $crate::vmstate::VMStateFieldType::vmstate_info_int64 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_int64) + } + $crate::vmstate::VMStateFieldType::vmstate_info_uint8 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint8) + } + $crate::vmstate::VMStateFieldType::vmstate_info_uint16 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint16) + } + $crate::vmstate::VMStateFieldType::vmstate_info_uint32 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32) + } + $crate::vmstate::VMStateFieldType::vmstate_info_uint64 => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint64) + } + $crate::vmstate::VMStateFieldType::vmstate_info_timer => { + ::core::ptr::addr_of!($crate::bindings::vmstate_info_timer) + } + } + } + }; +} + +/// A trait for types that can be included in a device's migration stream. It +/// provides the base contents of a `VMStateField` (minus the name and offset). +/// +/// # Safety +/// +/// The contents of this trait go straight into structs that are parsed by C +/// code and used to introspect into other structs. Generally, you don't need +/// to implement it except via macros that do it for you, such as +/// `impl_vmstate_bitsized!`. +pub unsafe trait VMState { + /// The `info` member of a `VMStateField` is a pointer and as such cannot + /// yet be included in the [`BASE`](VMState::BASE) associated constant; + /// this is only allowed by Rust 1.83.0 and newer. For now, include the + /// member as an enum which is stored in a separate constant. + const SCALAR_TYPE: VMStateFieldType = VMStateFieldType::null; + + /// The base contents of a `VMStateField` (minus the name and offset) for + /// the type that is implementing the trait. + const BASE: VMStateField; + + /// A flag that is added to another field's `VMStateField` to specify the + /// length's type in a variable-sized array. If this is not a supported + /// type for the length (i.e. if it is not `u8`, `u16`, `u32`), using it + /// in a call to [`vmstate_of!`](crate::vmstate_of) will cause a + /// compile-time error. + const VARRAY_FLAG: VMStateFlags = { + panic!("invalid type for variable-sized array"); + }; +} + +/// Internal utility function to retrieve a type's `VMStateFieldType`; +/// used by [`vmstate_of!`](crate::vmstate_of). +pub const fn vmstate_scalar_type<T: VMState>(_: PhantomData<T>) -> VMStateFieldType { + T::SCALAR_TYPE +} + +/// Internal utility function to retrieve a type's `VMStateField`; +/// used by [`vmstate_of!`](crate::vmstate_of). +pub const fn vmstate_base<T: VMState>(_: PhantomData<T>) -> VMStateField { + T::BASE +} + +/// Internal utility function to retrieve a type's `VMStateFlags` when it +/// is used as the element count of a `VMSTATE_VARRAY`; used by +/// [`vmstate_of!`](crate::vmstate_of). +pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags { + T::VARRAY_FLAG } -#[doc(alias = "VMSTATE_SINGLE_TEST")] +/// Return the `VMStateField` for a field of a struct. The field must be +/// visible in the current scope. +/// +/// Only a limited set of types is supported out of the box: +/// * scalar types (integer and `bool`) +/// * the C struct `QEMUTimer` +/// * a transparent wrapper for any of the above (`Cell`, `UnsafeCell`, +/// [`BqlCell`](crate::cell::BqlCell), [`BqlRefCell`](crate::cell::BqlRefCell) +/// * a raw pointer to any of the above +/// * a `NonNull` pointer or a `Box` for any of the above +/// * an array of any of the above +/// +/// In order to support other types, the trait `VMState` must be implemented +/// for them. The macros +/// [`impl_vmstate_bitsized!`](crate::impl_vmstate_bitsized) +/// and [`impl_vmstate_forward!`](crate::impl_vmstate_forward) help with this. #[macro_export] -macro_rules! vmstate_single_test { - ($field_name:ident, $struct_name:ty, $field_exists_fn:expr, $version_id:expr, $info:expr, $size:expr) => {{ +macro_rules! vmstate_of { + ($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])? $(,)?) => { $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), 0) + name: ::core::concat!(::core::stringify!($field_name), "\0") .as_bytes() .as_ptr() as *const ::std::os::raw::c_char, - err_hint: ::core::ptr::null(), offset: $crate::offset_of!($struct_name, $field_name), - size: $size, - start: 0, - num: 0, - num_offset: 0, - size_offset: 0, - info: unsafe { $info }, - flags: VMStateFlags::VMS_SINGLE, - vmsd: ::core::ptr::null(), - version_id: $version_id, - struct_version_id: 0, - field_exists: $field_exists_fn, + $(.num_offset: $crate::offset_of!($struct_name, $num),)? + // The calls to `call_func_with_field!` are the magic that + // computes most of the VMStateField from the type of the field. + info: $crate::info_enum_to_ref!($crate::call_func_with_field!( + $crate::vmstate::vmstate_scalar_type, + $struct_name, + $field_name + )), + ..$crate::call_func_with_field!( + $crate::vmstate::vmstate_base, + $struct_name, + $field_name + )$(.with_varray_flag($crate::call_func_with_field!( + $crate::vmstate::vmstate_varray_flag, + $struct_name, + $num)) + $(.with_varray_multiply($factor))?)? } - }}; + }; } -#[doc(alias = "VMSTATE_SINGLE")] -#[macro_export] -macro_rules! vmstate_single { - ($field_name:ident, $struct_name:ty, $version_id:expr, $info:expr, $size:expr) => {{ - $crate::vmstate_single_test!($field_name, $struct_name, None, $version_id, $info, $size) - }}; +impl VMStateFlags { + const VMS_VARRAY_FLAGS: VMStateFlags = VMStateFlags( + VMStateFlags::VMS_VARRAY_INT32.0 + | VMStateFlags::VMS_VARRAY_UINT8.0 + | VMStateFlags::VMS_VARRAY_UINT16.0 + | VMStateFlags::VMS_VARRAY_UINT32.0, + ); } -#[doc(alias = "VMSTATE_UINT32_V")] -#[macro_export] -macro_rules! vmstate_uint32_v { - ($field_name:ident, $struct_name:ty, $version_id:expr) => {{ - $crate::vmstate_single!( - $field_name, - $struct_name, - $version_id, - ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32), - ::core::mem::size_of::<u32>() - ) - }}; +// Add a couple builder-style methods to VMStateField, allowing +// easy derivation of VMStateField constants from other types. +impl VMStateField { + #[must_use] + pub const fn with_version_id(mut self, version_id: i32) -> Self { + assert!(version_id >= 0); + self.version_id = version_id; + self + } + + #[must_use] + pub const fn with_array_flag(mut self, num: usize) -> Self { + assert!(num <= 0x7FFF_FFFFusize); + assert!((self.flags.0 & VMStateFlags::VMS_ARRAY.0) == 0); + assert!((self.flags.0 & VMStateFlags::VMS_VARRAY_FLAGS.0) == 0); + if (self.flags.0 & VMStateFlags::VMS_POINTER.0) != 0 { + self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_POINTER.0); + self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0); + } + self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_SINGLE.0); + self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_ARRAY.0); + self.num = num as i32; + self + } + + #[must_use] + pub const fn with_pointer_flag(mut self) -> Self { + assert!((self.flags.0 & VMStateFlags::VMS_POINTER.0) == 0); + self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_POINTER.0); + self + } + + #[must_use] + pub const fn with_varray_flag<T: VMState>(mut self, flag: VMStateFlags) -> VMStateField { + assert!((self.flags.0 & VMStateFlags::VMS_ARRAY.0) != 0); + self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_ARRAY.0); + self.flags = VMStateFlags(self.flags.0 | flag.0); + self + } + + #[must_use] + pub const fn with_varray_multiply(mut self, num: u32) -> VMStateField { + assert!(num <= 0x7FFF_FFFFu32); + self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0); + self.num = num as i32; + self + } } -#[doc(alias = "VMSTATE_UINT32")] +/// This macro can be used (by just passing it a type) to forward the `VMState` +/// trait to the first field of a tuple. This is a workaround for lack of +/// support of nested [`offset_of`](core::mem::offset_of) until Rust 1.82.0. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::vmstate::impl_vmstate_forward; +/// pub struct Fifo([u8; 16]); +/// impl_vmstate_forward!(Fifo); +/// ``` #[macro_export] -macro_rules! vmstate_uint32 { - ($field_name:ident, $struct_name:ty) => {{ - $crate::vmstate_uint32_v!($field_name, $struct_name, 0) - }}; +macro_rules! impl_vmstate_forward { + // This is similar to impl_vmstate_transparent below, but it + // uses the same trick as vmstate_of! to obtain the type of + // the first field of the tuple + ($tuple:ty) => { + unsafe impl $crate::vmstate::VMState for $tuple { + const SCALAR_TYPE: $crate::vmstate::VMStateFieldType = + $crate::call_func_with_field!($crate::vmstate::vmstate_scalar_type, $tuple, 0); + const BASE: $crate::bindings::VMStateField = + $crate::call_func_with_field!($crate::vmstate::vmstate_base, $tuple, 0); + } + }; } -#[doc(alias = "VMSTATE_ARRAY")] -#[macro_export] -macro_rules! vmstate_array { - ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr, $info:expr, $size:expr) => {{ - $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), 0) - .as_bytes() - .as_ptr() as *const ::std::os::raw::c_char, - err_hint: ::core::ptr::null(), - offset: $crate::offset_of!($struct_name, $field_name), - size: $size, - start: 0, - num: $length as _, - num_offset: 0, - size_offset: 0, - info: unsafe { $info }, - flags: VMStateFlags::VMS_ARRAY, - vmsd: ::core::ptr::null(), - version_id: $version_id, - struct_version_id: 0, - field_exists: None, +// Transparent wrappers: just use the internal type + +macro_rules! impl_vmstate_transparent { + ($type:ty where $base:tt: VMState $($where:tt)*) => { + unsafe impl<$base> VMState for $type where $base: VMState $($where)* { + const SCALAR_TYPE: VMStateFieldType = <$base as VMState>::SCALAR_TYPE; + const BASE: VMStateField = VMStateField { + size: mem::size_of::<$type>(), + ..<$base as VMState>::BASE + }; + const VARRAY_FLAG: VMStateFlags = <$base as VMState>::VARRAY_FLAG; } - }}; + }; } -#[doc(alias = "VMSTATE_UINT32_ARRAY_V")] +impl_vmstate_transparent!(std::cell::Cell<T> where T: VMState); +impl_vmstate_transparent!(std::cell::UnsafeCell<T> where T: VMState); +impl_vmstate_transparent!(crate::cell::BqlCell<T> where T: VMState); +impl_vmstate_transparent!(crate::cell::BqlRefCell<T> where T: VMState); + #[macro_export] -macro_rules! vmstate_uint32_array_v { - ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr) => {{ - $crate::vmstate_array!( - $field_name, - $struct_name, - $length, - $version_id, - ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32), - ::core::mem::size_of::<u32>() - ) - }}; +macro_rules! impl_vmstate_bitsized { + ($type:ty) => { + unsafe impl $crate::vmstate::VMState for $type { + const SCALAR_TYPE: $crate::vmstate::VMStateFieldType = + <<<$type as ::bilge::prelude::Bitsized>::ArbitraryInt + as ::bilge::prelude::Number>::UnderlyingType + as $crate::vmstate::VMState>::SCALAR_TYPE; + const BASE: $crate::bindings::VMStateField = + <<<$type as ::bilge::prelude::Bitsized>::ArbitraryInt + as ::bilge::prelude::Number>::UnderlyingType + as $crate::vmstate::VMState>::BASE; + const VARRAY_FLAG: $crate::bindings::VMStateFlags = + <<<$type as ::bilge::prelude::Bitsized>::ArbitraryInt + as ::bilge::prelude::Number>::UnderlyingType + as $crate::vmstate::VMState>::VARRAY_FLAG; + } + }; } -#[doc(alias = "VMSTATE_UINT32_ARRAY")] -#[macro_export] -macro_rules! vmstate_uint32_array { - ($field_name:ident, $struct_name:ty, $length:expr) => {{ - $crate::vmstate_uint32_array_v!($field_name, $struct_name, $length, 0) - }}; +// Scalar types using predefined VMStateInfos + +macro_rules! impl_vmstate_scalar { + ($info:ident, $type:ty$(, $varray_flag:ident)?) => { + unsafe impl VMState for $type { + const SCALAR_TYPE: VMStateFieldType = VMStateFieldType::$info; + const BASE: VMStateField = VMStateField { + size: mem::size_of::<$type>(), + flags: VMStateFlags::VMS_SINGLE, + ..Zeroable::ZERO + }; + $(const VARRAY_FLAG: VMStateFlags = VMStateFlags::$varray_flag;)? + } + }; } -#[doc(alias = "VMSTATE_STRUCT_POINTER_V")] -#[macro_export] -macro_rules! vmstate_struct_pointer_v { - ($field_name:ident, $struct_name:ty, $version_id:expr, $vmsd:expr, $type:ty) => {{ - $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), 0) - .as_bytes() - .as_ptr() as *const ::std::os::raw::c_char, - err_hint: ::core::ptr::null(), - offset: $crate::offset_of!($struct_name, $field_name), - size: ::core::mem::size_of::<*const $type>(), - start: 0, - num: 0, - num_offset: 0, - size_offset: 0, - info: ::core::ptr::null(), - flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0), - vmsd: unsafe { $vmsd }, - version_id: $version_id, - struct_version_id: 0, - field_exists: None, +impl_vmstate_scalar!(vmstate_info_bool, bool); +impl_vmstate_scalar!(vmstate_info_int8, i8); +impl_vmstate_scalar!(vmstate_info_int16, i16); +impl_vmstate_scalar!(vmstate_info_int32, i32); +impl_vmstate_scalar!(vmstate_info_int64, i64); +impl_vmstate_scalar!(vmstate_info_uint8, u8, VMS_VARRAY_UINT8); +impl_vmstate_scalar!(vmstate_info_uint16, u16, VMS_VARRAY_UINT16); +impl_vmstate_scalar!(vmstate_info_uint32, u32, VMS_VARRAY_UINT32); +impl_vmstate_scalar!(vmstate_info_uint64, u64); +impl_vmstate_scalar!(vmstate_info_timer, bindings::QEMUTimer); + +// Pointer types using the underlying type's VMState plus VMS_POINTER +// Note that references are not supported, though references to cells +// could be allowed. + +macro_rules! impl_vmstate_pointer { + ($type:ty where $base:tt: VMState $($where:tt)*) => { + unsafe impl<$base> VMState for $type where $base: VMState $($where)* { + const SCALAR_TYPE: VMStateFieldType = <T as VMState>::SCALAR_TYPE; + const BASE: VMStateField = <$base as VMState>::BASE.with_pointer_flag(); } - }}; + }; +} + +impl_vmstate_pointer!(*const T where T: VMState); +impl_vmstate_pointer!(*mut T where T: VMState); +impl_vmstate_pointer!(NonNull<T> where T: VMState); + +// Unlike C pointers, Box is always non-null therefore there is no need +// to specify VMS_ALLOC. +impl_vmstate_pointer!(Box<T> where T: VMState); + +// Arrays using the underlying type's VMState plus +// VMS_ARRAY/VMS_ARRAY_OF_POINTER + +unsafe impl<T: VMState, const N: usize> VMState for [T; N] { + const SCALAR_TYPE: VMStateFieldType = <T as VMState>::SCALAR_TYPE; + const BASE: VMStateField = <T as VMState>::BASE.with_array_flag(N); } -#[doc(alias = "VMSTATE_ARRAY_OF_POINTER")] +#[doc(alias = "VMSTATE_UNUSED")] #[macro_export] -macro_rules! vmstate_array_of_pointer { - ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $info:expr, $type:ty) => {{ +macro_rules! vmstate_unused { + ($size:expr) => {{ $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), 0) - .as_bytes() - .as_ptr() as *const ::std::os::raw::c_char, - version_id: $version_id, - num: $num as _, - info: unsafe { $info }, - size: ::core::mem::size_of::<*const $type>(), - flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0), - offset: $crate::offset_of!($struct_name, $field_name), - err_hint: ::core::ptr::null(), - start: 0, - num_offset: 0, - size_offset: 0, - vmsd: ::core::ptr::null(), - struct_version_id: 0, - field_exists: None, + name: $crate::c_str!("unused").as_ptr(), + size: $size, + info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) }, + flags: $crate::bindings::VMStateFlags::VMS_BUFFER, + ..$crate::zeroable::Zeroable::ZERO } }}; } -#[doc(alias = "VMSTATE_ARRAY_OF_POINTER_TO_STRUCT")] +// FIXME: including the `vmsd` field in a `const` is not possible without +// the const_refs_static feature (stabilized in Rust 1.83.0). Without it, +// it is not possible to use VMS_STRUCT in a transparent manner using +// `vmstate_of!`. While VMSTATE_CLOCK can at least try to be type-safe, +// VMSTATE_STRUCT includes $type only for documentation purposes; it +// is checked against $field_name and $struct_name, but not against $vmsd +// which is what really would matter. +#[doc(alias = "VMSTATE_STRUCT")] #[macro_export] -macro_rules! vmstate_array_of_pointer_to_struct { - ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $vmsd:expr, $type:ty) => {{ +macro_rules! vmstate_struct { + ($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?, $vmsd:expr, $type:ty $(,)?) => { $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), 0) + name: ::core::concat!(::core::stringify!($field_name), "\0") .as_bytes() .as_ptr() as *const ::std::os::raw::c_char, - version_id: $version_id, - num: $num as _, + $(.num_offset: $crate::offset_of!($struct_name, $num),)? + offset: { + $crate::assert_field_type!($struct_name, $field_name, $type); + $crate::offset_of!($struct_name, $field_name) + }, + size: ::core::mem::size_of::<$type>(), + flags: $crate::bindings::VMStateFlags::VMS_STRUCT, vmsd: unsafe { $vmsd }, - size: ::core::mem::size_of::<*const $type>(), - flags: VMStateFlags( - VMStateFlags::VMS_ARRAY.0 - | VMStateFlags::VMS_STRUCT.0 - | VMStateFlags::VMS_ARRAY_OF_POINTER.0, - ), - offset: $crate::offset_of!($struct_name, $field_name), - err_hint: ::core::ptr::null(), - start: 0, - num_offset: 0, - size_offset: 0, - vmsd: ::core::ptr::null(), - struct_version_id: 0, - field_exists: None, + ..$crate::zeroable::Zeroable::ZERO $( + .with_varray_flag($crate::call_func_with_field!( + $crate::vmstate::vmstate_varray_flag, + $struct_name, + $num)) + $(.with_varray_multiply($factor))?)? } - }}; -} - -#[doc(alias = "VMSTATE_CLOCK_V")] -#[macro_export] -macro_rules! vmstate_clock_v { - ($field_name:ident, $struct_name:ty, $version_id:expr) => {{ - $crate::vmstate_struct_pointer_v!( - $field_name, - $struct_name, - $version_id, - ::core::ptr::addr_of!($crate::bindings::vmstate_clock), - $crate::bindings::Clock - ) - }}; + }; } #[doc(alias = "VMSTATE_CLOCK")] #[macro_export] macro_rules! vmstate_clock { - ($field_name:ident, $struct_name:ty) => {{ - $crate::vmstate_clock_v!($field_name, $struct_name, 0) - }}; -} - -#[doc(alias = "VMSTATE_ARRAY_CLOCK_V")] -#[macro_export] -macro_rules! vmstate_array_clock_v { - ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr) => {{ - $crate::vmstate_array_of_pointer_to_struct!( - $field_name, - $struct_name, - $num, - $version_id, - ::core::ptr::addr_of!($crate::bindings::vmstate_clock), - $crate::bindings::Clock - ) - }}; -} - -#[doc(alias = "VMSTATE_ARRAY_CLOCK")] -#[macro_export] -macro_rules! vmstate_array_clock { - ($field_name:ident, $struct_name:ty, $num:expr) => {{ - $crate::vmstate_array_clock_v!($field_name, $struct_name, $name, 0) + ($struct_name:ty, $field_name:ident) => {{ + $crate::bindings::VMStateField { + name: ::core::concat!(::core::stringify!($field_name), "\0") + .as_bytes() + .as_ptr() as *const ::std::os::raw::c_char, + offset: { + $crate::assert_field_type!( + $struct_name, + $field_name, + core::ptr::NonNull<$crate::bindings::Clock> + ); + $crate::offset_of!($struct_name, $field_name) + }, + size: ::core::mem::size_of::<*const $crate::bindings::Clock>(), + flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0), + vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) }, + ..$crate::zeroable::Zeroable::ZERO + } }}; } @@ -287,20 +487,8 @@ macro_rules! vmstate_fields { static _FIELDS: &[$crate::bindings::VMStateField] = &[ $($field),*, $crate::bindings::VMStateField { - name: ::core::ptr::null(), - err_hint: ::core::ptr::null(), - offset: 0, - size: 0, - start: 0, - num: 0, - num_offset: 0, - size_offset: 0, - info: ::core::ptr::null(), - flags: VMStateFlags::VMS_END, - vmsd: ::core::ptr::null(), - version_id: 0, - struct_version_id: 0, - field_exists: None, + flags: $crate::bindings::VMStateFlags::VMS_END, + ..$crate::zeroable::Zeroable::ZERO } ]; _FIELDS.as_ptr() diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 6125aeed8b..7b04947cb6 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -1,13 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -use std::ptr; - /// Encapsulates the requirement that /// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined /// behavior. This trait in principle could be implemented as just: /// /// ``` -/// pub unsafe trait Zeroable: Default { +/// pub unsafe trait Zeroable { /// const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() }; /// } /// ``` @@ -29,58 +27,76 @@ pub unsafe trait Zeroable: Default { const ZERO: Self; } -unsafe impl Zeroable for crate::bindings::Property__bindgen_ty_1 { - const ZERO: Self = Self { i: 0 }; -} - -unsafe impl Zeroable for crate::bindings::Property { - const ZERO: Self = Self { - name: ptr::null(), - info: ptr::null(), - offset: 0, - bitnr: 0, - bitmask: 0, - set_default: false, - defval: Zeroable::ZERO, - arrayoffset: 0, - arrayinfo: ptr::null(), - arrayfieldsize: 0, - link_type: ptr::null(), - }; +/// A macro that acts similarly to [`core::mem::zeroed()`], only is const +/// +/// ## Safety +/// +/// Similar to `core::mem::zeroed()`, except this zeroes padding bits. Zeroed +/// padding usually isn't relevant to safety, but might be if a C union is used. +/// +/// Just like for `core::mem::zeroed()`, an all zero byte pattern might not +/// be a valid value for a type, as is the case for references `&T` and `&mut +/// T`. Reference types trigger a (denied by default) lint and cause immediate +/// undefined behavior if the lint is ignored +/// +/// ```rust compile_fail +/// use const_zero::const_zero; +/// // error: any use of this value will cause an error +/// // note: `#[deny(const_err)]` on by default +/// const STR: &str = unsafe{const_zero!(&'static str)}; +/// ``` +/// +/// `const_zero` does not work on unsized types: +/// +/// ```rust compile_fail +/// use const_zero::const_zero; +/// // error[E0277]: the size for values of type `[u8]` cannot be known at compilation time +/// const BYTES: [u8] = unsafe{const_zero!([u8])}; +/// ``` +/// ## Differences with `core::mem::zeroed` +/// +/// `const_zero` zeroes padding bits, while `core::mem::zeroed` doesn't +macro_rules! const_zero { + // This macro to produce a type-generic zero constant is taken from the + // const_zero crate (v0.1.1): + // + // https://docs.rs/const-zero/latest/src/const_zero/lib.rs.html + // + // and used under MIT license + ($type_:ty) => {{ + const TYPE_SIZE: ::core::primitive::usize = ::core::mem::size_of::<$type_>(); + union TypeAsBytes { + bytes: [::core::primitive::u8; TYPE_SIZE], + inner: ::core::mem::ManuallyDrop<$type_>, + } + const ZERO_BYTES: TypeAsBytes = TypeAsBytes { + bytes: [0; TYPE_SIZE], + }; + ::core::mem::ManuallyDrop::<$type_>::into_inner(ZERO_BYTES.inner) + }}; } -unsafe impl Zeroable for crate::bindings::VMStateDescription { - const ZERO: Self = Self { - name: ptr::null(), - unmigratable: false, - early_setup: false, - version_id: 0, - minimum_version_id: 0, - priority: crate::bindings::MigrationPriority::MIG_PRI_DEFAULT, - pre_load: None, - post_load: None, - pre_save: None, - post_save: None, - needed: None, - dev_unplug_pending: None, - fields: ptr::null(), - subsections: ptr::null(), +/// A wrapper to implement the `Zeroable` trait through the `const_zero` macro. +macro_rules! impl_zeroable { + ($type:ty) => { + unsafe impl Zeroable for $type { + const ZERO: Self = unsafe { const_zero!($type) }; + } }; } -unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 { - const ZERO: Self = Self { - min_access_size: 0, - max_access_size: 0, - unaligned: false, - accepts: None, - }; +// bindgen does not derive Default here +#[allow(clippy::derivable_impls)] +impl Default for crate::bindings::VMStateFlags { + fn default() -> Self { + Self(0) + } } -unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 { - const ZERO: Self = Self { - min_access_size: 0, - max_access_size: 0, - unaligned: false, - }; -} +impl_zeroable!(crate::bindings::Property__bindgen_ty_1); +impl_zeroable!(crate::bindings::Property); +impl_zeroable!(crate::bindings::VMStateFlags); +impl_zeroable!(crate::bindings::VMStateField); +impl_zeroable!(crate::bindings::VMStateDescription); +impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); +impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 526c3f4f8e..5c3e75ed3d 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -14,8 +14,8 @@ use qemu_api::{ cell::{self, BqlCell}, declare_properties, define_property, prelude::*, - qdev::{DeviceImpl, DeviceState, Property}, - qom::{ObjectImpl, ParentField}, + qdev::{DeviceClass, DeviceImpl, DeviceState, Property}, + qom::{ClassInitImpl, ObjectImpl, ParentField}, vmstate::VMStateDescription, zeroable::Zeroable, }; @@ -37,6 +37,10 @@ pub struct DummyState { qom_isa!(DummyState: Object, DeviceState); +pub struct DummyClass { + parent_class: <DeviceState as ObjectType>::Class, +} + declare_properties! { DUMMY_PROPERTIES, define_property!( @@ -49,7 +53,7 @@ declare_properties! { } unsafe impl ObjectType for DummyState { - type Class = <DeviceState as ObjectType>::Class; + type Class = DummyClass; const TYPE_NAME: &'static CStr = c_str!("dummy"); } @@ -67,6 +71,51 @@ impl DeviceImpl for DummyState { } } +// `impl<T> ClassInitImpl<DummyClass> for T` doesn't work since it violates +// orphan rule. +impl ClassInitImpl<DummyClass> for DummyState { + fn class_init(klass: &mut DummyClass) { + <Self as ClassInitImpl<DeviceClass>>::class_init(&mut klass.parent_class); + } +} + +#[derive(qemu_api_macros::offsets)] +#[repr(C)] +#[derive(qemu_api_macros::Object)] +pub struct DummyChildState { + parent: ParentField<DummyState>, +} + +qom_isa!(DummyChildState: Object, DeviceState, DummyState); + +pub struct DummyChildClass { + parent_class: <DummyState as ObjectType>::Class, +} + +unsafe impl ObjectType for DummyChildState { + type Class = DummyChildClass; + const TYPE_NAME: &'static CStr = c_str!("dummy_child"); +} + +impl ObjectImpl for DummyChildState { + type ParentType = DummyState; + const ABSTRACT: bool = false; +} + +impl DeviceImpl for DummyChildState {} + +impl ClassInitImpl<DummyClass> for DummyChildState { + fn class_init(klass: &mut DummyClass) { + <Self as ClassInitImpl<DeviceClass>>::class_init(&mut klass.parent_class); + } +} + +impl ClassInitImpl<DummyChildClass> for DummyChildState { + fn class_init(klass: &mut DummyChildClass) { + <Self as ClassInitImpl<DummyClass>>::class_init(&mut klass.parent_class); + } +} + fn init_qom() { static ONCE: BqlCell<bool> = BqlCell::new(false); @@ -85,6 +134,7 @@ fn test_object_new() { init_qom(); unsafe { object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast()); + object_unref(object_new(DummyChildState::TYPE_NAME.as_ptr()).cast()); } } diff --git a/scripts/qom-cast-macro-clean-cocci-gen.py b/scripts/qom-cast-macro-clean-cocci-gen.py index 2fa8438a14..5aa51d0c18 100644 --- a/scripts/qom-cast-macro-clean-cocci-gen.py +++ b/scripts/qom-cast-macro-clean-cocci-gen.py @@ -13,8 +13,11 @@ # --in-place \ # --dir . # -# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> -# SPDX-FileCopyrightText: 2023 Linaro Ltd. +# Copyright (c) 2023 Linaro Ltd. +# +# Authors: +# Philippe Mathieu-Daudé +# # SPDX-License-Identifier: GPL-2.0-or-later import re diff --git a/stubs/meson.build b/stubs/meson.build index e91614a874..a8b3aeb564 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -57,8 +57,8 @@ if have_user stub_ss.add(files('cpu-synchronize-state.c')) # Stubs for QAPI events. Those can always be included in the build, but - # they are not built at all for --disable-system --disable-tools builds. - if not (have_system or have_tools) + # they are not built at all for --disable-system builds. + if not have_system stub_ss.add(files('qdev.c')) endif endif diff --git a/stubs/vmstate.c b/stubs/vmstate.c index 8513d9204e..c190762d7c 100644 --- a/stubs/vmstate.c +++ b/stubs/vmstate.c @@ -1,5 +1,7 @@ #include "qemu/osdep.h" #include "migration/vmstate.h" +#include "qapi/qapi-types-migration.h" +#include "migration/client-options.h" int vmstate_register_with_alias_id(VMStateIf *obj, uint32_t instance_id, @@ -21,3 +23,8 @@ bool vmstate_check_only_migratable(const VMStateDescription *vmsd) { return true; } + +MigMode migrate_mode(void) +{ + return MIG_MODE_NORMAL; +} diff --git a/system/memory.c b/system/memory.c index b17b5538ff..4c829793a0 100644 --- a/system/memory.c +++ b/system/memory.c @@ -1680,8 +1680,8 @@ bool memory_region_init_ram_from_fd(MemoryRegion *mr, mr->readonly = !!(ram_flags & RAM_READONLY); mr->terminates = true; mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc_from_fd(size, mr, ram_flags, fd, offset, - &err); + mr->ram_block = qemu_ram_alloc_from_fd(size, size, NULL, mr, ram_flags, fd, + offset, false, &err); if (err) { mr->size = int128_zero(); object_unparent(OBJECT(mr)); diff --git a/system/physmem.c b/system/physmem.c index c76503aea8..67c9db9daa 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -48,6 +48,7 @@ #include "qemu/qemu-print.h" #include "qemu/log.h" #include "qemu/memalign.h" +#include "qemu/memfd.h" #include "exec/memory.h" #include "exec/ioport.h" #include "system/dma.h" @@ -69,6 +70,7 @@ #include "qemu/pmem.h" +#include "migration/cpr.h" #include "migration/vmstate.h" #include "qemu/range.h" @@ -1660,6 +1662,18 @@ void qemu_ram_unset_idstr(RAMBlock *block) } } +static char *cpr_name(MemoryRegion *mr) +{ + const char *mr_name = memory_region_name(mr); + g_autofree char *id = mr->dev ? qdev_get_dev_path(mr->dev) : NULL; + + if (id) { + return g_strdup_printf("%s/%s", id, mr_name); + } else { + return g_strdup(mr_name); + } +} + size_t qemu_ram_pagesize(RAMBlock *rb) { return rb->page_size; @@ -1942,18 +1956,27 @@ out_free: } #ifdef CONFIG_POSIX -RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr, +RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, ram_addr_t max_size, + qemu_ram_resize_cb resized, MemoryRegion *mr, uint32_t ram_flags, int fd, off_t offset, + bool grow, Error **errp) { + ERRP_GUARD(); RAMBlock *new_block; Error *local_err = NULL; - int64_t file_size, file_align; + int64_t file_size, file_align, share_flags; + + share_flags = ram_flags & (RAM_PRIVATE | RAM_SHARED); + assert(share_flags != (RAM_SHARED | RAM_PRIVATE)); + ram_flags &= ~RAM_PRIVATE; /* Just support these ram flags by now. */ assert((ram_flags & ~(RAM_SHARED | RAM_PMEM | RAM_NORESERVE | RAM_PROTECTED | RAM_NAMED_FILE | RAM_READONLY | - RAM_READONLY_FD | RAM_GUEST_MEMFD)) == 0); + RAM_READONLY_FD | RAM_GUEST_MEMFD | + RAM_RESIZEABLE)) == 0); + assert(max_size >= size); if (xen_enabled()) { error_setg(errp, "-mem-path not supported with Xen"); @@ -1968,12 +1991,16 @@ RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr, size = TARGET_PAGE_ALIGN(size); size = REAL_HOST_PAGE_ALIGN(size); + max_size = TARGET_PAGE_ALIGN(max_size); + max_size = REAL_HOST_PAGE_ALIGN(max_size); file_size = get_file_size(fd); - if (file_size > offset && file_size < (offset + size)) { - error_setg(errp, "backing store size 0x%" PRIx64 - " does not match 'size' option 0x" RAM_ADDR_FMT, - file_size, size); + if (file_size && file_size < offset + max_size && !grow) { + error_setg(errp, "%s backing store size 0x%" PRIx64 + " is too small for 'size' option 0x" RAM_ADDR_FMT + " plus 'offset' option 0x%" PRIx64, + memory_region_name(mr), file_size, max_size, + (uint64_t)offset); return NULL; } @@ -1988,11 +2015,13 @@ RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr, new_block = g_malloc0(sizeof(*new_block)); new_block->mr = mr; new_block->used_length = size; - new_block->max_length = size; + new_block->max_length = max_size; + new_block->resized = resized; new_block->flags = ram_flags; new_block->guest_memfd = -1; - new_block->host = file_ram_alloc(new_block, size, fd, !file_size, offset, - errp); + new_block->host = file_ram_alloc(new_block, max_size, fd, + file_size < offset + max_size, + offset, errp); if (!new_block->host) { g_free(new_block); return NULL; @@ -2044,7 +2073,8 @@ RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, return NULL; } - block = qemu_ram_alloc_from_fd(size, mr, ram_flags, fd, offset, errp); + block = qemu_ram_alloc_from_fd(size, size, NULL, mr, ram_flags, fd, offset, + false, errp); if (!block) { if (created) { unlink(mem_path); @@ -2057,21 +2087,97 @@ RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, } #endif +#ifdef CONFIG_POSIX +/* + * Create MAP_SHARED RAMBlocks by mmap'ing a file descriptor, so it can be + * shared with another process if CPR is being used. Use memfd if available + * because it has no size limits, else use POSIX shm. + */ +static int qemu_ram_get_shared_fd(const char *name, bool *reused, Error **errp) +{ + int fd = cpr_find_fd(name, 0); + + if (fd >= 0) { + *reused = true; + return fd; + } + + if (qemu_memfd_check(0)) { + fd = qemu_memfd_create(name, 0, 0, 0, 0, errp); + } else { + fd = qemu_shm_alloc(0, errp); + } + + if (fd >= 0) { + cpr_save_fd(name, 0, fd); + } + *reused = false; + return fd; +} +#endif + static RAMBlock *qemu_ram_alloc_internal(ram_addr_t size, ram_addr_t max_size, - void (*resized)(const char*, - uint64_t length, - void *host), + qemu_ram_resize_cb resized, void *host, uint32_t ram_flags, MemoryRegion *mr, Error **errp) { RAMBlock *new_block; Error *local_err = NULL; - int align; + int align, share_flags; + + share_flags = ram_flags & (RAM_PRIVATE | RAM_SHARED); + assert(share_flags != (RAM_SHARED | RAM_PRIVATE)); + ram_flags &= ~RAM_PRIVATE; assert((ram_flags & ~(RAM_SHARED | RAM_RESIZEABLE | RAM_PREALLOC | RAM_NORESERVE | RAM_GUEST_MEMFD)) == 0); assert(!host ^ (ram_flags & RAM_PREALLOC)); + assert(max_size >= size); + +#ifdef CONFIG_POSIX /* ignore RAM_SHARED for Windows */ + if (!host) { + if (!share_flags && current_machine->aux_ram_share) { + ram_flags |= RAM_SHARED; + } + if (ram_flags & RAM_SHARED) { + bool reused; + g_autofree char *name = cpr_name(mr); + int fd = qemu_ram_get_shared_fd(name, &reused, errp); + + if (fd < 0) { + return NULL; + } + + /* Use same alignment as qemu_anon_ram_alloc */ + mr->align = QEMU_VMALLOC_ALIGN; + + /* + * This can fail if the shm mount size is too small, or alloc from + * fd is not supported, but previous QEMU versions that called + * qemu_anon_ram_alloc for anonymous shared memory could have + * succeeded. Quietly fail and fall back. + * + * After cpr-transfer, new QEMU could create a memory region + * with a larger max size than old, so pass reused to grow the + * region if necessary. The extra space will be usable after a + * guest reset. + */ + new_block = qemu_ram_alloc_from_fd(size, max_size, resized, mr, + ram_flags, fd, 0, reused, NULL); + if (new_block) { + trace_qemu_ram_alloc_shared(name, new_block->used_length, + new_block->max_length, fd, + new_block->host); + return new_block; + } + + cpr_delete_fd(name, 0); + close(fd); + /* fall back to anon allocation */ + } + } +#endif align = qemu_real_host_page_size(); align = MAX(align, TARGET_PAGE_SIZE); @@ -2083,7 +2189,6 @@ RAMBlock *qemu_ram_alloc_internal(ram_addr_t size, ram_addr_t max_size, new_block->resized = resized; new_block->used_length = size; new_block->max_length = max_size; - assert(max_size >= size); new_block->fd = -1; new_block->guest_memfd = -1; new_block->page_size = qemu_real_host_page_size(); @@ -2108,15 +2213,14 @@ RAMBlock *qemu_ram_alloc_from_ptr(ram_addr_t size, void *host, RAMBlock *qemu_ram_alloc(ram_addr_t size, uint32_t ram_flags, MemoryRegion *mr, Error **errp) { - assert((ram_flags & ~(RAM_SHARED | RAM_NORESERVE | RAM_GUEST_MEMFD)) == 0); + assert((ram_flags & ~(RAM_SHARED | RAM_NORESERVE | RAM_GUEST_MEMFD | + RAM_PRIVATE)) == 0); return qemu_ram_alloc_internal(size, size, NULL, NULL, ram_flags, mr, errp); } RAMBlock *qemu_ram_alloc_resizeable(ram_addr_t size, ram_addr_t maxsz, - void (*resized)(const char*, - uint64_t length, - void *host), - MemoryRegion *mr, Error **errp) + qemu_ram_resize_cb resized, + MemoryRegion *mr, Error **errp) { return qemu_ram_alloc_internal(size, maxsz, resized, NULL, RAM_RESIZEABLE, mr, errp); @@ -2147,6 +2251,8 @@ static void reclaim_ramblock(RAMBlock *block) void qemu_ram_free(RAMBlock *block) { + g_autofree char *name = NULL; + if (!block) { return; } @@ -2157,6 +2263,8 @@ void qemu_ram_free(RAMBlock *block) } qemu_mutex_lock_ramlist(); + name = cpr_name(block->mr); + cpr_delete_fd(name, 0); QLIST_REMOVE_RCU(block, next); ram_list.mru_block = NULL; /* Write list before version */ diff --git a/system/trace-events b/system/trace-events index 5bbc3fbffa..be12ebfb41 100644 --- a/system/trace-events +++ b/system/trace-events @@ -33,6 +33,7 @@ address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, uint32_t find_ram_offset(uint64_t size, uint64_t offset) "size: 0x%" PRIx64 " @ 0x%" PRIx64 find_ram_offset_loop(uint64_t size, uint64_t candidate, uint64_t offset, uint64_t next, uint64_t mingap) "trying size: 0x%" PRIx64 " @ 0x%" PRIx64 ", offset: 0x%" PRIx64" next: 0x%" PRIx64 " mingap: 0x%" PRIx64 ram_block_discard_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d" +qemu_ram_alloc_shared(const char *name, size_t size, size_t max_size, int fd, void *host) "%s size %zu max_size %zu fd %d host %p" # cpus.c vm_stop_flush_all(int ret) "ret %d" diff --git a/system/vl.c b/system/vl.c index c567826718..db8e604eba 100644 --- a/system/vl.c +++ b/system/vl.c @@ -77,6 +77,7 @@ #include "hw/block/block.h" #include "hw/i386/x86.h" #include "hw/i386/pc.h" +#include "migration/cpr.h" #include "migration/misc.h" #include "migration/snapshot.h" #include "system/tpm.h" @@ -123,6 +124,7 @@ #include "qapi/qapi-visit-block-core.h" #include "qapi/qapi-visit-compat.h" #include "qapi/qapi-visit-machine.h" +#include "qapi/qapi-visit-migration.h" #include "qapi/qapi-visit-ui.h" #include "qapi/qapi-commands-block-core.h" #include "qapi/qapi-commands-migration.h" @@ -159,6 +161,8 @@ typedef struct DeviceOption { static const char *cpu_option; static const char *mem_path; static const char *incoming; +static const char *incoming_str[MIGRATION_CHANNEL_TYPE__MAX]; +static MigrationChannel *incoming_channels[MIGRATION_CHANNEL_TYPE__MAX]; static const char *loadvm; static const char *accelerators; static bool have_custom_ram_size; @@ -1813,6 +1817,30 @@ static void object_option_add_visitor(Visitor *v) QTAILQ_INSERT_TAIL(&object_opts, opt, next); } +static void incoming_option_parse(const char *str) +{ + MigrationChannelType type = MIGRATION_CHANNEL_TYPE_MAIN; + MigrationChannel *channel; + Visitor *v; + + if (!strcmp(str, "defer")) { + channel = NULL; + } else if (migrate_is_uri(str)) { + migrate_uri_parse(str, &channel, &error_fatal); + } else { + v = qobject_input_visitor_new_str(str, "channel-type", &error_fatal); + visit_type_MigrationChannel(v, NULL, &channel, &error_fatal); + visit_free(v); + type = channel->channel_type; + } + + /* New incoming spec replaces the previous */ + qapi_free_MigrationChannel(incoming_channels[type]); + incoming_channels[type] = channel; + incoming_str[type] = str; + incoming = incoming_str[MIGRATION_CHANNEL_TYPE_MAIN]; +} + static void object_option_parse(const char *str) { QemuOpts *opts; @@ -2738,8 +2766,11 @@ void qmp_x_exit_preconfig(Error **errp) if (incoming) { Error *local_err = NULL; if (strcmp(incoming, "defer") != 0) { - qmp_migrate_incoming(incoming, false, NULL, true, true, - &local_err); + g_autofree MigrationChannelList *channels = + g_new0(MigrationChannelList, 1); + + channels->value = incoming_channels[MIGRATION_CHANNEL_TYPE_MAIN]; + qmp_migrate_incoming(NULL, true, channels, true, true, &local_err); if (local_err) { error_reportf_err(local_err, "-incoming %s: ", incoming); exit(1); @@ -3458,7 +3489,7 @@ void qemu_init(int argc, char **argv) if (!incoming) { runstate_set(RUN_STATE_INMIGRATE); } - incoming = optarg; + incoming_option_parse(optarg); break; case QEMU_OPTION_only_migratable: only_migratable = 1; @@ -3676,6 +3707,12 @@ void qemu_init(int argc, char **argv) qemu_create_machine(machine_opts_dict); + /* + * Load incoming CPR state before any devices are created, because it + * contains file descriptors that are needed in device initialization code. + */ + cpr_state_load(incoming_channels[MIGRATION_CHANNEL_TYPE_CPR], &error_fatal); + suspend_mux_open(); qemu_disable_default_devices(); diff --git a/target/arm/cpu.c b/target/arm/cpu.c index dc0231233a..7a83b9ee34 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -572,9 +572,11 @@ static void arm_cpu_reset_hold(Object *obj, ResetType type) set_flush_inputs_to_zero(1, &env->vfp.standard_fp_status); set_default_nan_mode(1, &env->vfp.standard_fp_status); set_default_nan_mode(1, &env->vfp.standard_fp_status_f16); - arm_set_default_fp_behaviours(&env->vfp.fp_status); + arm_set_default_fp_behaviours(&env->vfp.fp_status_a32); + arm_set_default_fp_behaviours(&env->vfp.fp_status_a64); arm_set_default_fp_behaviours(&env->vfp.standard_fp_status); - arm_set_default_fp_behaviours(&env->vfp.fp_status_f16); + arm_set_default_fp_behaviours(&env->vfp.fp_status_f16_a32); + arm_set_default_fp_behaviours(&env->vfp.fp_status_f16_a64); arm_set_default_fp_behaviours(&env->vfp.standard_fp_status_f16); #ifndef CONFIG_USER_ONLY diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 9a6e8e589c..2213c27734 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -633,8 +633,10 @@ typedef struct CPUArchState { /* There are a number of distinct float control structures: * - * fp_status: is the "normal" fp status. - * fp_status_fp16: used for half-precision calculations + * fp_status_a32: is the "normal" fp status for AArch32 insns + * fp_status_a64: is the "normal" fp status for AArch64 insns + * fp_status_fp16_a32: used for AArch32 half-precision calculations + * fp_status_fp16_a64: used for AArch64 half-precision calculations * standard_fp_status : the ARM "Standard FPSCR Value" * standard_fp_status_fp16 : used for half-precision * calculations with the ARM "Standard FPSCR Value" @@ -658,8 +660,10 @@ typedef struct CPUArchState { * only thing which needs to read the exception flags being * an explicit FPSCR read. */ - float_status fp_status; - float_status fp_status_f16; + float_status fp_status_a32; + float_status fp_status_a64; + float_status fp_status_f16_a32; + float_status fp_status_f16_a64; float_status standard_fp_status; float_status standard_fp_status_f16; diff --git a/target/arm/helper.c b/target/arm/helper.c index 6399767851..40bdfc851a 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -6413,7 +6413,7 @@ static void arm_reset_sve_state(CPUARMState *env) memset(env->vfp.zregs, 0, sizeof(env->vfp.zregs)); /* Recall that FFR is stored as pregs[16]. */ memset(env->vfp.pregs, 0, sizeof(env->vfp.pregs)); - vfp_set_fpcr(env, 0x0800009f); + vfp_set_fpsr(env, 0x0800009f); } void aarch64_set_svcr(CPUARMState *env, uint64_t new, uint64_t mask) diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index 3b226daee7..05036089dd 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -439,15 +439,6 @@ uint32_t ADVSIMD_HELPER(name, h)(uint32_t a, uint32_t b, float_status *fpst) \ return float16_ ## name(a, b, fpst); \ } -ADVSIMD_HALFOP(add) -ADVSIMD_HALFOP(sub) -ADVSIMD_HALFOP(mul) -ADVSIMD_HALFOP(div) -ADVSIMD_HALFOP(min) -ADVSIMD_HALFOP(max) -ADVSIMD_HALFOP(minnum) -ADVSIMD_HALFOP(maxnum) - #define ADVSIMD_TWOHALFOP(name) \ uint32_t ADVSIMD_HELPER(name, 2h)(uint32_t two_a, uint32_t two_b, \ float_status *fpst) \ diff --git a/target/arm/tcg/helper-a64.h b/target/arm/tcg/helper-a64.h index 0c120bf388..bac12fbe55 100644 --- a/target/arm/tcg/helper-a64.h +++ b/target/arm/tcg/helper-a64.h @@ -47,14 +47,6 @@ DEF_HELPER_FLAGS_2(frecpx_f16, TCG_CALL_NO_RWG, f16, f16, fpst) DEF_HELPER_FLAGS_2(fcvtx_f64_to_f32, TCG_CALL_NO_RWG, f32, f64, fpst) DEF_HELPER_FLAGS_3(crc32_64, TCG_CALL_NO_RWG_SE, i64, i64, i64, i32) DEF_HELPER_FLAGS_3(crc32c_64, TCG_CALL_NO_RWG_SE, i64, i64, i64, i32) -DEF_HELPER_FLAGS_3(advsimd_maxh, TCG_CALL_NO_RWG, f16, f16, f16, fpst) -DEF_HELPER_FLAGS_3(advsimd_minh, TCG_CALL_NO_RWG, f16, f16, f16, fpst) -DEF_HELPER_FLAGS_3(advsimd_maxnumh, TCG_CALL_NO_RWG, f16, f16, f16, fpst) -DEF_HELPER_FLAGS_3(advsimd_minnumh, TCG_CALL_NO_RWG, f16, f16, f16, fpst) -DEF_HELPER_3(advsimd_addh, f16, f16, f16, fpst) -DEF_HELPER_3(advsimd_subh, f16, f16, f16, fpst) -DEF_HELPER_3(advsimd_mulh, f16, f16, f16, fpst) -DEF_HELPER_3(advsimd_divh, f16, f16, f16, fpst) DEF_HELPER_3(advsimd_ceq_f16, i32, f16, f16, fpst) DEF_HELPER_3(advsimd_cge_f16, i32, f16, f16, fpst) DEF_HELPER_3(advsimd_cgt_f16, i32, f16, f16, fpst) diff --git a/target/arm/tcg/sme_helper.c b/target/arm/tcg/sme_helper.c index a0e6b4a41e..727c085f37 100644 --- a/target/arm/tcg/sme_helper.c +++ b/target/arm/tcg/sme_helper.c @@ -1038,13 +1038,13 @@ void HELPER(sme_fmopa_h)(void *vza, void *vzn, void *vzm, void *vpn, float_status fpst_odd, fpst_std, fpst_f16; /* - * Make copies of fp_status and fp_status_f16, because this operation + * Make copies of the fp status fields we use, because this operation * does not update the cumulative fp exception status. It also * produces default NaNs. We also need a second copy of fp_status with * round-to-odd -- see above. */ - fpst_f16 = env->vfp.fp_status_f16; - fpst_std = env->vfp.fp_status; + fpst_f16 = env->vfp.fp_status_f16_a64; + fpst_std = env->vfp.fp_status_a64; set_default_nan_mode(true, &fpst_std); set_default_nan_mode(true, &fpst_f16); fpst_odd = fpst_std; diff --git a/target/arm/tcg/sve_helper.c b/target/arm/tcg/sve_helper.c index d0865dece3..9837c5bc7a 100644 --- a/target/arm/tcg/sve_helper.c +++ b/target/arm/tcg/sve_helper.c @@ -4658,7 +4658,7 @@ static int16_t do_float16_logb_as_int(float16 a, float_status *s) return -15 - clz32(frac); } /* flush to zero */ - float_raise(float_flag_input_denormal, s); + float_raise(float_flag_input_denormal_flushed, s); } } else if (unlikely(exp == 0x1f)) { if (frac == 0) { @@ -4686,7 +4686,7 @@ static int32_t do_float32_logb_as_int(float32 a, float_status *s) return -127 - clz32(frac); } /* flush to zero */ - float_raise(float_flag_input_denormal, s); + float_raise(float_flag_input_denormal_flushed, s); } } else if (unlikely(exp == 0xff)) { if (frac == 0) { @@ -4714,7 +4714,7 @@ static int64_t do_float64_logb_as_int(float64 a, float_status *s) return -1023 - clz64(frac); } /* flush to zero */ - float_raise(float_flag_input_denormal, s); + float_raise(float_flag_input_denormal_flushed, s); } } else if (unlikely(exp == 0x7ff)) { if (frac == 0) { diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index bd814849c1..0b76a2cdb7 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -726,7 +726,7 @@ static void gen_gvec_op3_fpst(DisasContext *s, bool is_q, int rd, int rn, int rm, bool is_fp16, int data, gen_helper_gvec_3_ptr *fn) { - TCGv_ptr fpst = fpstatus_ptr(is_fp16 ? FPST_FPCR_F16 : FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(is_fp16 ? FPST_A64_F16 : FPST_A64); tcg_gen_gvec_3_ptr(vec_full_reg_offset(s, rd), vec_full_reg_offset(s, rn), vec_full_reg_offset(s, rm), fpst, @@ -768,7 +768,7 @@ static void gen_gvec_op4_fpst(DisasContext *s, bool is_q, int rd, int rn, int rm, int ra, bool is_fp16, int data, gen_helper_gvec_4_ptr *fn) { - TCGv_ptr fpst = fpstatus_ptr(is_fp16 ? FPST_FPCR_F16 : FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(is_fp16 ? FPST_A64_F16 : FPST_A64); tcg_gen_gvec_4_ptr(vec_full_reg_offset(s, rd), vec_full_reg_offset(s, rn), vec_full_reg_offset(s, rm), @@ -5043,7 +5043,7 @@ static bool do_fp3_scalar(DisasContext *s, arg_rrr_e *a, const FPScalar *f) if (fp_access_check(s)) { TCGv_i64 t0 = read_fp_dreg(s, a->rn); TCGv_i64 t1 = read_fp_dreg(s, a->rm); - f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_dreg(s, a->rd, t0); } break; @@ -5051,7 +5051,7 @@ static bool do_fp3_scalar(DisasContext *s, arg_rrr_e *a, const FPScalar *f) if (fp_access_check(s)) { TCGv_i32 t0 = read_fp_sreg(s, a->rn); TCGv_i32 t1 = read_fp_sreg(s, a->rm); - f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_sreg(s, a->rd, t0); } break; @@ -5062,7 +5062,7 @@ static bool do_fp3_scalar(DisasContext *s, arg_rrr_e *a, const FPScalar *f) if (fp_access_check(s)) { TCGv_i32 t0 = read_fp_hreg(s, a->rn); TCGv_i32 t1 = read_fp_hreg(s, a->rm); - f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_FPCR_F16)); + f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_A64_F16)); write_fp_sreg(s, a->rd, t0); } break; @@ -5101,28 +5101,28 @@ static const FPScalar f_scalar_fmul = { TRANS(FMUL_s, do_fp3_scalar, a, &f_scalar_fmul) static const FPScalar f_scalar_fmax = { - gen_helper_advsimd_maxh, + gen_helper_vfp_maxh, gen_helper_vfp_maxs, gen_helper_vfp_maxd, }; TRANS(FMAX_s, do_fp3_scalar, a, &f_scalar_fmax) static const FPScalar f_scalar_fmin = { - gen_helper_advsimd_minh, + gen_helper_vfp_minh, gen_helper_vfp_mins, gen_helper_vfp_mind, }; TRANS(FMIN_s, do_fp3_scalar, a, &f_scalar_fmin) static const FPScalar f_scalar_fmaxnm = { - gen_helper_advsimd_maxnumh, + gen_helper_vfp_maxnumh, gen_helper_vfp_maxnums, gen_helper_vfp_maxnumd, }; TRANS(FMAXNM_s, do_fp3_scalar, a, &f_scalar_fmaxnm) static const FPScalar f_scalar_fminnm = { - gen_helper_advsimd_minnumh, + gen_helper_vfp_minnumh, gen_helper_vfp_minnums, gen_helper_vfp_minnumd, }; @@ -5243,9 +5243,9 @@ static bool do_fcmp0_s(DisasContext *s, arg_rr_e *a, TCGv_i64 t0 = read_fp_dreg(s, a->rn); TCGv_i64 t1 = tcg_constant_i64(0); if (swap) { - f->gen_d(t0, t1, t0, fpstatus_ptr(FPST_FPCR)); + f->gen_d(t0, t1, t0, fpstatus_ptr(FPST_A64)); } else { - f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_A64)); } write_fp_dreg(s, a->rd, t0); } @@ -5255,9 +5255,9 @@ static bool do_fcmp0_s(DisasContext *s, arg_rr_e *a, TCGv_i32 t0 = read_fp_sreg(s, a->rn); TCGv_i32 t1 = tcg_constant_i32(0); if (swap) { - f->gen_s(t0, t1, t0, fpstatus_ptr(FPST_FPCR)); + f->gen_s(t0, t1, t0, fpstatus_ptr(FPST_A64)); } else { - f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_A64)); } write_fp_sreg(s, a->rd, t0); } @@ -5270,9 +5270,9 @@ static bool do_fcmp0_s(DisasContext *s, arg_rr_e *a, TCGv_i32 t0 = read_fp_hreg(s, a->rn); TCGv_i32 t1 = tcg_constant_i32(0); if (swap) { - f->gen_h(t0, t1, t0, fpstatus_ptr(FPST_FPCR_F16)); + f->gen_h(t0, t1, t0, fpstatus_ptr(FPST_A64_F16)); } else { - f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_FPCR_F16)); + f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_A64_F16)); } write_fp_sreg(s, a->rd, t0); } @@ -6207,7 +6207,7 @@ static bool do_fp3_scalar_idx(DisasContext *s, arg_rrx_e *a, const FPScalar *f) TCGv_i64 t1 = tcg_temp_new_i64(); read_vec_element(s, t1, a->rm, a->idx, MO_64); - f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_dreg(s, a->rd, t0); } break; @@ -6217,7 +6217,7 @@ static bool do_fp3_scalar_idx(DisasContext *s, arg_rrx_e *a, const FPScalar *f) TCGv_i32 t1 = tcg_temp_new_i32(); read_vec_element_i32(s, t1, a->rm, a->idx, MO_32); - f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_sreg(s, a->rd, t0); } break; @@ -6230,7 +6230,7 @@ static bool do_fp3_scalar_idx(DisasContext *s, arg_rrx_e *a, const FPScalar *f) TCGv_i32 t1 = tcg_temp_new_i32(); read_vec_element_i32(s, t1, a->rm, a->idx, MO_16); - f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_FPCR_F16)); + f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_A64_F16)); write_fp_sreg(s, a->rd, t0); } break; @@ -6256,7 +6256,7 @@ static bool do_fmla_scalar_idx(DisasContext *s, arg_rrx_e *a, bool neg) if (neg) { gen_vfp_negd(t1, t1); } - gen_helper_vfp_muladdd(t0, t1, t2, t0, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_muladdd(t0, t1, t2, t0, fpstatus_ptr(FPST_A64)); write_fp_dreg(s, a->rd, t0); } break; @@ -6270,7 +6270,7 @@ static bool do_fmla_scalar_idx(DisasContext *s, arg_rrx_e *a, bool neg) if (neg) { gen_vfp_negs(t1, t1); } - gen_helper_vfp_muladds(t0, t1, t2, t0, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_muladds(t0, t1, t2, t0, fpstatus_ptr(FPST_A64)); write_fp_sreg(s, a->rd, t0); } break; @@ -6288,7 +6288,7 @@ static bool do_fmla_scalar_idx(DisasContext *s, arg_rrx_e *a, bool neg) gen_vfp_negh(t1, t1); } gen_helper_advsimd_muladdh(t0, t1, t2, t0, - fpstatus_ptr(FPST_FPCR_F16)); + fpstatus_ptr(FPST_A64_F16)); write_fp_sreg(s, a->rd, t0); } break; @@ -6601,7 +6601,7 @@ static bool do_fp3_scalar_pair(DisasContext *s, arg_rr_e *a, const FPScalar *f) read_vec_element(s, t0, a->rn, 0, MO_64); read_vec_element(s, t1, a->rn, 1, MO_64); - f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_d(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_dreg(s, a->rd, t0); } break; @@ -6612,7 +6612,7 @@ static bool do_fp3_scalar_pair(DisasContext *s, arg_rr_e *a, const FPScalar *f) read_vec_element_i32(s, t0, a->rn, 0, MO_32); read_vec_element_i32(s, t1, a->rn, 1, MO_32); - f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_FPCR)); + f->gen_s(t0, t0, t1, fpstatus_ptr(FPST_A64)); write_fp_sreg(s, a->rd, t0); } break; @@ -6626,7 +6626,7 @@ static bool do_fp3_scalar_pair(DisasContext *s, arg_rr_e *a, const FPScalar *f) read_vec_element_i32(s, t0, a->rn, 0, MO_16); read_vec_element_i32(s, t1, a->rn, 1, MO_16); - f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_FPCR_F16)); + f->gen_h(t0, t0, t1, fpstatus_ptr(FPST_A64_F16)); write_fp_sreg(s, a->rd, t0); } break; @@ -6762,7 +6762,7 @@ static bool do_fmadd(DisasContext *s, arg_rrrr_e *a, bool neg_a, bool neg_n) if (neg_n) { gen_vfp_negd(tn, tn); } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_muladdd(ta, tn, tm, ta, fpst); write_fp_dreg(s, a->rd, ta); } @@ -6780,7 +6780,7 @@ static bool do_fmadd(DisasContext *s, arg_rrrr_e *a, bool neg_a, bool neg_n) if (neg_n) { gen_vfp_negs(tn, tn); } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_muladds(ta, tn, tm, ta, fpst); write_fp_sreg(s, a->rd, ta); } @@ -6801,7 +6801,7 @@ static bool do_fmadd(DisasContext *s, arg_rrrr_e *a, bool neg_a, bool neg_n) if (neg_n) { gen_vfp_negh(tn, tn); } - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A64_F16); gen_helper_advsimd_muladdh(ta, tn, tm, ta, fpst); write_fp_sreg(s, a->rd, ta); } @@ -6895,17 +6895,17 @@ static bool do_fp_reduction(DisasContext *s, arg_qrr_e *a, if (fp_access_check(s)) { MemOp esz = a->esz; int elts = (a->q ? 16 : 8) >> esz; - TCGv_ptr fpst = fpstatus_ptr(esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(esz == MO_16 ? FPST_A64_F16 : FPST_A64); TCGv_i32 res = do_reduction_op(s, a->rn, esz, 0, elts, fpst, fn); write_fp_sreg(s, a->rd, res); } return true; } -TRANS_FEAT(FMAXNMV_h, aa64_fp16, do_fp_reduction, a, gen_helper_advsimd_maxnumh) -TRANS_FEAT(FMINNMV_h, aa64_fp16, do_fp_reduction, a, gen_helper_advsimd_minnumh) -TRANS_FEAT(FMAXV_h, aa64_fp16, do_fp_reduction, a, gen_helper_advsimd_maxh) -TRANS_FEAT(FMINV_h, aa64_fp16, do_fp_reduction, a, gen_helper_advsimd_minh) +TRANS_FEAT(FMAXNMV_h, aa64_fp16, do_fp_reduction, a, gen_helper_vfp_maxnumh) +TRANS_FEAT(FMINNMV_h, aa64_fp16, do_fp_reduction, a, gen_helper_vfp_minnumh) +TRANS_FEAT(FMAXV_h, aa64_fp16, do_fp_reduction, a, gen_helper_vfp_maxh) +TRANS_FEAT(FMINV_h, aa64_fp16, do_fp_reduction, a, gen_helper_vfp_minh) TRANS(FMAXNMV_s, do_fp_reduction, a, gen_helper_vfp_maxnums) TRANS(FMINNMV_s, do_fp_reduction, a, gen_helper_vfp_minnums) @@ -6939,7 +6939,7 @@ static void handle_fp_compare(DisasContext *s, int size, bool cmp_with_zero, bool signal_all_nans) { TCGv_i64 tcg_flags = tcg_temp_new_i64(); - TCGv_ptr fpst = fpstatus_ptr(size == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(size == MO_16 ? FPST_A64_F16 : FPST_A64); if (size == MO_64) { TCGv_i64 tcg_vn, tcg_vm; @@ -8407,7 +8407,7 @@ static bool do_fp1_scalar(DisasContext *s, arg_rr_e *a, return check == 0; } - fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); if (rmode >= 0) { tcg_rmode = gen_set_rmode(rmode, fpst); } @@ -8513,7 +8513,7 @@ static bool trans_FCVT_s_ds(DisasContext *s, arg_rr *a) if (fp_access_check(s)) { TCGv_i32 tcg_rn = read_fp_sreg(s, a->rn); TCGv_i64 tcg_rd = tcg_temp_new_i64(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_fcvtds(tcg_rd, tcg_rn, fpst); write_fp_dreg(s, a->rd, tcg_rd); @@ -8526,7 +8526,7 @@ static bool trans_FCVT_s_hs(DisasContext *s, arg_rr *a) if (fp_access_check(s)) { TCGv_i32 tmp = read_fp_sreg(s, a->rn); TCGv_i32 ahp = get_ahp_flag(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_fcvt_f32_to_f16(tmp, tmp, fpst, ahp); /* write_fp_sreg is OK here because top half of result is zero */ @@ -8540,7 +8540,7 @@ static bool trans_FCVT_s_sd(DisasContext *s, arg_rr *a) if (fp_access_check(s)) { TCGv_i64 tcg_rn = read_fp_dreg(s, a->rn); TCGv_i32 tcg_rd = tcg_temp_new_i32(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_fcvtsd(tcg_rd, tcg_rn, fpst); write_fp_sreg(s, a->rd, tcg_rd); @@ -8554,7 +8554,7 @@ static bool trans_FCVT_s_hd(DisasContext *s, arg_rr *a) TCGv_i64 tcg_rn = read_fp_dreg(s, a->rn); TCGv_i32 tcg_rd = tcg_temp_new_i32(); TCGv_i32 ahp = get_ahp_flag(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_fcvt_f64_to_f16(tcg_rd, tcg_rn, fpst, ahp); /* write_fp_sreg is OK here because top half of tcg_rd is zero */ @@ -8568,7 +8568,7 @@ static bool trans_FCVT_s_sh(DisasContext *s, arg_rr *a) if (fp_access_check(s)) { TCGv_i32 tcg_rn = read_fp_hreg(s, a->rn); TCGv_i32 tcg_rd = tcg_temp_new_i32(); - TCGv_ptr tcg_fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr tcg_fpst = fpstatus_ptr(FPST_A64_F16); TCGv_i32 tcg_ahp = get_ahp_flag(); gen_helper_vfp_fcvt_f16_to_f32(tcg_rd, tcg_rn, tcg_fpst, tcg_ahp); @@ -8582,7 +8582,7 @@ static bool trans_FCVT_s_dh(DisasContext *s, arg_rr *a) if (fp_access_check(s)) { TCGv_i32 tcg_rn = read_fp_hreg(s, a->rn); TCGv_i64 tcg_rd = tcg_temp_new_i64(); - TCGv_ptr tcg_fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr tcg_fpst = fpstatus_ptr(FPST_A64_F16); TCGv_i32 tcg_ahp = get_ahp_flag(); gen_helper_vfp_fcvt_f16_to_f64(tcg_rd, tcg_rn, tcg_fpst, tcg_ahp); @@ -8598,7 +8598,7 @@ static bool do_cvtf_scalar(DisasContext *s, MemOp esz, int rd, int shift, TCGv_i32 tcg_shift, tcg_single; TCGv_i64 tcg_double; - tcg_fpstatus = fpstatus_ptr(esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + tcg_fpstatus = fpstatus_ptr(esz == MO_16 ? FPST_A64_F16 : FPST_A64); tcg_shift = tcg_constant_i32(shift); switch (esz) { @@ -8693,7 +8693,7 @@ static void do_fcvt_scalar(DisasContext *s, MemOp out, MemOp esz, TCGv_ptr tcg_fpstatus; TCGv_i32 tcg_shift, tcg_rmode, tcg_single; - tcg_fpstatus = fpstatus_ptr(esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + tcg_fpstatus = fpstatus_ptr(esz == MO_16 ? FPST_A64_F16 : FPST_A64); tcg_shift = tcg_constant_i32(shift); tcg_rmode = gen_set_rmode(rmode, tcg_fpstatus); @@ -8857,7 +8857,7 @@ static bool trans_FJCVTZS(DisasContext *s, arg_FJCVTZS *a) } if (fp_access_check(s)) { TCGv_i64 t = read_fp_dreg(s, a->rn); - TCGv_ptr fpstatus = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpstatus = fpstatus_ptr(FPST_A64); gen_helper_fjcvtzs(t, t, fpstatus); @@ -9115,7 +9115,7 @@ static void gen_fcvtxn_sd(TCGv_i64 d, TCGv_i64 n) * with von Neumann rounding (round to odd) */ TCGv_i32 tmp = tcg_temp_new_i32(); - gen_helper_fcvtx_f64_to_f32(tmp, n, fpstatus_ptr(FPST_FPCR)); + gen_helper_fcvtx_f64_to_f32(tmp, n, fpstatus_ptr(FPST_A64)); tcg_gen_extu_i32_i64(d, tmp); } @@ -9208,7 +9208,7 @@ static void gen_fcvtn_hs(TCGv_i64 d, TCGv_i64 n) { TCGv_i32 tcg_lo = tcg_temp_new_i32(); TCGv_i32 tcg_hi = tcg_temp_new_i32(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); TCGv_i32 ahp = get_ahp_flag(); tcg_gen_extr_i64_i32(tcg_lo, tcg_hi, n); @@ -9221,7 +9221,7 @@ static void gen_fcvtn_hs(TCGv_i64 d, TCGv_i64 n) static void gen_fcvtn_sd(TCGv_i64 d, TCGv_i64 n) { TCGv_i32 tmp = tcg_temp_new_i32(); - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); gen_helper_vfp_fcvtsd(tmp, n, fpst); tcg_gen_extu_i32_i64(d, tmp); @@ -9237,7 +9237,7 @@ TRANS(FCVTXN_v, do_2misc_narrow_vector, a, f_scalar_fcvtxn) static void gen_bfcvtn_hs(TCGv_i64 d, TCGv_i64 n) { - TCGv_ptr fpst = fpstatus_ptr(FPST_FPCR); + TCGv_ptr fpst = fpstatus_ptr(FPST_A64); TCGv_i32 tmp = tcg_temp_new_i32(); gen_helper_bfcvt_pair(tmp, n, fpst); tcg_gen_extu_i32_i64(d, tmp); @@ -9312,7 +9312,7 @@ static bool do_fp1_vector(DisasContext *s, arg_qrr_e *a, return check == 0; } - fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); if (rmode >= 0) { tcg_rmode = gen_set_rmode(rmode, fpst); } @@ -9372,7 +9372,7 @@ static bool do_gvec_op2_fpst(DisasContext *s, MemOp esz, bool is_q, return check == 0; } - fpst = fpstatus_ptr(esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + fpst = fpstatus_ptr(esz == MO_16 ? FPST_A64_F16 : FPST_A64); tcg_gen_gvec_2_ptr(vec_full_reg_offset(s, rd), vec_full_reg_offset(s, rn), fpst, is_q ? 16 : 8, vec_full_reg_size(s), @@ -9511,13 +9511,14 @@ static bool trans_FCVTL_v(DisasContext *s, arg_qrr_e *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); if (a->esz == MO_64) { /* 32 -> 64 bit fp conversion */ TCGv_i64 tcg_res[2]; TCGv_i32 tcg_op = tcg_temp_new_i32(); int srcelt = a->q ? 2 : 0; + fpst = fpstatus_ptr(FPST_A64); + for (pass = 0; pass < 2; pass++) { tcg_res[pass] = tcg_temp_new_i64(); read_vec_element_i32(s, tcg_op, a->rn, srcelt + pass, MO_32); @@ -9532,6 +9533,8 @@ static bool trans_FCVTL_v(DisasContext *s, arg_qrr_e *a) TCGv_i32 tcg_res[4]; TCGv_i32 ahp = get_ahp_flag(); + fpst = fpstatus_ptr(FPST_A64_F16); + for (pass = 0; pass < 4; pass++) { tcg_res[pass] = tcg_temp_new_i32(); read_vec_element_i32(s, tcg_res[pass], a->rn, srcelt + pass, MO_16); diff --git a/target/arm/tcg/translate-sme.c b/target/arm/tcg/translate-sme.c index 01ece57016..fcbb350016 100644 --- a/target/arm/tcg/translate-sme.c +++ b/target/arm/tcg/translate-sme.c @@ -358,9 +358,9 @@ static bool do_outprod_env(DisasContext *s, arg_op *a, MemOp esz, TRANS_FEAT(FMOPA_h, aa64_sme, do_outprod_env, a, MO_32, gen_helper_sme_fmopa_h) TRANS_FEAT(FMOPA_s, aa64_sme, do_outprod_fpst, a, - MO_32, FPST_FPCR, gen_helper_sme_fmopa_s) + MO_32, FPST_A64, gen_helper_sme_fmopa_s) TRANS_FEAT(FMOPA_d, aa64_sme_f64f64, do_outprod_fpst, a, - MO_64, FPST_FPCR, gen_helper_sme_fmopa_d) + MO_64, FPST_A64, gen_helper_sme_fmopa_d) TRANS_FEAT(BFMOPA, aa64_sme, do_outprod_env, a, MO_32, gen_helper_sme_bfmopa) diff --git a/target/arm/tcg/translate-sve.c b/target/arm/tcg/translate-sve.c index e303196592..e1788330aa 100644 --- a/target/arm/tcg/translate-sve.c +++ b/target/arm/tcg/translate-sve.c @@ -141,7 +141,7 @@ static bool gen_gvec_fpst_arg_zz(DisasContext *s, gen_helper_gvec_2_ptr *fn, arg_rr_esz *a, int data) { return gen_gvec_fpst_zz(s, fn, a->rd, a->rn, data, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); } /* Invoke an out-of-line helper on 3 Zregs. */ @@ -191,7 +191,7 @@ static bool gen_gvec_fpst_arg_zzz(DisasContext *s, gen_helper_gvec_3_ptr *fn, arg_rrr_esz *a, int data) { return gen_gvec_fpst_zzz(s, fn, a->rd, a->rn, a->rm, data, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); } /* Invoke an out-of-line helper on 4 Zregs. */ @@ -397,7 +397,7 @@ static bool gen_gvec_fpst_arg_zpzz(DisasContext *s, gen_helper_gvec_4_ptr *fn, arg_rprr_esz *a) { return gen_gvec_fpst_zzzp(s, fn, a->rd, a->rn, a->rm, a->pg, 0, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); } /* Invoke a vector expander on two Zregs and an immediate. */ @@ -3517,7 +3517,7 @@ static bool do_FMLA_zzxz(DisasContext *s, arg_rrxr_esz *a, bool sub) }; return gen_gvec_fpst_zzzz(s, fns[a->esz], a->rd, a->rn, a->rm, a->ra, (a->index << 1) | sub, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); } TRANS_FEAT(FMLA_zzxz, aa64_sve, do_FMLA_zzxz, a, false) @@ -3533,7 +3533,7 @@ static gen_helper_gvec_3_ptr * const fmul_idx_fns[4] = { }; TRANS_FEAT(FMUL_zzx, aa64_sve, gen_gvec_fpst_zzz, fmul_idx_fns[a->esz], a->rd, a->rn, a->rm, a->index, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) /* *** SVE Floating Point Fast Reduction Group @@ -3566,7 +3566,7 @@ static bool do_reduce(DisasContext *s, arg_rpr_esz *a, tcg_gen_addi_ptr(t_zn, tcg_env, vec_full_reg_offset(s, a->rn)); tcg_gen_addi_ptr(t_pg, tcg_env, pred_full_reg_offset(s, a->pg)); - status = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + status = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); fn(temp, t_zn, t_pg, status, t_desc); @@ -3618,7 +3618,7 @@ static bool do_ppz_fp(DisasContext *s, arg_rpr_esz *a, if (sve_access_check(s)) { unsigned vsz = vec_full_reg_size(s); TCGv_ptr status = - fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); tcg_gen_gvec_3_ptr(pred_full_reg_offset(s, a->rd), vec_full_reg_offset(s, a->rn), @@ -3654,7 +3654,7 @@ static gen_helper_gvec_3_ptr * const ftmad_fns[4] = { }; TRANS_FEAT_NONSTREAMING(FTMAD, aa64_sve, gen_gvec_fpst_zzz, ftmad_fns[a->esz], a->rd, a->rn, a->rm, a->imm, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) /* *** SVE Floating Point Accumulating Reduction Group @@ -3687,7 +3687,7 @@ static bool trans_FADDA(DisasContext *s, arg_rprr_esz *a) t_pg = tcg_temp_new_ptr(); tcg_gen_addi_ptr(t_rm, tcg_env, vec_full_reg_offset(s, a->rm)); tcg_gen_addi_ptr(t_pg, tcg_env, pred_full_reg_offset(s, a->pg)); - t_fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + t_fpst = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); t_desc = tcg_constant_i32(simd_desc(vsz, vsz, 0)); fns[a->esz - 1](t_val, t_val, t_rm, t_pg, t_fpst, t_desc); @@ -3762,7 +3762,7 @@ static void do_fp_scalar(DisasContext *s, int zd, int zn, int pg, bool is_fp16, tcg_gen_addi_ptr(t_zn, tcg_env, vec_full_reg_offset(s, zn)); tcg_gen_addi_ptr(t_pg, tcg_env, pred_full_reg_offset(s, pg)); - status = fpstatus_ptr(is_fp16 ? FPST_FPCR_F16 : FPST_FPCR); + status = fpstatus_ptr(is_fp16 ? FPST_A64_F16 : FPST_A64); desc = tcg_constant_i32(simd_desc(vsz, vsz, 0)); fn(t_zd, t_zn, t_pg, scalar, status, desc); } @@ -3814,7 +3814,7 @@ static bool do_fp_cmp(DisasContext *s, arg_rprr_esz *a, } if (sve_access_check(s)) { unsigned vsz = vec_full_reg_size(s); - TCGv_ptr status = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + TCGv_ptr status = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); tcg_gen_gvec_4_ptr(pred_full_reg_offset(s, a->rd), vec_full_reg_offset(s, a->rn), vec_full_reg_offset(s, a->rm), @@ -3847,7 +3847,7 @@ static gen_helper_gvec_4_ptr * const fcadd_fns[] = { }; TRANS_FEAT(FCADD, aa64_sve, gen_gvec_fpst_zzzp, fcadd_fns[a->esz], a->rd, a->rn, a->rm, a->pg, a->rot, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) #define DO_FMLA(NAME, name) \ static gen_helper_gvec_5_ptr * const name##_fns[4] = { \ @@ -3856,7 +3856,7 @@ TRANS_FEAT(FCADD, aa64_sve, gen_gvec_fpst_zzzp, fcadd_fns[a->esz], }; \ TRANS_FEAT(NAME, aa64_sve, gen_gvec_fpst_zzzzp, name##_fns[a->esz], \ a->rd, a->rn, a->rm, a->ra, a->pg, 0, \ - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) DO_FMLA(FMLA_zpzzz, fmla_zpzzz) DO_FMLA(FMLS_zpzzz, fmls_zpzzz) @@ -3871,66 +3871,66 @@ static gen_helper_gvec_5_ptr * const fcmla_fns[4] = { }; TRANS_FEAT(FCMLA_zpzzz, aa64_sve, gen_gvec_fpst_zzzzp, fcmla_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->pg, a->rot, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static gen_helper_gvec_4_ptr * const fcmla_idx_fns[4] = { NULL, gen_helper_gvec_fcmlah_idx, gen_helper_gvec_fcmlas_idx, NULL }; TRANS_FEAT(FCMLA_zzxz, aa64_sve, gen_gvec_fpst_zzzz, fcmla_idx_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->index * 4 + a->rot, - a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) /* *** SVE Floating Point Unary Operations Predicated Group */ TRANS_FEAT(FCVT_sh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_sh, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_sh, a, 0, FPST_A64) TRANS_FEAT(FCVT_hs, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_hs, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_hs, a, 0, FPST_A64_F16) TRANS_FEAT(BFCVT, aa64_sve_bf16, gen_gvec_fpst_arg_zpz, - gen_helper_sve_bfcvt, a, 0, FPST_FPCR) + gen_helper_sve_bfcvt, a, 0, FPST_A64) TRANS_FEAT(FCVT_dh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_dh, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_dh, a, 0, FPST_A64) TRANS_FEAT(FCVT_hd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_hd, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_hd, a, 0, FPST_A64_F16) TRANS_FEAT(FCVT_ds, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_ds, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_ds, a, 0, FPST_A64) TRANS_FEAT(FCVT_sd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvt_sd, a, 0, FPST_FPCR) + gen_helper_sve_fcvt_sd, a, 0, FPST_A64) TRANS_FEAT(FCVTZS_hh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_hh, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzs_hh, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZU_hh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_hh, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzu_hh, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZS_hs, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_hs, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzs_hs, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZU_hs, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_hs, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzu_hs, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZS_hd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_hd, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzs_hd, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZU_hd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_hd, a, 0, FPST_FPCR_F16) + gen_helper_sve_fcvtzu_hd, a, 0, FPST_A64_F16) TRANS_FEAT(FCVTZS_ss, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_ss, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzs_ss, a, 0, FPST_A64) TRANS_FEAT(FCVTZU_ss, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_ss, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzu_ss, a, 0, FPST_A64) TRANS_FEAT(FCVTZS_sd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_sd, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzs_sd, a, 0, FPST_A64) TRANS_FEAT(FCVTZU_sd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_sd, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzu_sd, a, 0, FPST_A64) TRANS_FEAT(FCVTZS_ds, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_ds, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzs_ds, a, 0, FPST_A64) TRANS_FEAT(FCVTZU_ds, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_ds, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzu_ds, a, 0, FPST_A64) TRANS_FEAT(FCVTZS_dd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzs_dd, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzs_dd, a, 0, FPST_A64) TRANS_FEAT(FCVTZU_dd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_fcvtzu_dd, a, 0, FPST_FPCR) + gen_helper_sve_fcvtzu_dd, a, 0, FPST_A64) static gen_helper_gvec_3_ptr * const frint_fns[] = { NULL, @@ -3939,7 +3939,7 @@ static gen_helper_gvec_3_ptr * const frint_fns[] = { gen_helper_sve_frint_d }; TRANS_FEAT(FRINTI, aa64_sve, gen_gvec_fpst_arg_zpz, frint_fns[a->esz], - a, 0, a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static gen_helper_gvec_3_ptr * const frintx_fns[] = { NULL, @@ -3948,7 +3948,7 @@ static gen_helper_gvec_3_ptr * const frintx_fns[] = { gen_helper_sve_frintx_d }; TRANS_FEAT(FRINTX, aa64_sve, gen_gvec_fpst_arg_zpz, frintx_fns[a->esz], - a, 0, a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); static bool do_frint_mode(DisasContext *s, arg_rpr_esz *a, ARMFPRounding mode, gen_helper_gvec_3_ptr *fn) @@ -3965,7 +3965,7 @@ static bool do_frint_mode(DisasContext *s, arg_rpr_esz *a, } vsz = vec_full_reg_size(s); - status = fpstatus_ptr(a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR); + status = fpstatus_ptr(a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); tmode = gen_set_rmode(mode, status); tcg_gen_gvec_3_ptr(vec_full_reg_offset(s, a->rd), @@ -3993,48 +3993,48 @@ static gen_helper_gvec_3_ptr * const frecpx_fns[] = { gen_helper_sve_frecpx_s, gen_helper_sve_frecpx_d, }; TRANS_FEAT(FRECPX, aa64_sve, gen_gvec_fpst_arg_zpz, frecpx_fns[a->esz], - a, 0, a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static gen_helper_gvec_3_ptr * const fsqrt_fns[] = { NULL, gen_helper_sve_fsqrt_h, gen_helper_sve_fsqrt_s, gen_helper_sve_fsqrt_d, }; TRANS_FEAT(FSQRT, aa64_sve, gen_gvec_fpst_arg_zpz, fsqrt_fns[a->esz], - a, 0, a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) TRANS_FEAT(SCVTF_hh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_hh, a, 0, FPST_FPCR_F16) + gen_helper_sve_scvt_hh, a, 0, FPST_A64_F16) TRANS_FEAT(SCVTF_sh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_sh, a, 0, FPST_FPCR_F16) + gen_helper_sve_scvt_sh, a, 0, FPST_A64_F16) TRANS_FEAT(SCVTF_dh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_dh, a, 0, FPST_FPCR_F16) + gen_helper_sve_scvt_dh, a, 0, FPST_A64_F16) TRANS_FEAT(SCVTF_ss, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_ss, a, 0, FPST_FPCR) + gen_helper_sve_scvt_ss, a, 0, FPST_A64) TRANS_FEAT(SCVTF_ds, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_ds, a, 0, FPST_FPCR) + gen_helper_sve_scvt_ds, a, 0, FPST_A64) TRANS_FEAT(SCVTF_sd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_sd, a, 0, FPST_FPCR) + gen_helper_sve_scvt_sd, a, 0, FPST_A64) TRANS_FEAT(SCVTF_dd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_scvt_dd, a, 0, FPST_FPCR) + gen_helper_sve_scvt_dd, a, 0, FPST_A64) TRANS_FEAT(UCVTF_hh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_hh, a, 0, FPST_FPCR_F16) + gen_helper_sve_ucvt_hh, a, 0, FPST_A64_F16) TRANS_FEAT(UCVTF_sh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_sh, a, 0, FPST_FPCR_F16) + gen_helper_sve_ucvt_sh, a, 0, FPST_A64_F16) TRANS_FEAT(UCVTF_dh, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_dh, a, 0, FPST_FPCR_F16) + gen_helper_sve_ucvt_dh, a, 0, FPST_A64_F16) TRANS_FEAT(UCVTF_ss, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_ss, a, 0, FPST_FPCR) + gen_helper_sve_ucvt_ss, a, 0, FPST_A64) TRANS_FEAT(UCVTF_ds, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_ds, a, 0, FPST_FPCR) + gen_helper_sve_ucvt_ds, a, 0, FPST_A64) TRANS_FEAT(UCVTF_sd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_sd, a, 0, FPST_FPCR) + gen_helper_sve_ucvt_sd, a, 0, FPST_A64) TRANS_FEAT(UCVTF_dd, aa64_sve, gen_gvec_fpst_arg_zpz, - gen_helper_sve_ucvt_dd, a, 0, FPST_FPCR) + gen_helper_sve_ucvt_dd, a, 0, FPST_A64) /* *** SVE Memory - 32-bit Gather and Unsized Contiguous Group @@ -6916,10 +6916,10 @@ DO_ZPZZ_FP(FMINP, aa64_sve2, sve2_fminp_zpzz) TRANS_FEAT_NONSTREAMING(FMMLA_s, aa64_sve_f32mm, gen_gvec_fpst_zzzz, gen_helper_fmmla_s, a->rd, a->rn, a->rm, a->ra, - 0, FPST_FPCR) + 0, FPST_A64) TRANS_FEAT_NONSTREAMING(FMMLA_d, aa64_sve_f64mm, gen_gvec_fpst_zzzz, gen_helper_fmmla_d, a->rd, a->rn, a->rm, a->ra, - 0, FPST_FPCR) + 0, FPST_A64) static gen_helper_gvec_4 * const sqdmlal_zzzw_fns[] = { NULL, gen_helper_sve2_sqdmlal_zzzw_h, @@ -7035,17 +7035,17 @@ TRANS_FEAT_NONSTREAMING(RAX1, aa64_sve2_sha3, gen_gvec_fn_arg_zzz, gen_gvec_rax1, a) TRANS_FEAT(FCVTNT_sh, aa64_sve2, gen_gvec_fpst_arg_zpz, - gen_helper_sve2_fcvtnt_sh, a, 0, FPST_FPCR) + gen_helper_sve2_fcvtnt_sh, a, 0, FPST_A64) TRANS_FEAT(FCVTNT_ds, aa64_sve2, gen_gvec_fpst_arg_zpz, - gen_helper_sve2_fcvtnt_ds, a, 0, FPST_FPCR) + gen_helper_sve2_fcvtnt_ds, a, 0, FPST_A64) TRANS_FEAT(BFCVTNT, aa64_sve_bf16, gen_gvec_fpst_arg_zpz, - gen_helper_sve_bfcvtnt, a, 0, FPST_FPCR) + gen_helper_sve_bfcvtnt, a, 0, FPST_A64) TRANS_FEAT(FCVTLT_hs, aa64_sve2, gen_gvec_fpst_arg_zpz, - gen_helper_sve2_fcvtlt_hs, a, 0, FPST_FPCR) + gen_helper_sve2_fcvtlt_hs, a, 0, FPST_A64) TRANS_FEAT(FCVTLT_sd, aa64_sve2, gen_gvec_fpst_arg_zpz, - gen_helper_sve2_fcvtlt_sd, a, 0, FPST_FPCR) + gen_helper_sve2_fcvtlt_sd, a, 0, FPST_A64) TRANS_FEAT(FCVTX_ds, aa64_sve2, do_frint_mode, a, FPROUNDING_ODD, gen_helper_sve_fcvt_ds) @@ -7057,7 +7057,7 @@ static gen_helper_gvec_3_ptr * const flogb_fns[] = { gen_helper_flogb_s, gen_helper_flogb_d }; TRANS_FEAT(FLOGB, aa64_sve2, gen_gvec_fpst_arg_zpz, flogb_fns[a->esz], - a, 0, a->esz == MO_16 ? FPST_FPCR_F16 : FPST_FPCR) + a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static bool do_FMLAL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sub, bool sel) { @@ -7101,7 +7101,7 @@ TRANS_FEAT_NONSTREAMING(BFMMLA, aa64_sve_bf16, gen_gvec_env_arg_zzzz, static bool do_BFMLAL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sel) { return gen_gvec_fpst_zzzz(s, gen_helper_gvec_bfmlal, - a->rd, a->rn, a->rm, a->ra, sel, FPST_FPCR); + a->rd, a->rn, a->rm, a->ra, sel, FPST_A64); } TRANS_FEAT(BFMLALB_zzzw, aa64_sve_bf16, do_BFMLAL_zzzw, a, false) @@ -7111,7 +7111,7 @@ static bool do_BFMLAL_zzxw(DisasContext *s, arg_rrxr_esz *a, bool sel) { return gen_gvec_fpst_zzzz(s, gen_helper_gvec_bfmlal_idx, a->rd, a->rn, a->rm, a->ra, - (a->index << 1) | sel, FPST_FPCR); + (a->index << 1) | sel, FPST_A64); } TRANS_FEAT(BFMLALB_zzxw, aa64_sve_bf16, do_BFMLAL_zzxw, a, false) diff --git a/target/arm/tcg/translate-vfp.c b/target/arm/tcg/translate-vfp.c index 3cbe9a7418..8d9d1ab877 100644 --- a/target/arm/tcg/translate-vfp.c +++ b/target/arm/tcg/translate-vfp.c @@ -460,9 +460,9 @@ static bool trans_VRINT(DisasContext *s, arg_VRINT *a) } if (sz == 1) { - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); } else { - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); } tcg_rmode = gen_set_rmode(rounding, fpst); @@ -527,9 +527,9 @@ static bool trans_VCVT(DisasContext *s, arg_VCVT *a) } if (sz == 1) { - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); } else { - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); } tcg_shift = tcg_constant_i32(0); @@ -1398,7 +1398,7 @@ static bool do_vfp_3op_sp(DisasContext *s, VFPGen3OpSPFn *fn, f0 = tcg_temp_new_i32(); f1 = tcg_temp_new_i32(); fd = tcg_temp_new_i32(); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); vfp_load_reg32(f0, vn); vfp_load_reg32(f1, vm); @@ -1433,7 +1433,7 @@ static bool do_vfp_3op_hp(DisasContext *s, VFPGen3OpSPFn *fn, /* * Do a half-precision operation. Functionally this is * the same as do_vfp_3op_sp(), except: - * - it uses the FPST_FPCR_F16 + * - it uses the FPST_A32_F16 * - it doesn't need the VFP vector handling (fp16 is a * v8 feature, and in v8 VFP vectors don't exist) * - it does the aa32_fp16_arith feature test @@ -1456,7 +1456,7 @@ static bool do_vfp_3op_hp(DisasContext *s, VFPGen3OpSPFn *fn, f0 = tcg_temp_new_i32(); f1 = tcg_temp_new_i32(); fd = tcg_temp_new_i32(); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); vfp_load_reg16(f0, vn); vfp_load_reg16(f1, vm); @@ -1517,7 +1517,7 @@ static bool do_vfp_3op_dp(DisasContext *s, VFPGen3OpDPFn *fn, f0 = tcg_temp_new_i64(); f1 = tcg_temp_new_i64(); fd = tcg_temp_new_i64(); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); vfp_load_reg64(f0, vn); vfp_load_reg64(f1, vm); @@ -2122,7 +2122,7 @@ static bool do_vfm_hp(DisasContext *s, arg_VFMA_sp *a, bool neg_n, bool neg_d) /* VFNMA, VFNMS */ gen_vfp_negh(vd, vd); } - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); gen_helper_vfp_muladdh(vd, vn, vm, vd, fpst); vfp_store_reg32(vd, a->vd); return true; @@ -2181,7 +2181,7 @@ static bool do_vfm_sp(DisasContext *s, arg_VFMA_sp *a, bool neg_n, bool neg_d) /* VFNMA, VFNMS */ gen_vfp_negs(vd, vd); } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_vfp_muladds(vd, vn, vm, vd, fpst); vfp_store_reg32(vd, a->vd); return true; @@ -2246,7 +2246,7 @@ static bool do_vfm_dp(DisasContext *s, arg_VFMA_dp *a, bool neg_n, bool neg_d) /* VFNMA, VFNMS */ gen_vfp_negd(vd, vd); } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_vfp_muladdd(vd, vn, vm, vd, fpst); vfp_store_reg64(vd, a->vd); return true; @@ -2424,17 +2424,17 @@ DO_VFP_2OP(VNEG, dp, gen_vfp_negd, aa32_fpdp_v2) static void gen_VSQRT_hp(TCGv_i32 vd, TCGv_i32 vm) { - gen_helper_vfp_sqrth(vd, vm, fpstatus_ptr(FPST_FPCR_F16)); + gen_helper_vfp_sqrth(vd, vm, fpstatus_ptr(FPST_A32_F16)); } static void gen_VSQRT_sp(TCGv_i32 vd, TCGv_i32 vm) { - gen_helper_vfp_sqrts(vd, vm, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_sqrts(vd, vm, fpstatus_ptr(FPST_A32)); } static void gen_VSQRT_dp(TCGv_i64 vd, TCGv_i64 vm) { - gen_helper_vfp_sqrtd(vd, vm, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_sqrtd(vd, vm, fpstatus_ptr(FPST_A32)); } DO_VFP_2OP(VSQRT, hp, gen_VSQRT_hp, aa32_fp16_arith) @@ -2565,7 +2565,7 @@ static bool trans_VCVT_f32_f16(DisasContext *s, arg_VCVT_f32_f16 *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); ahp_mode = get_ahp_flag(); tmp = tcg_temp_new_i32(); /* The T bit tells us if we want the low or high 16 bits of Vm */ @@ -2599,7 +2599,7 @@ static bool trans_VCVT_f64_f16(DisasContext *s, arg_VCVT_f64_f16 *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); ahp_mode = get_ahp_flag(); tmp = tcg_temp_new_i32(); /* The T bit tells us if we want the low or high 16 bits of Vm */ @@ -2623,7 +2623,7 @@ static bool trans_VCVT_b16_f32(DisasContext *s, arg_VCVT_b16_f32 *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); tmp = tcg_temp_new_i32(); vfp_load_reg32(tmp, a->vm); @@ -2646,7 +2646,7 @@ static bool trans_VCVT_f16_f32(DisasContext *s, arg_VCVT_f16_f32 *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); ahp_mode = get_ahp_flag(); tmp = tcg_temp_new_i32(); @@ -2680,7 +2680,7 @@ static bool trans_VCVT_f16_f64(DisasContext *s, arg_VCVT_f16_f64 *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); ahp_mode = get_ahp_flag(); tmp = tcg_temp_new_i32(); vm = tcg_temp_new_i64(); @@ -2706,7 +2706,7 @@ static bool trans_VRINTR_hp(DisasContext *s, arg_VRINTR_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg16(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); gen_helper_rinth(tmp, tmp, fpst); vfp_store_reg32(tmp, a->vd); return true; @@ -2727,7 +2727,7 @@ static bool trans_VRINTR_sp(DisasContext *s, arg_VRINTR_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg32(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_rints(tmp, tmp, fpst); vfp_store_reg32(tmp, a->vd); return true; @@ -2757,7 +2757,7 @@ static bool trans_VRINTR_dp(DisasContext *s, arg_VRINTR_dp *a) tmp = tcg_temp_new_i64(); vfp_load_reg64(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_rintd(tmp, tmp, fpst); vfp_store_reg64(tmp, a->vd); return true; @@ -2779,7 +2779,7 @@ static bool trans_VRINTZ_hp(DisasContext *s, arg_VRINTZ_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg16(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); tcg_rmode = gen_set_rmode(FPROUNDING_ZERO, fpst); gen_helper_rinth(tmp, tmp, fpst); gen_restore_rmode(tcg_rmode, fpst); @@ -2803,7 +2803,7 @@ static bool trans_VRINTZ_sp(DisasContext *s, arg_VRINTZ_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg32(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); tcg_rmode = gen_set_rmode(FPROUNDING_ZERO, fpst); gen_helper_rints(tmp, tmp, fpst); gen_restore_rmode(tcg_rmode, fpst); @@ -2836,7 +2836,7 @@ static bool trans_VRINTZ_dp(DisasContext *s, arg_VRINTZ_dp *a) tmp = tcg_temp_new_i64(); vfp_load_reg64(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); tcg_rmode = gen_set_rmode(FPROUNDING_ZERO, fpst); gen_helper_rintd(tmp, tmp, fpst); gen_restore_rmode(tcg_rmode, fpst); @@ -2859,7 +2859,7 @@ static bool trans_VRINTX_hp(DisasContext *s, arg_VRINTX_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg16(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); gen_helper_rinth_exact(tmp, tmp, fpst); vfp_store_reg32(tmp, a->vd); return true; @@ -2880,7 +2880,7 @@ static bool trans_VRINTX_sp(DisasContext *s, arg_VRINTX_sp *a) tmp = tcg_temp_new_i32(); vfp_load_reg32(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_rints_exact(tmp, tmp, fpst); vfp_store_reg32(tmp, a->vd); return true; @@ -2910,7 +2910,7 @@ static bool trans_VRINTX_dp(DisasContext *s, arg_VRINTX_dp *a) tmp = tcg_temp_new_i64(); vfp_load_reg64(tmp, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); gen_helper_rintd_exact(tmp, tmp, fpst); vfp_store_reg64(tmp, a->vd); return true; @@ -2937,7 +2937,7 @@ static bool trans_VCVT_sp(DisasContext *s, arg_VCVT_sp *a) vm = tcg_temp_new_i32(); vd = tcg_temp_new_i64(); vfp_load_reg32(vm, a->vm); - gen_helper_vfp_fcvtds(vd, vm, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_fcvtds(vd, vm, fpstatus_ptr(FPST_A32)); vfp_store_reg64(vd, a->vd); return true; } @@ -2963,7 +2963,7 @@ static bool trans_VCVT_dp(DisasContext *s, arg_VCVT_dp *a) vd = tcg_temp_new_i32(); vm = tcg_temp_new_i64(); vfp_load_reg64(vm, a->vm); - gen_helper_vfp_fcvtsd(vd, vm, fpstatus_ptr(FPST_FPCR)); + gen_helper_vfp_fcvtsd(vd, vm, fpstatus_ptr(FPST_A32)); vfp_store_reg32(vd, a->vd); return true; } @@ -2983,7 +2983,7 @@ static bool trans_VCVT_int_hp(DisasContext *s, arg_VCVT_int_sp *a) vm = tcg_temp_new_i32(); vfp_load_reg32(vm, a->vm); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); if (a->s) { /* i32 -> f16 */ gen_helper_vfp_sitoh(vm, vm, fpst); @@ -3010,7 +3010,7 @@ static bool trans_VCVT_int_sp(DisasContext *s, arg_VCVT_int_sp *a) vm = tcg_temp_new_i32(); vfp_load_reg32(vm, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); if (a->s) { /* i32 -> f32 */ gen_helper_vfp_sitos(vm, vm, fpst); @@ -3044,7 +3044,7 @@ static bool trans_VCVT_int_dp(DisasContext *s, arg_VCVT_int_dp *a) vm = tcg_temp_new_i32(); vd = tcg_temp_new_i64(); vfp_load_reg32(vm, a->vm); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); if (a->s) { /* i32 -> f64 */ gen_helper_vfp_sitod(vd, vm, fpst); @@ -3105,7 +3105,7 @@ static bool trans_VCVT_fix_hp(DisasContext *s, arg_VCVT_fix_sp *a) vd = tcg_temp_new_i32(); vfp_load_reg32(vd, a->vd); - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); shift = tcg_constant_i32(frac_bits); /* Switch on op:U:sx bits */ @@ -3161,7 +3161,7 @@ static bool trans_VCVT_fix_sp(DisasContext *s, arg_VCVT_fix_sp *a) vd = tcg_temp_new_i32(); vfp_load_reg32(vd, a->vd); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); shift = tcg_constant_i32(frac_bits); /* Switch on op:U:sx bits */ @@ -3223,7 +3223,7 @@ static bool trans_VCVT_fix_dp(DisasContext *s, arg_VCVT_fix_dp *a) vd = tcg_temp_new_i64(); vfp_load_reg64(vd, a->vd); - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); shift = tcg_constant_i32(frac_bits); /* Switch on op:U:sx bits */ @@ -3273,7 +3273,7 @@ static bool trans_VCVT_hp_int(DisasContext *s, arg_VCVT_sp_int *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR_F16); + fpst = fpstatus_ptr(FPST_A32_F16); vm = tcg_temp_new_i32(); vfp_load_reg16(vm, a->vm); @@ -3307,7 +3307,7 @@ static bool trans_VCVT_sp_int(DisasContext *s, arg_VCVT_sp_int *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); vm = tcg_temp_new_i32(); vfp_load_reg32(vm, a->vm); @@ -3347,7 +3347,7 @@ static bool trans_VCVT_dp_int(DisasContext *s, arg_VCVT_dp_int *a) return true; } - fpst = fpstatus_ptr(FPST_FPCR); + fpst = fpstatus_ptr(FPST_A32); vm = tcg_temp_new_i64(); vd = tcg_temp_new_i32(); vfp_load_reg64(vm, a->vm); diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h index 2d37d7c9f2..084ee63d99 100644 --- a/target/arm/tcg/translate.h +++ b/target/arm/tcg/translate.h @@ -670,8 +670,10 @@ static inline CPUARMTBFlags arm_tbflags_from_tb(const TranslationBlock *tb) * Enum for argument to fpstatus_ptr(). */ typedef enum ARMFPStatusFlavour { - FPST_FPCR, - FPST_FPCR_F16, + FPST_A32, + FPST_A64, + FPST_A32_F16, + FPST_A64_F16, FPST_STD, FPST_STD_F16, } ARMFPStatusFlavour; @@ -684,10 +686,14 @@ typedef enum ARMFPStatusFlavour { * been set up to point to the requested field in the CPU state struct. * The options are: * - * FPST_FPCR - * for non-FP16 operations controlled by the FPCR - * FPST_FPCR_F16 - * for operations controlled by the FPCR where FPCR.FZ16 is to be used + * FPST_A32 + * for AArch32 non-FP16 operations controlled by the FPCR + * FPST_A64 + * for AArch64 non-FP16 operations controlled by the FPCR + * FPST_A32_F16 + * for AArch32 operations controlled by the FPCR where FPCR.FZ16 is to be used + * FPST_A64_F16 + * for AArch64 operations controlled by the FPCR where FPCR.FZ16 is to be used * FPST_STD * for A32/T32 Neon operations using the "standard FPSCR value" * FPST_STD_F16 @@ -699,11 +705,17 @@ static inline TCGv_ptr fpstatus_ptr(ARMFPStatusFlavour flavour) int offset; switch (flavour) { - case FPST_FPCR: - offset = offsetof(CPUARMState, vfp.fp_status); + case FPST_A32: + offset = offsetof(CPUARMState, vfp.fp_status_a32); break; - case FPST_FPCR_F16: - offset = offsetof(CPUARMState, vfp.fp_status_f16); + case FPST_A64: + offset = offsetof(CPUARMState, vfp.fp_status_a64); + break; + case FPST_A32_F16: + offset = offsetof(CPUARMState, vfp.fp_status_f16_a32); + break; + case FPST_A64_F16: + offset = offsetof(CPUARMState, vfp.fp_status_f16_a64); break; case FPST_STD: offset = offsetof(CPUARMState, vfp.standard_fp_status); diff --git a/target/arm/tcg/vec_helper.c b/target/arm/tcg/vec_helper.c index e3083c6e84..7330b373c3 100644 --- a/target/arm/tcg/vec_helper.c +++ b/target/arm/tcg/vec_helper.c @@ -2060,14 +2060,14 @@ void HELPER(gvec_fmlal_a32)(void *vd, void *vn, void *vm, CPUARMState *env, uint32_t desc) { do_fmlal(vd, vn, vm, &env->vfp.standard_fp_status, desc, - get_flush_inputs_to_zero(&env->vfp.fp_status_f16)); + get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a32)); } void HELPER(gvec_fmlal_a64)(void *vd, void *vn, void *vm, CPUARMState *env, uint32_t desc) { - do_fmlal(vd, vn, vm, &env->vfp.fp_status, desc, - get_flush_inputs_to_zero(&env->vfp.fp_status_f16)); + do_fmlal(vd, vn, vm, &env->vfp.fp_status_a64, desc, + get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a64)); } void HELPER(sve2_fmlal_zzzw_s)(void *vd, void *vn, void *vm, void *va, @@ -2076,8 +2076,8 @@ void HELPER(sve2_fmlal_zzzw_s)(void *vd, void *vn, void *vm, void *va, intptr_t i, oprsz = simd_oprsz(desc); uint16_t negn = extract32(desc, SIMD_DATA_SHIFT, 1) << 15; intptr_t sel = extract32(desc, SIMD_DATA_SHIFT + 1, 1) * sizeof(float16); - float_status *status = &env->vfp.fp_status; - bool fz16 = get_flush_inputs_to_zero(&env->vfp.fp_status_f16); + float_status *status = &env->vfp.fp_status_a64; + bool fz16 = get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a64); for (i = 0; i < oprsz; i += sizeof(float32)) { float16 nn_16 = *(float16 *)(vn + H1_2(i + sel)) ^ negn; @@ -2122,14 +2122,14 @@ void HELPER(gvec_fmlal_idx_a32)(void *vd, void *vn, void *vm, CPUARMState *env, uint32_t desc) { do_fmlal_idx(vd, vn, vm, &env->vfp.standard_fp_status, desc, - get_flush_inputs_to_zero(&env->vfp.fp_status_f16)); + get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a32)); } void HELPER(gvec_fmlal_idx_a64)(void *vd, void *vn, void *vm, CPUARMState *env, uint32_t desc) { - do_fmlal_idx(vd, vn, vm, &env->vfp.fp_status, desc, - get_flush_inputs_to_zero(&env->vfp.fp_status_f16)); + do_fmlal_idx(vd, vn, vm, &env->vfp.fp_status_a64, desc, + get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a64)); } void HELPER(sve2_fmlal_zzxw_s)(void *vd, void *vn, void *vm, void *va, @@ -2139,8 +2139,8 @@ void HELPER(sve2_fmlal_zzxw_s)(void *vd, void *vn, void *vm, void *va, uint16_t negn = extract32(desc, SIMD_DATA_SHIFT, 1) << 15; intptr_t sel = extract32(desc, SIMD_DATA_SHIFT + 1, 1) * sizeof(float16); intptr_t idx = extract32(desc, SIMD_DATA_SHIFT + 2, 3) * sizeof(float16); - float_status *status = &env->vfp.fp_status; - bool fz16 = get_flush_inputs_to_zero(&env->vfp.fp_status_f16); + float_status *status = &env->vfp.fp_status_a64; + bool fz16 = get_flush_inputs_to_zero(&env->vfp.fp_status_f16_a64); for (i = 0; i < oprsz; i += 16) { float16 mm_16 = *(float16 *)(vm + i + idx); @@ -2808,7 +2808,7 @@ bool is_ebf(CPUARMState *env, float_status *statusp, float_status *oddstatusp) */ bool ebf = is_a64(env) && env->vfp.fpcr & FPCR_EBF; - *statusp = env->vfp.fp_status; + *statusp = is_a64(env) ? env->vfp.fp_status_a64 : env->vfp.fp_status_a32; set_default_nan_mode(true, statusp); if (ebf) { diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_helper.c index fc20a56753..3c8f3e6588 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_helper.c @@ -34,42 +34,45 @@ #ifdef CONFIG_TCG /* Convert host exception flags to vfp form. */ -static inline int vfp_exceptbits_from_host(int host_bits) +static inline uint32_t vfp_exceptbits_from_host(int host_bits) { - int target_bits = 0; + uint32_t target_bits = 0; if (host_bits & float_flag_invalid) { - target_bits |= 1; + target_bits |= FPSR_IOC; } if (host_bits & float_flag_divbyzero) { - target_bits |= 2; + target_bits |= FPSR_DZC; } if (host_bits & float_flag_overflow) { - target_bits |= 4; + target_bits |= FPSR_OFC; } - if (host_bits & (float_flag_underflow | float_flag_output_denormal)) { - target_bits |= 8; + if (host_bits & (float_flag_underflow | float_flag_output_denormal_flushed)) { + target_bits |= FPSR_UFC; } if (host_bits & float_flag_inexact) { - target_bits |= 0x10; + target_bits |= FPSR_IXC; } - if (host_bits & float_flag_input_denormal) { - target_bits |= 0x80; + if (host_bits & float_flag_input_denormal_flushed) { + target_bits |= FPSR_IDC; } return target_bits; } static uint32_t vfp_get_fpsr_from_host(CPUARMState *env) { - uint32_t i; + uint32_t i = 0; - i = get_float_exception_flags(&env->vfp.fp_status); + i |= get_float_exception_flags(&env->vfp.fp_status_a32); + i |= get_float_exception_flags(&env->vfp.fp_status_a64); i |= get_float_exception_flags(&env->vfp.standard_fp_status); /* FZ16 does not generate an input denormal exception. */ - i |= (get_float_exception_flags(&env->vfp.fp_status_f16) - & ~float_flag_input_denormal); + i |= (get_float_exception_flags(&env->vfp.fp_status_f16_a32) + & ~float_flag_input_denormal_flushed); + i |= (get_float_exception_flags(&env->vfp.fp_status_f16_a64) + & ~float_flag_input_denormal_flushed); i |= (get_float_exception_flags(&env->vfp.standard_fp_status_f16) - & ~float_flag_input_denormal); + & ~float_flag_input_denormal_flushed); return vfp_exceptbits_from_host(i); } @@ -80,8 +83,10 @@ static void vfp_clear_float_status_exc_flags(CPUARMState *env) * values. The caller should have arranged for env->vfp.fpsr to * be the architecturally up-to-date exception flag information first. */ - set_float_exception_flags(0, &env->vfp.fp_status); - set_float_exception_flags(0, &env->vfp.fp_status_f16); + set_float_exception_flags(0, &env->vfp.fp_status_a32); + set_float_exception_flags(0, &env->vfp.fp_status_a64); + set_float_exception_flags(0, &env->vfp.fp_status_f16_a32); + set_float_exception_flags(0, &env->vfp.fp_status_f16_a64); set_float_exception_flags(0, &env->vfp.standard_fp_status); set_float_exception_flags(0, &env->vfp.standard_fp_status_f16); } @@ -108,25 +113,33 @@ static void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) i = float_round_to_zero; break; } - set_float_rounding_mode(i, &env->vfp.fp_status); - set_float_rounding_mode(i, &env->vfp.fp_status_f16); + set_float_rounding_mode(i, &env->vfp.fp_status_a32); + set_float_rounding_mode(i, &env->vfp.fp_status_a64); + set_float_rounding_mode(i, &env->vfp.fp_status_f16_a32); + set_float_rounding_mode(i, &env->vfp.fp_status_f16_a64); } if (changed & FPCR_FZ16) { bool ftz_enabled = val & FPCR_FZ16; - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_f16); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_f16_a32); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_f16_a64); set_flush_to_zero(ftz_enabled, &env->vfp.standard_fp_status_f16); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_f16); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_f16_a32); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_f16_a64); set_flush_inputs_to_zero(ftz_enabled, &env->vfp.standard_fp_status_f16); } if (changed & FPCR_FZ) { bool ftz_enabled = val & FPCR_FZ; - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_a32); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_a32); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_a64); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_a64); } if (changed & FPCR_DN) { bool dnan_enabled = val & FPCR_DN; - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_f16); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_a32); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_a64); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_f16_a32); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_f16_a64); } } @@ -360,9 +373,9 @@ void VFP_HELPER(cmpe, P)(ARGTYPE a, ARGTYPE b, CPUARMState *env) \ softfloat_to_vfp_compare(env, \ FLOATTYPE ## _compare(a, b, &env->vfp.FPST)); \ } -DO_VFP_cmp(h, float16, dh_ctype_f16, fp_status_f16) -DO_VFP_cmp(s, float32, float32, fp_status) -DO_VFP_cmp(d, float64, float64, fp_status) +DO_VFP_cmp(h, float16, dh_ctype_f16, fp_status_f16_a32) +DO_VFP_cmp(s, float32, float32, fp_status_a32) +DO_VFP_cmp(d, float64, float64, fp_status_a32) #undef DO_VFP_cmp /* Integer to float and float to integer conversions */ @@ -1120,7 +1133,7 @@ uint64_t HELPER(fjcvtzs)(float64 value, float_status *status) /* Normal inexact, denormal with flush-to-zero, or overflow or NaN */ inexact = e_new & (float_flag_inexact | - float_flag_input_denormal | + float_flag_input_denormal_flushed | float_flag_invalid); /* While not inexact for IEEE FP, -0.0 is inexact for JavaScript. */ @@ -1132,7 +1145,7 @@ uint64_t HELPER(fjcvtzs)(float64 value, float_status *status) uint32_t HELPER(vjcvt)(float64 value, CPUARMState *env) { - uint64_t pair = HELPER(fjcvtzs)(value, &env->vfp.fp_status); + uint64_t pair = HELPER(fjcvtzs)(value, &env->vfp.fp_status_a32); uint32_t result = pair; uint32_t z = (pair >> 32) == 0; diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 1b9c11022c..b5dd60d281 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -1364,7 +1364,7 @@ FeatureWordInfo feature_word_info[FEATURE_WORDS] = { "taa-no", NULL, NULL, NULL, NULL, "sbdr-ssdp-no", "fbsdp-no", "psdp-no", NULL, "fb-clear", NULL, NULL, - NULL, NULL, NULL, NULL, + "bhi-no", NULL, NULL, NULL, "pbrsb-no", NULL, "gds-no", "rfds-no", "rfds-clear", NULL, NULL, NULL, }, @@ -4549,6 +4549,160 @@ static const X86CPUDefinition builtin_x86_defs[] = { .model_id = "Intel Xeon Processor (SierraForest)", .versions = (X86CPUVersionDefinition[]) { { .version = 1 }, + { + .version = 2, + .props = (PropValue[]) { + { "ss", "on" }, + { "tsc-adjust", "on" }, + { "cldemote", "on" }, + { "movdiri", "on" }, + { "movdir64b", "on" }, + { "gds-no", "on" }, + { "rfds-no", "on" }, + { "lam", "on" }, + { "intel-psfd", "on"}, + { "ipred-ctrl", "on"}, + { "rrsba-ctrl", "on"}, + { "bhi-ctrl", "on"}, + { "stepping", "3" }, + { /* end of list */ } + } + }, + { /* end of list */ }, + }, + }, + { + .name = "ClearwaterForest", + .level = 0x23, + .xlevel = 0x80000008, + .vendor = CPUID_VENDOR_INTEL, + .family = 6, + .model = 221, + .stepping = 0, + /* + * please keep the ascending order so that we can have a clear view of + * bit position of each feature. + */ + .features[FEAT_1_EDX] = + CPUID_FP87 | CPUID_VME | CPUID_DE | CPUID_PSE | CPUID_TSC | + CPUID_MSR | CPUID_PAE | CPUID_MCE | CPUID_CX8 | CPUID_APIC | + CPUID_SEP | CPUID_MTRR | CPUID_PGE | CPUID_MCA | CPUID_CMOV | + CPUID_PAT | CPUID_PSE36 | CPUID_CLFLUSH | CPUID_MMX | CPUID_FXSR | + CPUID_SSE | CPUID_SSE2 | CPUID_SS, + .features[FEAT_1_ECX] = + CPUID_EXT_SSE3 | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSSE3 | + CPUID_EXT_FMA | CPUID_EXT_CX16 | CPUID_EXT_PCID | CPUID_EXT_SSE41 | + CPUID_EXT_SSE42 | CPUID_EXT_X2APIC | CPUID_EXT_MOVBE | + CPUID_EXT_POPCNT | CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_AES | + CPUID_EXT_XSAVE | CPUID_EXT_AVX | CPUID_EXT_F16C | CPUID_EXT_RDRAND, + .features[FEAT_8000_0001_EDX] = + CPUID_EXT2_SYSCALL | CPUID_EXT2_NX | CPUID_EXT2_PDPE1GB | + CPUID_EXT2_RDTSCP | CPUID_EXT2_LM, + .features[FEAT_8000_0001_ECX] = + CPUID_EXT3_LAHF_LM | CPUID_EXT3_ABM | CPUID_EXT3_3DNOWPREFETCH, + .features[FEAT_8000_0008_EBX] = + CPUID_8000_0008_EBX_WBNOINVD, + .features[FEAT_7_0_EBX] = + CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_TSC_ADJUST | + CPUID_7_0_EBX_BMI1 | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP | + CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID | + CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX | CPUID_7_0_EBX_SMAP | + CPUID_7_0_EBX_CLFLUSHOPT | CPUID_7_0_EBX_CLWB | + CPUID_7_0_EBX_SHA_NI, + .features[FEAT_7_0_ECX] = + CPUID_7_0_ECX_UMIP | CPUID_7_0_ECX_PKU | CPUID_7_0_ECX_GFNI | + CPUID_7_0_ECX_VAES | CPUID_7_0_ECX_VPCLMULQDQ | + CPUID_7_0_ECX_RDPID | CPUID_7_0_ECX_BUS_LOCK_DETECT | + CPUID_7_0_ECX_CLDEMOTE | CPUID_7_0_ECX_MOVDIRI | + CPUID_7_0_ECX_MOVDIR64B, + .features[FEAT_7_0_EDX] = + CPUID_7_0_EDX_FSRM | CPUID_7_0_EDX_SERIALIZE | + CPUID_7_0_EDX_SPEC_CTRL | CPUID_7_0_EDX_ARCH_CAPABILITIES | + CPUID_7_0_EDX_SPEC_CTRL_SSBD, + .features[FEAT_ARCH_CAPABILITIES] = + MSR_ARCH_CAP_RDCL_NO | MSR_ARCH_CAP_IBRS_ALL | + MSR_ARCH_CAP_SKIP_L1DFL_VMENTRY | MSR_ARCH_CAP_MDS_NO | + MSR_ARCH_CAP_PSCHANGE_MC_NO | MSR_ARCH_CAP_SBDR_SSDP_NO | + MSR_ARCH_CAP_FBSDP_NO | MSR_ARCH_CAP_PSDP_NO | + MSR_ARCH_CAP_BHI_NO | MSR_ARCH_CAP_PBRSB_NO | + MSR_ARCH_CAP_GDS_NO | MSR_ARCH_CAP_RFDS_NO, + .features[FEAT_XSAVE] = + CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC | + CPUID_XSAVE_XGETBV1 | CPUID_XSAVE_XSAVES, + .features[FEAT_6_EAX] = + CPUID_6_EAX_ARAT, + .features[FEAT_7_1_EAX] = + CPUID_7_1_EAX_SHA512 | CPUID_7_1_EAX_SM3 | CPUID_7_1_EAX_SM4 | + CPUID_7_1_EAX_AVX_VNNI | CPUID_7_1_EAX_CMPCCXADD | + CPUID_7_1_EAX_FSRS | CPUID_7_1_EAX_AVX_IFMA | + CPUID_7_1_EAX_LAM, + .features[FEAT_7_1_EDX] = + CPUID_7_1_EDX_AVX_VNNI_INT8 | CPUID_7_1_EDX_AVX_NE_CONVERT | + CPUID_7_1_EDX_AVX_VNNI_INT16 | CPUID_7_1_EDX_PREFETCHITI, + .features[FEAT_7_2_EDX] = + CPUID_7_2_EDX_PSFD | CPUID_7_2_EDX_IPRED_CTRL | + CPUID_7_2_EDX_RRSBA_CTRL | CPUID_7_2_EDX_DDPD_U | + CPUID_7_2_EDX_BHI_CTRL | CPUID_7_2_EDX_MCDT_NO, + .features[FEAT_VMX_BASIC] = + MSR_VMX_BASIC_INS_OUTS | MSR_VMX_BASIC_TRUE_CTLS, + .features[FEAT_VMX_ENTRY_CTLS] = + VMX_VM_ENTRY_LOAD_DEBUG_CONTROLS | VMX_VM_ENTRY_IA32E_MODE | + VMX_VM_ENTRY_LOAD_IA32_PERF_GLOBAL_CTRL | + VMX_VM_ENTRY_LOAD_IA32_PAT | VMX_VM_ENTRY_LOAD_IA32_EFER, + .features[FEAT_VMX_EPT_VPID_CAPS] = + MSR_VMX_EPT_EXECONLY | MSR_VMX_EPT_PAGE_WALK_LENGTH_4 | + MSR_VMX_EPT_WB | MSR_VMX_EPT_2MB | MSR_VMX_EPT_1GB | + MSR_VMX_EPT_INVEPT | MSR_VMX_EPT_AD_BITS | + MSR_VMX_EPT_INVEPT_SINGLE_CONTEXT | MSR_VMX_EPT_INVEPT_ALL_CONTEXT | + MSR_VMX_EPT_INVVPID | MSR_VMX_EPT_INVVPID_SINGLE_ADDR | + MSR_VMX_EPT_INVVPID_SINGLE_CONTEXT | + MSR_VMX_EPT_INVVPID_ALL_CONTEXT | + MSR_VMX_EPT_INVVPID_SINGLE_CONTEXT_NOGLOBALS, + .features[FEAT_VMX_EXIT_CTLS] = + VMX_VM_EXIT_SAVE_DEBUG_CONTROLS | + VMX_VM_EXIT_LOAD_IA32_PERF_GLOBAL_CTRL | + VMX_VM_EXIT_ACK_INTR_ON_EXIT | VMX_VM_EXIT_SAVE_IA32_PAT | + VMX_VM_EXIT_LOAD_IA32_PAT | VMX_VM_EXIT_SAVE_IA32_EFER | + VMX_VM_EXIT_LOAD_IA32_EFER | VMX_VM_EXIT_SAVE_VMX_PREEMPTION_TIMER, + .features[FEAT_VMX_MISC] = + MSR_VMX_MISC_STORE_LMA | MSR_VMX_MISC_ACTIVITY_HLT | + MSR_VMX_MISC_VMWRITE_VMEXIT, + .features[FEAT_VMX_PINBASED_CTLS] = + VMX_PIN_BASED_EXT_INTR_MASK | VMX_PIN_BASED_NMI_EXITING | + VMX_PIN_BASED_VIRTUAL_NMIS | VMX_PIN_BASED_VMX_PREEMPTION_TIMER | + VMX_PIN_BASED_POSTED_INTR, + .features[FEAT_VMX_PROCBASED_CTLS] = + VMX_CPU_BASED_VIRTUAL_INTR_PENDING | + VMX_CPU_BASED_USE_TSC_OFFSETING | VMX_CPU_BASED_HLT_EXITING | + VMX_CPU_BASED_INVLPG_EXITING | VMX_CPU_BASED_MWAIT_EXITING | + VMX_CPU_BASED_RDPMC_EXITING | VMX_CPU_BASED_RDTSC_EXITING | + VMX_CPU_BASED_CR3_LOAD_EXITING | VMX_CPU_BASED_CR3_STORE_EXITING | + VMX_CPU_BASED_CR8_LOAD_EXITING | VMX_CPU_BASED_CR8_STORE_EXITING | + VMX_CPU_BASED_TPR_SHADOW | VMX_CPU_BASED_VIRTUAL_NMI_PENDING | + VMX_CPU_BASED_MOV_DR_EXITING | VMX_CPU_BASED_UNCOND_IO_EXITING | + VMX_CPU_BASED_USE_IO_BITMAPS | VMX_CPU_BASED_MONITOR_TRAP_FLAG | + VMX_CPU_BASED_USE_MSR_BITMAPS | VMX_CPU_BASED_MONITOR_EXITING | + VMX_CPU_BASED_PAUSE_EXITING | + VMX_CPU_BASED_ACTIVATE_SECONDARY_CONTROLS, + .features[FEAT_VMX_SECONDARY_CTLS] = + VMX_SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES | + VMX_SECONDARY_EXEC_ENABLE_EPT | VMX_SECONDARY_EXEC_DESC | + VMX_SECONDARY_EXEC_RDTSCP | + VMX_SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + VMX_SECONDARY_EXEC_ENABLE_VPID | VMX_SECONDARY_EXEC_WBINVD_EXITING | + VMX_SECONDARY_EXEC_UNRESTRICTED_GUEST | + VMX_SECONDARY_EXEC_APIC_REGISTER_VIRT | + VMX_SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY | + VMX_SECONDARY_EXEC_RDRAND_EXITING | + VMX_SECONDARY_EXEC_ENABLE_INVPCID | + VMX_SECONDARY_EXEC_ENABLE_VMFUNC | VMX_SECONDARY_EXEC_SHADOW_VMCS | + VMX_SECONDARY_EXEC_RDSEED_EXITING | VMX_SECONDARY_EXEC_ENABLE_PML | + VMX_SECONDARY_EXEC_XSAVES, + .features[FEAT_VMX_VMFUNC] = + MSR_VMX_VMFUNC_EPT_SWITCHING, + .model_id = "Intel Xeon Processor (ClearwaterForest)", + .versions = (X86CPUVersionDefinition[]) { + { .version = 1 }, { /* end of list */ }, }, }, diff --git a/target/i386/cpu.h b/target/i386/cpu.h index b26e25ba15..c67b42d34f 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -951,6 +951,12 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); /* Speculative Store Bypass Disable */ #define CPUID_7_0_EDX_SPEC_CTRL_SSBD (1U << 31) +/* SHA512 Instruction */ +#define CPUID_7_1_EAX_SHA512 (1U << 0) +/* SM3 Instruction */ +#define CPUID_7_1_EAX_SM3 (1U << 1) +/* SM4 Instruction */ +#define CPUID_7_1_EAX_SM4 (1U << 2) /* AVX VNNI Instruction */ #define CPUID_7_1_EAX_AVX_VNNI (1U << 4) /* AVX512 BFloat16 Instruction */ @@ -963,6 +969,12 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define CPUID_7_1_EAX_FSRS (1U << 11) /* Fast Short REP CMPS/SCAS */ #define CPUID_7_1_EAX_FSRC (1U << 12) +/* Flexible return and event delivery (FRED) */ +#define CPUID_7_1_EAX_FRED (1U << 17) +/* Load into IA32_KERNEL_GS_BASE (LKGS) */ +#define CPUID_7_1_EAX_LKGS (1U << 18) +/* Non-Serializing Write to Model Specific Register (WRMSRNS) */ +#define CPUID_7_1_EAX_WRMSRNS (1U << 19) /* Support Tile Computational Operations on FP16 Numbers */ #define CPUID_7_1_EAX_AMX_FP16 (1U << 21) /* Support for VPMADD52[H,L]UQ */ @@ -976,17 +988,23 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define CPUID_7_1_EDX_AVX_NE_CONVERT (1U << 5) /* AMX COMPLEX Instructions */ #define CPUID_7_1_EDX_AMX_COMPLEX (1U << 8) +/* AVX-VNNI-INT16 Instructions */ +#define CPUID_7_1_EDX_AVX_VNNI_INT16 (1U << 10) /* PREFETCHIT0/1 Instructions */ #define CPUID_7_1_EDX_PREFETCHITI (1U << 14) /* Support for Advanced Vector Extensions 10 */ #define CPUID_7_1_EDX_AVX10 (1U << 19) -/* Flexible return and event delivery (FRED) */ -#define CPUID_7_1_EAX_FRED (1U << 17) -/* Load into IA32_KERNEL_GS_BASE (LKGS) */ -#define CPUID_7_1_EAX_LKGS (1U << 18) -/* Non-Serializing Write to Model Specific Register (WRMSRNS) */ -#define CPUID_7_1_EAX_WRMSRNS (1U << 19) +/* Indicate bit 7 of the IA32_SPEC_CTRL MSR is supported */ +#define CPUID_7_2_EDX_PSFD (1U << 0) +/* Indicate bits 3 and 4 of the IA32_SPEC_CTRL MSR are supported */ +#define CPUID_7_2_EDX_IPRED_CTRL (1U << 1) +/* Indicate bits 5 and 6 of the IA32_SPEC_CTRL MSR are supported */ +#define CPUID_7_2_EDX_RRSBA_CTRL (1U << 2) +/* Indicate bit 8 of the IA32_SPEC_CTRL MSR is supported */ +#define CPUID_7_2_EDX_DDPD_U (1U << 3) +/* Indicate bit 10 of the IA32_SPEC_CTRL MSR is supported */ +#define CPUID_7_2_EDX_BHI_CTRL (1U << 4) /* Do not exhibit MXCSR Configuration Dependent Timing (MCDT) behavior */ #define CPUID_7_2_EDX_MCDT_NO (1U << 5) @@ -1144,7 +1162,10 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define MSR_ARCH_CAP_FBSDP_NO (1U << 14) #define MSR_ARCH_CAP_PSDP_NO (1U << 15) #define MSR_ARCH_CAP_FB_CLEAR (1U << 17) +#define MSR_ARCH_CAP_BHI_NO (1U << 20) #define MSR_ARCH_CAP_PBRSB_NO (1U << 24) +#define MSR_ARCH_CAP_GDS_NO (1U << 26) +#define MSR_ARCH_CAP_RFDS_NO (1U << 27) #define MSR_CORE_CAP_SPLIT_LOCK_DETECT (1U << 5) diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 97069b969f..0fa1664a24 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1630,7 +1630,7 @@ static void gen_CMC(DisasContext *s, X86DecodedInsn *decode) static void gen_CMOVcc(DisasContext *s, X86DecodedInsn *decode) { - gen_cmovcc1(s, decode->b & 0xf, s->T0, s->T1); + gen_cmovcc(s, decode->b & 0xf, s->T0, s->T1); } static void gen_CMPccXADD(DisasContext *s, X86DecodedInsn *decode) @@ -1739,11 +1739,7 @@ static void gen_CMPccXADD(DisasContext *s, X86DecodedInsn *decode) static void gen_CMPS(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[2].ot; - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz_nz(s, ot, gen_cmps); - } else { - gen_cmps(s, ot); - } + gen_repz_nz(s, ot, gen_cmps); } static void gen_CMPXCHG(DisasContext *s, X86DecodedInsn *decode) @@ -2234,11 +2230,7 @@ static void gen_INS(DisasContext *s, X86DecodedInsn *decode) } translator_io_start(&s->base); - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz(s, ot, gen_ins); - } else { - gen_ins(s, ot); - } + gen_repz(s, ot, gen_ins); } static void gen_INSERTQ_i(DisasContext *s, X86DecodedInsn *decode) @@ -2293,8 +2285,11 @@ static void gen_IRET(DisasContext *s, X86DecodedInsn *decode) static void gen_Jcc(DisasContext *s, X86DecodedInsn *decode) { + TCGLabel *taken = gen_new_label(); + gen_bnd_jmp(s); - gen_jcc(s, decode->b & 0xf, decode->immediate); + gen_jcc(s, decode->b & 0xf, taken); + gen_conditional_jump_labels(s, decode->immediate, NULL, taken); } static void gen_JCXZ(DisasContext *s, X86DecodedInsn *decode) @@ -2419,11 +2414,7 @@ static void gen_LGS(DisasContext *s, X86DecodedInsn *decode) static void gen_LODS(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[1].ot; - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz(s, ot, gen_lods); - } else { - gen_lods(s, ot); - } + gen_repz(s, ot, gen_lods); } static void gen_LOOP(DisasContext *s, X86DecodedInsn *decode) @@ -2444,7 +2435,7 @@ static void gen_LOOPE(DisasContext *s, X86DecodedInsn *decode) gen_update_cc_op(s); gen_op_add_reg_im(s, s->aflag, R_ECX, -1); gen_op_jz_ecx(s, not_taken); - gen_jcc1(s, (JCC_Z << 1), taken); /* jz taken */ + gen_jcc(s, (JCC_Z << 1), taken); /* jz taken */ gen_conditional_jump_labels(s, decode->immediate, not_taken, taken); } @@ -2456,7 +2447,7 @@ static void gen_LOOPNE(DisasContext *s, X86DecodedInsn *decode) gen_update_cc_op(s); gen_op_add_reg_im(s, s->aflag, R_ECX, -1); gen_op_jz_ecx(s, not_taken); - gen_jcc1(s, (JCC_Z << 1) | 1, taken); /* jnz taken */ + gen_jcc(s, (JCC_Z << 1) | 1, taken); /* jnz taken */ gen_conditional_jump_labels(s, decode->immediate, not_taken, taken); } @@ -2621,11 +2612,7 @@ static void gen_MOVq_dq(DisasContext *s, X86DecodedInsn *decode) static void gen_MOVS(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[2].ot; - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz(s, ot, gen_movs); - } else { - gen_movs(s, ot); - } + gen_repz(s, ot, gen_movs); } static void gen_MUL(DisasContext *s, X86DecodedInsn *decode) @@ -2787,11 +2774,7 @@ static void gen_OUTS(DisasContext *s, X86DecodedInsn *decode) } translator_io_start(&s->base); - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz(s, ot, gen_outs); - } else { - gen_outs(s, ot); - } + gen_repz(s, ot, gen_outs); } static void gen_PALIGNR(DisasContext *s, X86DecodedInsn *decode) @@ -3872,16 +3855,12 @@ static void gen_SBB(DisasContext *s, X86DecodedInsn *decode) static void gen_SCAS(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[2].ot; - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz_nz(s, ot, gen_scas); - } else { - gen_scas(s, ot); - } + gen_repz_nz(s, ot, gen_scas); } static void gen_SETcc(DisasContext *s, X86DecodedInsn *decode) { - gen_setcc1(s, decode->b & 0xf, s->T0); + gen_setcc(s, decode->b & 0xf, s->T0); } static void gen_SFENCE(DisasContext *s, X86DecodedInsn *decode) @@ -4082,11 +4061,7 @@ static void gen_STMXCSR(DisasContext *s, X86DecodedInsn *decode) static void gen_STOS(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[1].ot; - if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { - gen_repz(s, ot, gen_stos); - } else { - gen_stos(s, ot); - } + gen_repz(s, ot, gen_stos); } static void gen_SUB(DisasContext *s, X86DecodedInsn *decode) diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index d0a1e2f3c8..3d764bc138 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -204,7 +204,7 @@ static void merge_exception_flags(CPUX86State *env, uint8_t old_flags) (new_flags & float_flag_overflow ? FPUS_OE : 0) | (new_flags & float_flag_underflow ? FPUS_UE : 0) | (new_flags & float_flag_inexact ? FPUS_PE : 0) | - (new_flags & float_flag_input_denormal ? FPUS_DE : 0))); + (new_flags & float_flag_input_denormal_flushed ? FPUS_DE : 0))); } static inline floatx80 helper_fdiv(CPUX86State *env, floatx80 a, floatx80 b) @@ -1829,7 +1829,7 @@ void helper_fxtract(CPUX86State *env) int shift = clz64(temp.l.lower); temp.l.lower <<= shift; expdif = 1 - EXPBIAS - shift; - float_raise(float_flag_input_denormal, &env->fp_status); + float_raise(float_flag_input_denormal_flushed, &env->fp_status); } else { expdif = EXPD(temp) - EXPBIAS; } @@ -3258,7 +3258,7 @@ void update_mxcsr_from_sse_status(CPUX86State *env) uint8_t flags = get_float_exception_flags(&env->sse_status); /* * The MXCSR denormal flag has opposite semantics to - * float_flag_input_denormal (the softfloat code sets that flag + * float_flag_input_denormal_flushed (the softfloat code sets that flag * only when flushing input denormals to zero, but SSE sets it * only when not flushing them to zero), so is not converted * here. @@ -3268,7 +3268,7 @@ void update_mxcsr_from_sse_status(CPUX86State *env) (flags & float_flag_overflow ? FPUS_OE : 0) | (flags & float_flag_underflow ? FPUS_UE : 0) | (flags & float_flag_inexact ? FPUS_PE : 0) | - (flags & float_flag_output_denormal ? FPUS_UE | FPUS_PE : + (flags & float_flag_output_denormal_flushed ? FPUS_UE | FPUS_PE : 0)); } diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index dbc9d637c4..a8935f487a 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -113,7 +113,6 @@ typedef struct DisasContext { #endif bool vex_w; /* used by AVX even on 32-bit processors */ bool jmp_opt; /* use direct block chaining for direct jumps */ - bool repz_opt; /* optimize jumps within repz instructions */ bool cc_op_dirty; CCOp cc_op; /* current CC operation */ @@ -506,17 +505,24 @@ static inline void gen_op_jmp_v(DisasContext *s, TCGv dest) s->pc_save = -1; } -static inline -void gen_op_add_reg_im(DisasContext *s, MemOp size, int reg, int32_t val) +static inline void gen_op_add_reg(DisasContext *s, MemOp size, int reg, TCGv val) { - tcg_gen_addi_tl(s->tmp0, cpu_regs[reg], val); - gen_op_mov_reg_v(s, size, reg, s->tmp0); + /* Using cpu_regs[reg] does not work for xH registers. */ + assert(size >= MO_16); + if (size == MO_16) { + TCGv temp = tcg_temp_new(); + tcg_gen_add_tl(temp, cpu_regs[reg], val); + gen_op_mov_reg_v(s, size, reg, temp); + } else { + tcg_gen_add_tl(cpu_regs[reg], cpu_regs[reg], val); + tcg_gen_ext_tl(cpu_regs[reg], cpu_regs[reg], size); + } } -static inline void gen_op_add_reg(DisasContext *s, MemOp size, int reg, TCGv val) +static inline +void gen_op_add_reg_im(DisasContext *s, MemOp size, int reg, int32_t val) { - tcg_gen_add_tl(s->tmp0, cpu_regs[reg], val); - gen_op_mov_reg_v(s, size, reg, s->tmp0); + gen_op_add_reg(s, size, reg, tcg_constant_tl(val)); } static inline void gen_op_ld_v(DisasContext *s, int idx, TCGv t0, TCGv a0) @@ -689,14 +695,6 @@ static inline void gen_string_movl_A0_EDI(DisasContext *s) gen_lea_v_seg(s, cpu_regs[R_EDI], R_ES, -1); } -static inline TCGv gen_compute_Dshift(DisasContext *s, MemOp ot) -{ - TCGv dshift = tcg_temp_new(); - tcg_gen_ld32s_tl(dshift, tcg_env, offsetof(CPUX86State, df)); - tcg_gen_shli_tl(dshift, dshift, ot); - return dshift; -}; - static TCGv gen_ext_tl(TCGv dst, TCGv src, MemOp size, bool sign) { if (size == MO_TL) { @@ -726,6 +724,46 @@ static inline void gen_op_jnz_ecx(DisasContext *s, TCGLabel *label1) gen_op_j_ecx(s, TCG_COND_NE, label1); } +static void gen_set_hflag(DisasContext *s, uint32_t mask) +{ + if ((s->flags & mask) == 0) { + TCGv_i32 t = tcg_temp_new_i32(); + tcg_gen_ld_i32(t, tcg_env, offsetof(CPUX86State, hflags)); + tcg_gen_ori_i32(t, t, mask); + tcg_gen_st_i32(t, tcg_env, offsetof(CPUX86State, hflags)); + s->flags |= mask; + } +} + +static void gen_reset_hflag(DisasContext *s, uint32_t mask) +{ + if (s->flags & mask) { + TCGv_i32 t = tcg_temp_new_i32(); + tcg_gen_ld_i32(t, tcg_env, offsetof(CPUX86State, hflags)); + tcg_gen_andi_i32(t, t, ~mask); + tcg_gen_st_i32(t, tcg_env, offsetof(CPUX86State, hflags)); + s->flags &= ~mask; + } +} + +static void gen_set_eflags(DisasContext *s, target_ulong mask) +{ + TCGv t = tcg_temp_new(); + + tcg_gen_ld_tl(t, tcg_env, offsetof(CPUX86State, eflags)); + tcg_gen_ori_tl(t, t, mask); + tcg_gen_st_tl(t, tcg_env, offsetof(CPUX86State, eflags)); +} + +static void gen_reset_eflags(DisasContext *s, target_ulong mask) +{ + TCGv t = tcg_temp_new(); + + tcg_gen_ld_tl(t, tcg_env, offsetof(CPUX86State, eflags)); + tcg_gen_andi_tl(t, t, ~mask); + tcg_gen_st_tl(t, tcg_env, offsetof(CPUX86State, eflags)); +} + static void gen_helper_in_func(MemOp ot, TCGv v, TCGv_i32 n) { switch (ot) { @@ -793,16 +831,13 @@ static bool gen_check_io(DisasContext *s, MemOp ot, TCGv_i32 port, #endif } -static void gen_movs(DisasContext *s, MemOp ot) +static void gen_movs(DisasContext *s, MemOp ot, TCGv dshift) { - TCGv dshift; - gen_string_movl_A0_ESI(s); gen_op_ld_v(s, ot, s->T0, s->A0); gen_string_movl_A0_EDI(s); gen_op_st_v(s, ot, s->T0, s->A0); - dshift = gen_compute_Dshift(s, ot); gen_op_add_reg(s, s->aflag, R_ESI, dshift); gen_op_add_reg(s, s->aflag, R_EDI, dshift); } @@ -1148,7 +1183,7 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) return cc; } -static void gen_setcc1(DisasContext *s, int b, TCGv reg) +static void gen_setcc(DisasContext *s, int b, TCGv reg) { CCPrepare cc = gen_prepare_cc(s, b, reg); @@ -1170,12 +1205,12 @@ static void gen_setcc1(DisasContext *s, int b, TCGv reg) static inline void gen_compute_eflags_c(DisasContext *s, TCGv reg) { - gen_setcc1(s, JCC_B << 1, reg); + gen_setcc(s, JCC_B << 1, reg); } /* generate a conditional jump to label 'l1' according to jump opcode value 'b'. In the fast case, T0 is guaranteed not to be used. */ -static inline void gen_jcc1_noeob(DisasContext *s, int b, TCGLabel *l1) +static inline void gen_jcc_noeob(DisasContext *s, int b, TCGLabel *l1) { CCPrepare cc = gen_prepare_cc(s, b, NULL); @@ -1190,13 +1225,14 @@ static inline void gen_jcc1_noeob(DisasContext *s, int b, TCGLabel *l1) value 'b'. In the fast case, T0 is guaranteed not to be used. One or both of the branches will call gen_jmp_rel, so ensure cc_op is clean. */ -static inline void gen_jcc1(DisasContext *s, int b, TCGLabel *l1) +static inline void gen_jcc(DisasContext *s, int b, TCGLabel *l1) { CCPrepare cc = gen_prepare_cc(s, b, NULL); /* - * Note that this must be _after_ gen_prepare_cc, because it - * can change the cc_op from CC_OP_DYNAMIC to CC_OP_EFLAGS! + * Note that this must be _after_ gen_prepare_cc, because it can change + * the cc_op to CC_OP_EFLAGS (because it's CC_OP_DYNAMIC or because + * it's cheaper to just compute the flags)! */ gen_update_cc_op(s); if (cc.use_reg2) { @@ -1206,39 +1242,22 @@ static inline void gen_jcc1(DisasContext *s, int b, TCGLabel *l1) } } -/* XXX: does not work with gdbstub "ice" single step - not a - serious problem. The caller can jump to the returned label - to stop the REP but, if the flags have changed, it has to call - gen_update_cc_op before doing so. */ -static TCGLabel *gen_jz_ecx_string(DisasContext *s) -{ - TCGLabel *l1 = gen_new_label(); - TCGLabel *l2 = gen_new_label(); - - gen_update_cc_op(s); - gen_op_jnz_ecx(s, l1); - gen_set_label(l2); - gen_jmp_rel_csize(s, 0, 1); - gen_set_label(l1); - return l2; -} - -static void gen_stos(DisasContext *s, MemOp ot) +static void gen_stos(DisasContext *s, MemOp ot, TCGv dshift) { gen_string_movl_A0_EDI(s); gen_op_st_v(s, ot, s->T0, s->A0); - gen_op_add_reg(s, s->aflag, R_EDI, gen_compute_Dshift(s, ot)); + gen_op_add_reg(s, s->aflag, R_EDI, dshift); } -static void gen_lods(DisasContext *s, MemOp ot) +static void gen_lods(DisasContext *s, MemOp ot, TCGv dshift) { gen_string_movl_A0_ESI(s); gen_op_ld_v(s, ot, s->T0, s->A0); gen_op_mov_reg_v(s, ot, R_EAX, s->T0); - gen_op_add_reg(s, s->aflag, R_ESI, gen_compute_Dshift(s, ot)); + gen_op_add_reg(s, s->aflag, R_ESI, dshift); } -static void gen_scas(DisasContext *s, MemOp ot) +static void gen_scas(DisasContext *s, MemOp ot, TCGv dshift) { gen_string_movl_A0_EDI(s); gen_op_ld_v(s, ot, s->T1, s->A0); @@ -1247,13 +1266,11 @@ static void gen_scas(DisasContext *s, MemOp ot) tcg_gen_sub_tl(cpu_cc_dst, s->T0, s->T1); set_cc_op(s, CC_OP_SUBB + ot); - gen_op_add_reg(s, s->aflag, R_EDI, gen_compute_Dshift(s, ot)); + gen_op_add_reg(s, s->aflag, R_EDI, dshift); } -static void gen_cmps(DisasContext *s, MemOp ot) +static void gen_cmps(DisasContext *s, MemOp ot, TCGv dshift) { - TCGv dshift; - gen_string_movl_A0_EDI(s); gen_op_ld_v(s, ot, s->T1, s->A0); gen_string_movl_A0_ESI(s); @@ -1263,7 +1280,6 @@ static void gen_cmps(DisasContext *s, MemOp ot) tcg_gen_sub_tl(cpu_cc_dst, s->T0, s->T1); set_cc_op(s, CC_OP_SUBB + ot); - dshift = gen_compute_Dshift(s, ot); gen_op_add_reg(s, s->aflag, R_ESI, dshift); gen_op_add_reg(s, s->aflag, R_EDI, dshift); } @@ -1282,7 +1298,7 @@ static void gen_bpt_io(DisasContext *s, TCGv_i32 t_port, int ot) } } -static void gen_ins(DisasContext *s, MemOp ot) +static void gen_ins(DisasContext *s, MemOp ot, TCGv dshift) { gen_string_movl_A0_EDI(s); /* Note: we must do this dummy write first to be restartable in @@ -1293,11 +1309,11 @@ static void gen_ins(DisasContext *s, MemOp ot) tcg_gen_andi_i32(s->tmp2_i32, s->tmp2_i32, 0xffff); gen_helper_in_func(ot, s->T0, s->tmp2_i32); gen_op_st_v(s, ot, s->T0, s->A0); - gen_op_add_reg(s, s->aflag, R_EDI, gen_compute_Dshift(s, ot)); + gen_op_add_reg(s, s->aflag, R_EDI, dshift); gen_bpt_io(s, s->tmp2_i32, ot); } -static void gen_outs(DisasContext *s, MemOp ot) +static void gen_outs(DisasContext *s, MemOp ot, TCGv dshift) { gen_string_movl_A0_ESI(s); gen_op_ld_v(s, ot, s->T0, s->A0); @@ -1306,47 +1322,154 @@ static void gen_outs(DisasContext *s, MemOp ot) tcg_gen_andi_i32(s->tmp2_i32, s->tmp2_i32, 0xffff); tcg_gen_trunc_tl_i32(s->tmp3_i32, s->T0); gen_helper_out_func(ot, s->tmp2_i32, s->tmp3_i32); - gen_op_add_reg(s, s->aflag, R_ESI, gen_compute_Dshift(s, ot)); + gen_op_add_reg(s, s->aflag, R_ESI, dshift); gen_bpt_io(s, s->tmp2_i32, ot); } -/* Generate jumps to current or next instruction */ -static void gen_repz(DisasContext *s, MemOp ot, - void (*fn)(DisasContext *s, MemOp ot)) +#define REP_MAX 65535 + +static void do_gen_rep(DisasContext *s, MemOp ot, TCGv dshift, + void (*fn)(DisasContext *s, MemOp ot, TCGv dshift), + bool is_repz_nz) { - TCGLabel *l2; - l2 = gen_jz_ecx_string(s); - fn(s, ot); - gen_op_add_reg_im(s, s->aflag, R_ECX, -1); + TCGLabel *last = gen_new_label(); + TCGLabel *loop = gen_new_label(); + TCGLabel *done = gen_new_label(); + + target_ulong cx_mask = MAKE_64BIT_MASK(0, 8 << s->aflag); + TCGv cx_next = tcg_temp_new(); + /* - * A loop would cause two single step exceptions if ECX = 1 - * before rep string_insn + * Check if we must translate a single iteration only. Normally, HF_RF_MASK + * would also limit translation blocks to one instruction, so that gen_eob + * can reset the flag; here however RF is set throughout the repetition, so + * we can plow through until CX/ECX/RCX is zero. */ - if (s->repz_opt) { - gen_op_jz_ecx(s, l2); + bool can_loop = + (!(tb_cflags(s->base.tb) & (CF_USE_ICOUNT | CF_SINGLE_STEP)) + && !(s->flags & (HF_TF_MASK | HF_INHIBIT_IRQ_MASK))); + bool had_rf = s->flags & HF_RF_MASK; + + /* + * Even if EFLAGS.RF was set on entry (such as if we're on the second or + * later iteration and an exception or interrupt happened), force gen_eob() + * not to clear the flag. We do that ourselves after the last iteration. + */ + s->flags &= ~HF_RF_MASK; + + /* + * For CMPS/SCAS, the CC_OP after a memory fault could come from either + * the previous instruction or the string instruction; but because we + * arrange to keep CC_OP up to date all the time, just mark the whole + * insn as CC_OP_DYNAMIC. + * + * It's not a problem to do this even for instructions that do not + * modify the flags, so do it unconditionally. + */ + gen_update_cc_op(s); + tcg_set_insn_start_param(s->base.insn_start, 1, CC_OP_DYNAMIC); + + /* Any iteration at all? */ + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cpu_regs[R_ECX], cx_mask, done); + + /* + * From now on we operate on the value of CX/ECX/RCX that will be written + * back, which is stored in cx_next. There can be no carry, so we can zero + * extend here if needed and not do any expensive deposit operations later. + */ + tcg_gen_subi_tl(cx_next, cpu_regs[R_ECX], 1); +#ifdef TARGET_X86_64 + if (s->aflag == MO_32) { + tcg_gen_ext32u_tl(cx_next, cx_next); + cx_mask = ~0; } - gen_jmp_rel_csize(s, -cur_insn_len(s), 0); -} +#endif -static void gen_repz_nz(DisasContext *s, MemOp ot, - void (*fn)(DisasContext *s, MemOp ot)) -{ - TCGLabel *l2; - int nz = (s->prefix & PREFIX_REPNZ) ? 1 : 0; + /* + * The last iteration is handled outside the loop, so that cx_next + * can never underflow. + */ + if (can_loop) { + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cx_next, cx_mask, last); + } - l2 = gen_jz_ecx_string(s); - fn(s, ot); - gen_op_add_reg_im(s, s->aflag, R_ECX, -1); - gen_jcc1(s, (JCC_Z << 1) | (nz ^ 1), l2); - if (s->repz_opt) { - gen_op_jz_ecx(s, l2); + gen_set_label(loop); + fn(s, ot, dshift); + tcg_gen_mov_tl(cpu_regs[R_ECX], cx_next); + gen_update_cc_op(s); + + /* Leave if REP condition fails. */ + if (is_repz_nz) { + int nz = (s->prefix & PREFIX_REPNZ) ? 1 : 0; + gen_jcc_noeob(s, (JCC_Z << 1) | (nz ^ 1), done); + /* gen_prepare_eflags_z never changes cc_op. */ + assert(!s->cc_op_dirty); } + + if (can_loop) { + tcg_gen_subi_tl(cx_next, cx_next, 1); + tcg_gen_brcondi_tl(TCG_COND_TSTNE, cx_next, REP_MAX, loop); + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cx_next, cx_mask, last); + } + /* - * Only one iteration is done at a time, so the translation - * block ends unconditionally after this instruction and there - * is no control flow junction - no need to set CC_OP_DYNAMIC. + * Traps or interrupts set RF_MASK if they happen after any iteration + * but the last. Set it here before giving the main loop a chance to + * execute. (For faults, seg_helper.c sets the flag as usual). */ + if (!had_rf) { + gen_set_eflags(s, RF_MASK); + } + + /* Go to the main loop but reenter the same instruction. */ gen_jmp_rel_csize(s, -cur_insn_len(s), 0); + + if (can_loop) { + /* + * The last iteration needs no conditional jump, even if is_repz_nz, + * because the repeats are ending anyway. + */ + gen_set_label(last); + set_cc_op(s, CC_OP_DYNAMIC); + fn(s, ot, dshift); + tcg_gen_mov_tl(cpu_regs[R_ECX], cx_next); + gen_update_cc_op(s); + } + + /* CX/ECX/RCX is zero, or REPZ/REPNZ broke the repetition. */ + gen_set_label(done); + set_cc_op(s, CC_OP_DYNAMIC); + if (had_rf) { + gen_reset_eflags(s, RF_MASK); + } + gen_jmp_rel_csize(s, 0, 1); +} + +static void do_gen_string(DisasContext *s, MemOp ot, + void (*fn)(DisasContext *s, MemOp ot, TCGv dshift), + bool is_repz_nz) +{ + TCGv dshift = tcg_temp_new(); + tcg_gen_ld32s_tl(dshift, tcg_env, offsetof(CPUX86State, df)); + tcg_gen_shli_tl(dshift, dshift, ot); + + if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { + do_gen_rep(s, ot, dshift, fn, is_repz_nz); + } else { + fn(s, ot, dshift); + } +} + +static void gen_repz(DisasContext *s, MemOp ot, + void (*fn)(DisasContext *s, MemOp ot, TCGv dshift)) +{ + do_gen_string(s, ot, fn, false); +} + +static void gen_repz_nz(DisasContext *s, MemOp ot, + void (*fn)(DisasContext *s, MemOp ot, TCGv dshift)) +{ + do_gen_string(s, ot, fn, true); } static void gen_helper_fp_arith_ST0_FT0(int op) @@ -1847,15 +1970,7 @@ static void gen_conditional_jump_labels(DisasContext *s, target_long diff, gen_jmp_rel(s, s->dflag, diff, 0); } -static void gen_jcc(DisasContext *s, int b, int diff) -{ - TCGLabel *l1 = gen_new_label(); - - gen_jcc1(s, b, l1); - gen_conditional_jump_labels(s, diff, NULL, l1); -} - -static void gen_cmovcc1(DisasContext *s, int b, TCGv dest, TCGv src) +static void gen_cmovcc(DisasContext *s, int b, TCGv dest, TCGv src) { CCPrepare cc = gen_prepare_cc(s, b, NULL); @@ -2103,46 +2218,6 @@ static void gen_interrupt(DisasContext *s, uint8_t intno) s->base.is_jmp = DISAS_NORETURN; } -static void gen_set_hflag(DisasContext *s, uint32_t mask) -{ - if ((s->flags & mask) == 0) { - TCGv_i32 t = tcg_temp_new_i32(); - tcg_gen_ld_i32(t, tcg_env, offsetof(CPUX86State, hflags)); - tcg_gen_ori_i32(t, t, mask); - tcg_gen_st_i32(t, tcg_env, offsetof(CPUX86State, hflags)); - s->flags |= mask; - } -} - -static void gen_reset_hflag(DisasContext *s, uint32_t mask) -{ - if (s->flags & mask) { - TCGv_i32 t = tcg_temp_new_i32(); - tcg_gen_ld_i32(t, tcg_env, offsetof(CPUX86State, hflags)); - tcg_gen_andi_i32(t, t, ~mask); - tcg_gen_st_i32(t, tcg_env, offsetof(CPUX86State, hflags)); - s->flags &= ~mask; - } -} - -static void gen_set_eflags(DisasContext *s, target_ulong mask) -{ - TCGv t = tcg_temp_new(); - - tcg_gen_ld_tl(t, tcg_env, offsetof(CPUX86State, eflags)); - tcg_gen_ori_tl(t, t, mask); - tcg_gen_st_tl(t, tcg_env, offsetof(CPUX86State, eflags)); -} - -static void gen_reset_eflags(DisasContext *s, target_ulong mask) -{ - TCGv t = tcg_temp_new(); - - tcg_gen_ld_tl(t, tcg_env, offsetof(CPUX86State, eflags)); - tcg_gen_andi_tl(t, t, ~mask); - tcg_gen_st_tl(t, tcg_env, offsetof(CPUX86State, eflags)); -} - /* Clear BND registers during legacy branches. */ static void gen_bnd_jmp(DisasContext *s) { @@ -2177,7 +2252,7 @@ gen_eob(DisasContext *s, int mode) gen_set_hflag(s, HF_INHIBIT_IRQ_MASK); } - if (s->base.tb->flags & HF_RF_MASK) { + if (s->flags & HF_RF_MASK) { gen_reset_eflags(s, RF_MASK); } if (mode == DISAS_EOB_RECHECK_TF) { @@ -2864,7 +2939,7 @@ static void gen_x87(DisasContext *s, X86DecodedInsn *decode) } op1 = fcmov_cc[op & 3] | (((op >> 3) & 1) ^ 1); l1 = gen_new_label(); - gen_jcc1_noeob(s, op1, l1); + gen_jcc_noeob(s, op1, l1); gen_helper_fmov_ST0_STN(tcg_env, tcg_constant_i32(opreg)); gen_set_label(l1); @@ -3664,20 +3739,6 @@ static void i386_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cpu) dc->cpuid_xsave_features = env->features[FEAT_XSAVE]; dc->jmp_opt = !((cflags & CF_NO_GOTO_TB) || (flags & (HF_RF_MASK | HF_TF_MASK | HF_INHIBIT_IRQ_MASK))); - /* - * If jmp_opt, we want to handle each string instruction individually. - * For icount also disable repz optimization so that each iteration - * is accounted separately. - * - * FIXME: this is messy; it makes REP string instructions a lot less - * efficient than they should be and it gets in the way of correct - * handling of RF (interrupts or traps arriving after any iteration - * of a repeated string instruction but the last should set RF to 1). - * Perhaps it would be more efficient if REP string instructions were - * always at the beginning of the TB, or even their own TB? That - * would even allow accounting up to 64k iterations at once for icount. - */ - dc->repz_opt = !dc->jmp_opt && !(cflags & CF_USE_ICOUNT); dc->T0 = tcg_temp_new(); dc->T1 = tcg_temp_new(); diff --git a/target/m68k/fpu_helper.c b/target/m68k/fpu_helper.c index e3f4a18850..339b73ad7d 100644 --- a/target/m68k/fpu_helper.c +++ b/target/m68k/fpu_helper.c @@ -175,7 +175,7 @@ static int cpu_m68k_exceptbits_from_host(int host_bits) if (host_bits & float_flag_overflow) { target_bits |= 0x40; } - if (host_bits & (float_flag_underflow | float_flag_output_denormal)) { + if (host_bits & (float_flag_underflow | float_flag_output_denormal_flushed)) { target_bits |= 0x20; } if (host_bits & float_flag_divbyzero) { diff --git a/target/m68k/semihosting-stub.c b/target/m68k/semihosting-stub.c index d6a5965e29..dbe669cc5f 100644 --- a/target/m68k/semihosting-stub.c +++ b/target/m68k/semihosting-stub.c @@ -1,8 +1,11 @@ /* * m68k/ColdFire semihosting stub * - * SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> - * SPDX-FileCopyrightText: 2024 Linaro Ltd. + * Copyright (c) 2024 Linaro Ltd. + * + * Authors: + * Philippe Mathieu-Daudé + * * SPDX-License-Identifier: GPL-2.0-or-later */ diff --git a/target/mips/tcg/msa_helper.c b/target/mips/tcg/msa_helper.c index 1d40383ca4..ec38d9fde5 100644 --- a/target/mips/tcg/msa_helper.c +++ b/target/mips/tcg/msa_helper.c @@ -6231,7 +6231,7 @@ static inline int update_msacsr(CPUMIPSState *env, int action, int denormal) enable = GET_FP_ENABLE(env->active_tc.msacsr) | FP_UNIMPLEMENTED; /* Set Inexact (I) when flushing inputs to zero */ - if ((ieee_exception_flags & float_flag_input_denormal) && + if ((ieee_exception_flags & float_flag_input_denormal_flushed) && (env->active_tc.msacsr & MSACSR_FS_MASK) != 0) { if (action & CLEAR_IS_INEXACT) { mips_exception_flags &= ~FP_INEXACT; @@ -6241,7 +6241,7 @@ static inline int update_msacsr(CPUMIPSState *env, int action, int denormal) } /* Set Inexact (I) and Underflow (U) when flushing outputs to zero */ - if ((ieee_exception_flags & float_flag_output_denormal) && + if ((ieee_exception_flags & float_flag_output_denormal_flushed) && (env->active_tc.msacsr & MSACSR_FS_MASK) != 0) { mips_exception_flags |= FP_INEXACT; if (action & CLEAR_FS_UNDERFLOW) { diff --git a/target/mips/tcg/system/semihosting-stub.c b/target/mips/tcg/system/semihosting-stub.c index 7ae27d746f..bb1f7aae62 100644 --- a/target/mips/tcg/system/semihosting-stub.c +++ b/target/mips/tcg/system/semihosting-stub.c @@ -1,9 +1,10 @@ /* * MIPS semihosting stub * - * SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> - * SPDX-FileCopyrightText: 2024 Linaro Ltd. * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2024 Linaro Ltd. + * Authors: + * Philippe Mathieu-Daudé */ #include "qemu/osdep.h" diff --git a/target/rx/op_helper.c b/target/rx/op_helper.c index 691a12b2be..b3ed822dd1 100644 --- a/target/rx/op_helper.c +++ b/target/rx/op_helper.c @@ -99,8 +99,8 @@ static void update_fpsw(CPURXState *env, float32 ret, uintptr_t retaddr) if (xcpt & float_flag_inexact) { SET_FPSW(X); } - if ((xcpt & (float_flag_input_denormal - | float_flag_output_denormal)) + if ((xcpt & (float_flag_input_denormal_flushed + | float_flag_output_denormal_flushed)) && !FIELD_EX32(env->fpsw, FPSW, DN)) { env->fpsw = FIELD_DP32(env->fpsw, FPSW, CE, 1); } diff --git a/target/s390x/tcg/insn-data.h.inc b/target/s390x/tcg/insn-data.h.inc index e7d61cdec2..ec730ee091 100644 --- a/target/s390x/tcg/insn-data.h.inc +++ b/target/s390x/tcg/insn-data.h.inc @@ -1012,7 +1012,7 @@ D(0xb92e, KM, RRE, MSA, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_KM) D(0xb92f, KMC, RRE, MSA, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_KMC) D(0xb929, KMA, RRF_b, MSA8, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_KMA) - D(0xb93c, PPNO, RRE, MSA5, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_PPNO) + E(0xb93c, PPNO, RRE, MSA5, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_PPNO, IF_IO) D(0xb93e, KIMD, RRE, MSA, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_KIMD) D(0xb93f, KLMD, RRE, MSA, 0, 0, 0, 0, msa, 0, S390_FEAT_TYPE_KLMD) diff --git a/target/s390x/tcg/mem_helper.c b/target/s390x/tcg/mem_helper.c index 32717acb7d..c6ab2901e5 100644 --- a/target/s390x/tcg/mem_helper.c +++ b/target/s390x/tcg/mem_helper.c @@ -149,7 +149,7 @@ static inline int s390_probe_access(CPUArchState *env, target_ulong addr, int mmu_idx, bool nonfault, void **phost, uintptr_t ra) { - int flags = probe_access_flags(env, addr, 0, access_type, mmu_idx, + int flags = probe_access_flags(env, addr, size, access_type, mmu_idx, nonfault, phost, ra); if (unlikely(flags & TLB_INVALID_MASK)) { diff --git a/target/tricore/fpu_helper.c b/target/tricore/fpu_helper.c index 5d38aea143..1b72dcc5f5 100644 --- a/target/tricore/fpu_helper.c +++ b/target/tricore/fpu_helper.c @@ -43,7 +43,7 @@ static inline uint8_t f_get_excp_flags(CPUTriCoreState *env) & (float_flag_invalid | float_flag_overflow | float_flag_underflow - | float_flag_output_denormal + | float_flag_output_denormal_flushed | float_flag_divbyzero | float_flag_inexact); } @@ -99,7 +99,7 @@ static void f_update_psw_flags(CPUTriCoreState *env, uint8_t flags) some_excp = 1; } - if (flags & float_flag_underflow || flags & float_flag_output_denormal) { + if (flags & float_flag_underflow || flags & float_flag_output_denormal_flushed) { env->FPU_FU = 1 << 31; some_excp = 1; } @@ -109,7 +109,7 @@ static void f_update_psw_flags(CPUTriCoreState *env, uint8_t flags) some_excp = 1; } - if (flags & float_flag_inexact || flags & float_flag_output_denormal) { + if (flags & float_flag_inexact || flags & float_flag_output_denormal_flushed) { env->PSW |= 1 << 26; some_excp = 1; } diff --git a/tests/avocado/migration.py b/tests/avocado/migration.py deleted file mode 100644 index be6234b3c2..0000000000 --- a/tests/avocado/migration.py +++ /dev/null @@ -1,135 +0,0 @@ -# Migration test -# -# Copyright (c) 2019 Red Hat, Inc. -# -# Authors: -# Cleber Rosa <crosa@redhat.com> -# Caio Carrara <ccarrara@redhat.com> -# -# This work is licensed under the terms of the GNU GPL, version 2 or -# later. See the COPYING file in the top-level directory. - - -import tempfile -import os - -from avocado_qemu import QemuSystemTest -from avocado import skipUnless - -from avocado.utils.network import ports -from avocado.utils import wait -from avocado.utils.path import find_command - - -class MigrationTest(QemuSystemTest): - """ - :avocado: tags=migration - """ - - timeout = 10 - - @staticmethod - def migration_finished(vm): - return vm.cmd('query-migrate')['status'] in ('completed', 'failed') - - def assert_migration(self, src_vm, dst_vm): - wait.wait_for(self.migration_finished, - timeout=self.timeout, - step=0.1, - args=(src_vm,)) - wait.wait_for(self.migration_finished, - timeout=self.timeout, - step=0.1, - args=(dst_vm,)) - self.assertEqual(src_vm.cmd('query-migrate')['status'], 'completed') - self.assertEqual(dst_vm.cmd('query-migrate')['status'], 'completed') - self.assertEqual(dst_vm.cmd('query-status')['status'], 'running') - self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate') - - def do_migrate(self, dest_uri, src_uri=None): - dest_vm = self.get_vm('-incoming', dest_uri) - dest_vm.add_args('-nodefaults') - dest_vm.launch() - if src_uri is None: - src_uri = dest_uri - source_vm = self.get_vm() - source_vm.add_args('-nodefaults') - source_vm.launch() - source_vm.qmp('migrate', uri=src_uri) - self.assert_migration(source_vm, dest_vm) - - def _get_free_port(self): - port = ports.find_free_port() - if port is None: - self.cancel('Failed to find a free port') - return port - - def migration_with_tcp_localhost(self): - dest_uri = 'tcp:localhost:%u' % self._get_free_port() - self.do_migrate(dest_uri) - - def migration_with_unix(self): - with tempfile.TemporaryDirectory(prefix='socket_') as socket_path: - dest_uri = 'unix:%s/qemu-test.sock' % socket_path - self.do_migrate(dest_uri) - - @skipUnless(find_command('nc', default=False), "'nc' command not found") - def migration_with_exec(self): - """The test works for both netcat-traditional and netcat-openbsd packages.""" - free_port = self._get_free_port() - dest_uri = 'exec:nc -l localhost %u' % free_port - src_uri = 'exec:nc localhost %u' % free_port - self.do_migrate(dest_uri, src_uri) - - -@skipUnless('aarch64' in os.uname()[4], "host != target") -class Aarch64(MigrationTest): - """ - :avocado: tags=arch:aarch64 - :avocado: tags=machine:virt - :avocado: tags=cpu:max - """ - - def test_migration_with_tcp_localhost(self): - self.migration_with_tcp_localhost() - - def test_migration_with_unix(self): - self.migration_with_unix() - - def test_migration_with_exec(self): - self.migration_with_exec() - - -@skipUnless('x86_64' in os.uname()[4], "host != target") -class X86_64(MigrationTest): - """ - :avocado: tags=arch:x86_64 - :avocado: tags=machine:pc - :avocado: tags=cpu:qemu64 - """ - - def test_migration_with_tcp_localhost(self): - self.migration_with_tcp_localhost() - - def test_migration_with_unix(self): - self.migration_with_unix() - - def test_migration_with_exec(self): - self.migration_with_exec() - - -@skipUnless('ppc64le' in os.uname()[4], "host != target") -class PPC64(MigrationTest): - """ - :avocado: tags=arch:ppc64 - :avocado: tags=machine:pseries - """ - - def test_migration_with_tcp_localhost(self): - self.migration_with_tcp_localhost() - - def test_migration_with_unix(self): - self.migration_with_unix() - - def test_migration_with_exec(self): - self.migration_with_exec() diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index e22c200a36..b9b54a8793 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -144,51 +144,6 @@ class ReplayKernelNormal(ReplayKernelBase): self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - def test_mips_malta(self): - """ - :avocado: tags=arch:mips - :avocado: tags=machine:malta - :avocado: tags=endian:big - """ - deb_url = ('http://snapshot.debian.org/archive/debian/' - '20130217T032700Z/pool/main/l/linux-2.6/' - 'linux-image-2.6.32-5-4kc-malta_2.6.32-48_mips.deb') - deb_hash = 'a8cfc28ad8f45f54811fc6cf74fc43ffcfe0ba04' - deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) - kernel_path = self.extract_from_deb(deb_path, - '/boot/vmlinux-2.6.32-5-4kc-malta') - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' - console_pattern = 'Kernel command line: %s' % kernel_command_line - - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - - def test_mips64el_malta(self): - """ - This test requires the ar tool to extract "data.tar.gz" from - the Debian package. - - The kernel can be rebuilt using this Debian kernel source [1] and - following the instructions on [2]. - - [1] http://snapshot.debian.org/package/linux-2.6/2.6.32-48/ - #linux-source-2.6.32_2.6.32-48 - [2] https://kernel-team.pages.debian.net/kernel-handbook/ - ch-common-tasks.html#s-common-official - - :avocado: tags=arch:mips64el - :avocado: tags=machine:malta - """ - deb_url = ('http://snapshot.debian.org/archive/debian/' - '20130217T032700Z/pool/main/l/linux-2.6/' - 'linux-image-2.6.32-5-5kc-malta_2.6.32-48_mipsel.deb') - deb_hash = '1aaec92083bf22fda31e0d27fa8d9a388e5fc3d5' - deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) - kernel_path = self.extract_from_deb(deb_path, - '/boot/vmlinux-2.6.32-5-5kc-malta') - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' - console_pattern = 'Kernel command line: %s' % kernel_command_line - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - def test_aarch64_virt(self): """ :avocado: tags=arch:aarch64 @@ -455,123 +410,3 @@ class ReplayKernelNormal(ReplayKernelBase): '/qac-best-of-multiarch/download/day02.tar.xz') file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'santas-sleigh-ride.elf') - -@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') -class ReplayKernelSlow(ReplayKernelBase): - # Override the timeout, because this kernel includes an inner - # loop which is executed with TB recompilings during replay, - # making it very slow. - timeout = 180 - - def test_mips_malta_cpio(self): - """ - :avocado: tags=arch:mips - :avocado: tags=machine:malta - :avocado: tags=endian:big - :avocado: tags=slowness:high - """ - deb_url = ('http://snapshot.debian.org/archive/debian/' - '20160601T041800Z/pool/main/l/linux/' - 'linux-image-4.5.0-2-4kc-malta_4.5.5-1_mips.deb') - deb_hash = 'a3c84f3e88b54e06107d65a410d1d1e8e0f340f8' - deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) - kernel_path = self.extract_from_deb(deb_path, - '/boot/vmlinux-4.5.0-2-4kc-malta') - initrd_url = ('https://github.com/groeck/linux-build-test/raw/' - '8584a59ed9e5eb5ee7ca91f6d74bbb06619205b8/rootfs/' - 'mips/rootfs.cpio.gz') - initrd_hash = 'bf806e17009360a866bf537f6de66590de349a99' - initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) - initrd_path = self.workdir + "rootfs.cpio" - archive.gzip_uncompress(initrd_path_gz, initrd_path) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyS0 console=tty ' - 'rdinit=/sbin/init noreboot') - console_pattern = 'Boot successful.' - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, - args=('-initrd', initrd_path)) - - @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') - def test_mips64el_malta_5KEc_cpio(self): - """ - :avocado: tags=arch:mips64el - :avocado: tags=machine:malta - :avocado: tags=endian:little - :avocado: tags=slowness:high - :avocado: tags=cpu:5KEc - """ - kernel_url = ('https://github.com/philmd/qemu-testing-blob/' - 'raw/9ad2df38/mips/malta/mips64el/' - 'vmlinux-3.19.3.mtoman.20150408') - kernel_hash = '00d1d268fb9f7d8beda1de6bebcc46e884d71754' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - initrd_url = ('https://github.com/groeck/linux-build-test/' - 'raw/8584a59e/rootfs/' - 'mipsel64/rootfs.mipsel64r1.cpio.gz') - initrd_hash = '1dbb8a396e916847325284dbe2151167' - initrd_path_gz = self.fetch_asset(initrd_url, algorithm='md5', - asset_hash=initrd_hash) - initrd_path = self.workdir + "rootfs.cpio" - archive.gzip_uncompress(initrd_path_gz, initrd_path) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyS0 console=tty ' - 'rdinit=/sbin/init noreboot') - console_pattern = 'Boot successful.' - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, - args=('-initrd', initrd_path)) - - def do_test_mips_malta32el_nanomips(self, kernel_path_xz): - kernel_path = self.workdir + "kernel" - with lzma.open(kernel_path_xz, 'rb') as f_in: - with open(kernel_path, 'wb') as f_out: - shutil.copyfileobj(f_in, f_out) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'mem=256m@@0x0 ' - 'console=ttyS0') - console_pattern = 'Kernel command line: %s' % kernel_command_line - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - - def test_mips_malta32el_nanomips_4k(self): - """ - :avocado: tags=arch:mipsel - :avocado: tags=machine:malta - :avocado: tags=endian:little - :avocado: tags=cpu:I7200 - """ - kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' - 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' - 'generic_nano32r6el_page4k.xz') - kernel_hash = '477456aafd2a0f1ddc9482727f20fe9575565dd6' - kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - self.do_test_mips_malta32el_nanomips(kernel_path_xz) - - def test_mips_malta32el_nanomips_16k_up(self): - """ - :avocado: tags=arch:mipsel - :avocado: tags=machine:malta - :avocado: tags=endian:little - :avocado: tags=cpu:I7200 - """ - kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' - 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' - 'generic_nano32r6el_page16k_up.xz') - kernel_hash = 'e882868f944c71c816e832e2303b7874d044a7bc' - kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - self.do_test_mips_malta32el_nanomips(kernel_path_xz) - - def test_mips_malta32el_nanomips_64k_dbg(self): - """ - :avocado: tags=arch:mipsel - :avocado: tags=machine:malta - :avocado: tags=endian:little - :avocado: tags=cpu:I7200 - """ - kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' - 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' - 'generic_nano32r6el_page64k_dbg.xz') - kernel_hash = '18d1c68f2e23429e266ca39ba5349ccd0aeb7180' - kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - self.do_test_mips_malta32el_nanomips(kernel_path_xz) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index b7719ab85f..3f085bfbca 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -35,12 +35,14 @@ test_timeouts = { 'arm_sx1' : 360, 'intel_iommu': 300, 'mips_malta' : 120, + 'mipsel_replay' : 480, 'netdev_ethtool' : 180, 'ppc_40p' : 240, 'ppc64_hv' : 1000, 'ppc64_powernv' : 480, 'ppc64_pseries' : 480, 'ppc64_tuxrun' : 420, + 'ppc64_mac99' : 120, 'riscv64_tuxrun' : 120, 's390x_ccw_virtio' : 420, 'sh4_tuxrun' : 240, @@ -59,6 +61,10 @@ tests_generic_linuxuser = [ tests_generic_bsduser = [ ] +tests_aarch64_system_quick = [ + 'migration', +] + tests_aarch64_system_thorough = [ 'aarch64_aspeed', 'aarch64_raspi3', @@ -68,16 +74,25 @@ tests_aarch64_system_thorough = [ 'aarch64_sbsaref', 'aarch64_sbsaref_alpine', 'aarch64_sbsaref_freebsd', + 'aarch64_tcg_plugins', 'aarch64_tuxrun', 'aarch64_virt', 'aarch64_xlnx_versal', 'multiprocess', ] +tests_alpha_system_quick = [ + 'migration', +] + tests_alpha_system_thorough = [ 'alpha_clipper', ] +tests_arm_system_quick = [ + 'migration', +] + tests_arm_system_thorough = [ 'arm_aspeed_ast1030', 'arm_aspeed_palmetto', @@ -91,6 +106,7 @@ tests_arm_system_thorough = [ 'arm_cubieboard', 'arm_emcraft_sf2', 'arm_integratorcp', + 'arm_microbit', 'arm_orangepi', 'arm_quanta_gsj', 'arm_raspi2', @@ -113,6 +129,10 @@ tests_hppa_system_quick = [ 'hppa_seabios', ] +tests_i386_system_quick = [ + 'migration', +] + tests_i386_system_thorough = [ 'i386_tuxrun', ] @@ -138,11 +158,13 @@ tests_microblazeel_system_thorough = [ tests_mips_system_thorough = [ 'mips_malta', + 'mips_replay', 'mips_tuxrun', ] tests_mipsel_system_thorough = [ 'mipsel_malta', + 'mipsel_replay', 'mipsel_tuxrun', ] @@ -154,6 +176,7 @@ tests_mips64el_system_thorough = [ 'mips64el_fuloong2e', 'mips64el_loongson3v', 'mips64el_malta', + 'mips64el_replay', 'mips64el_tuxrun', ] @@ -162,6 +185,7 @@ tests_or1k_system_thorough = [ ] tests_ppc_system_quick = [ + 'migration', 'ppc_74xx', ] @@ -176,15 +200,21 @@ tests_ppc_system_thorough = [ 'ppc_virtex_ml507', ] +tests_ppc64_system_quick = [ + 'migration', +] + tests_ppc64_system_thorough = [ 'ppc64_e500', 'ppc64_hv', 'ppc64_powernv', 'ppc64_pseries', 'ppc64_tuxrun', + 'ppc64_mac99', ] tests_riscv32_system_quick = [ + 'migration', 'riscv_opensbi', ] @@ -193,6 +223,7 @@ tests_riscv32_system_thorough = [ ] tests_riscv64_system_quick = [ + 'migration', 'riscv_opensbi', ] @@ -219,10 +250,18 @@ tests_sh4eb_system_thorough = [ 'sh4eb_r2d', ] +tests_sparc_system_quick = [ + 'migration', +] + tests_sparc_system_thorough = [ 'sparc_sun4m', ] +tests_sparc64_system_quick = [ + 'migration', +] + tests_sparc64_system_thorough = [ 'sparc64_sun4u', 'sparc64_tuxrun', @@ -231,6 +270,7 @@ tests_sparc64_system_thorough = [ tests_x86_64_system_quick = [ 'cpu_queries', 'mem_addr_space', + 'migration', 'pc_cpu_hotplug_props', 'virtio_version', 'x86_cpu_model_versions', diff --git a/tests/functional/qemu_test/__init__.py b/tests/functional/qemu_test/__init__.py index da1830286d..5c972843a6 100644 --- a/tests/functional/qemu_test/__init__.py +++ b/tests/functional/qemu_test/__init__.py @@ -14,7 +14,7 @@ from .cmd import is_readable_executable_file, \ from .testcase import QemuBaseTest, QemuUserTest, QemuSystemTest from .linuxkernel import LinuxKernelTest from .decorators import skipIfMissingCommands, skipIfNotMachine, \ - skipFlakyTest, skipUntrustedTest, skipBigDataTest, \ + skipFlakyTest, skipUntrustedTest, skipBigDataTest, skipSlowTest, \ skipIfMissingImports from .archive import archive_extract from .uncompress import uncompress diff --git a/tests/functional/qemu_test/decorators.py b/tests/functional/qemu_test/decorators.py index df088bc090..1651eb739a 100644 --- a/tests/functional/qemu_test/decorators.py +++ b/tests/functional/qemu_test/decorators.py @@ -2,6 +2,7 @@ # # Decorators useful in functional tests +import importlib import os import platform from unittest import skipUnless @@ -16,15 +17,14 @@ Example: @skipIfMissingCommands("mkisofs", "losetup") ''' def skipIfMissingCommands(*args): - def has_cmds(cmdlist): - for cmd in cmdlist: - if not which(cmd): - return False - return True - - return skipUnless(lambda: has_cmds(args), - 'required command(s) "%s" not installed' % - ", ".join(args)) + has_cmds = True + for cmd in args: + if not which(cmd): + has_cmds = False + break + + return skipUnless(has_cmds, 'required command(s) "%s" not installed' % + ", ".join(args)) ''' Decorator to skip execution of a test if the current @@ -35,9 +35,9 @@ Example @skipIfNotMachine("x86_64", "aarch64") ''' def skipIfNotMachine(*args): - return skipUnless(lambda: platform.machine() in args, - 'not running on one of the required machine(s) "%s"' % - ", ".join(args)) + return skipUnless(platform.machine() in args, + 'not running on one of the required machine(s) "%s"' % + ", ".join(args)) ''' Decorator to skip execution of flaky tests, unless @@ -87,6 +87,20 @@ def skipBigDataTest(): 'Test requires large host storage space') ''' +Decorator to skip execution of tests which have a really long +runtime (and might e.g. time out if QEMU has been compiled with +debugging enabled) unless the $QEMU_TEST_ALLOW_SLOW +environment variable is set + +Example: + + @skipSlowTest() +''' +def skipSlowTest(): + return skipUnless(os.getenv('QEMU_TEST_ALLOW_SLOW'), + 'Test has a very long runtime and might time out') + +''' Decorator to skip execution of a test if the list of python imports is not available. Example: @@ -94,14 +108,13 @@ Example: @skipIfMissingImports("numpy", "cv2") ''' def skipIfMissingImports(*args): - def has_imports(importlist): - for impname in importlist: - try: - import impname - except ImportError: - return False - return True - - return skipUnless(lambda: has_imports(args), - 'required import(s) "%s" not installed' % - ", ".join(args)) + has_imports = True + for impname in args: + try: + importlib.import_module(impname) + except ImportError: + has_imports = False + break + + return skipUnless(has_imports, 'required import(s) "%s" not installed' % + ", ".join(args)) diff --git a/tests/functional/replay_kernel.py b/tests/functional/replay_kernel.py new file mode 100644 index 0000000000..8e8ac7d052 --- /dev/null +++ b/tests/functional/replay_kernel.py @@ -0,0 +1,84 @@ +# Record/replay test that boots a Linux kernel +# +# Copyright (c) 2020 ISP RAS +# +# Author: +# Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +import os +import logging +import time +import subprocess + +from qemu_test.linuxkernel import LinuxKernelTest + +class ReplayKernelBase(LinuxKernelTest): + """ + Boots a Linux kernel in record mode and checks that the console + is operational and the kernel command line is properly passed + from QEMU to the kernel. + Then replays the same scenario and verifies, that QEMU correctly + terminates. + """ + + timeout = 180 + REPLAY_KERNEL_COMMAND_LINE = 'printk.time=1 panic=-1 ' + + def run_vm(self, kernel_path, kernel_command_line, console_pattern, + record, shift, args, replay_path): + # icount requires TCG to be available + self.require_accelerator('tcg') + + logger = logging.getLogger('replay') + start_time = time.time() + vm = self.get_vm() + vm.set_console() + if record: + logger.info('recording the execution...') + mode = 'record' + else: + logger.info('replaying the execution...') + mode = 'replay' + vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s' % + (shift, mode, replay_path), + '-kernel', kernel_path, + '-append', kernel_command_line, + '-net', 'none', + '-no-reboot') + if args: + vm.add_args(*args) + vm.launch() + self.wait_for_console_pattern(console_pattern, vm) + if record: + vm.shutdown() + logger.info('finished the recording with log size %s bytes' + % os.path.getsize(replay_path)) + self.run_replay_dump(replay_path) + logger.info('successfully tested replay-dump.py') + else: + vm.wait() + logger.info('successfully finished the replay') + elapsed = time.time() - start_time + logger.info('elapsed time %.2f sec' % elapsed) + return elapsed + + def run_replay_dump(self, replay_path): + try: + subprocess.check_call(["./scripts/replay-dump.py", + "-f", replay_path], + stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError: + self.fail('replay-dump.py failed') + + def run_rr(self, kernel_path, kernel_command_line, console_pattern, + shift=7, args=None): + replay_path = os.path.join(self.workdir, 'replay.bin') + t1 = self.run_vm(kernel_path, kernel_command_line, console_pattern, + True, shift, args, replay_path) + t2 = self.run_vm(kernel_path, kernel_command_line, console_pattern, + False, shift, args, replay_path) + logger = logging.getLogger('replay') + logger.info('replay overhead {:.2%}'.format(t2 / t1 - 1)) diff --git a/tests/functional/test_aarch64_sbsaref.py b/tests/functional/test_aarch64_sbsaref.py index 99cfb6f29a..e6a55aecfa 100755 --- a/tests/functional/test_aarch64_sbsaref.py +++ b/tests/functional/test_aarch64_sbsaref.py @@ -2,9 +2,11 @@ # # Functional test that boots a kernel and checks the console # -# SPDX-FileCopyrightText: 2023-2024 Linaro Ltd. -# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> -# SPDX-FileContributor: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> +# Copyright (c) 2023-2024 Linaro Ltd. +# +# Authors: +# Philippe Mathieu-Daudé +# Marcin Juszkiewicz # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/tests/functional/test_aarch64_sbsaref_alpine.py b/tests/functional/test_aarch64_sbsaref_alpine.py index 6dbc90f30e..c660cc7a40 100755 --- a/tests/functional/test_aarch64_sbsaref_alpine.py +++ b/tests/functional/test_aarch64_sbsaref_alpine.py @@ -2,15 +2,17 @@ # # Functional test that boots a kernel and checks the console # -# SPDX-FileCopyrightText: 2023-2024 Linaro Ltd. -# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> -# SPDX-FileContributor: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> +# Copyright (c) 2023-2024 Linaro Ltd. +# +# Authors: +# Philippe Mathieu-Daudé +# Marcin Juszkiewicz # # SPDX-License-Identifier: GPL-2.0-or-later import os -from qemu_test import QemuSystemTest, Asset +from qemu_test import QemuSystemTest, Asset, skipSlowTest from qemu_test import wait_for_console_pattern from unittest import skipUnless from test_aarch64_sbsaref import fetch_firmware @@ -53,8 +55,7 @@ class Aarch64SbsarefAlpine(QemuSystemTest): def test_sbsaref_alpine_linux_max_pauth_impdef(self): self.boot_alpine_linux("max,pauth-impdef=on") - @skipUnless(os.getenv('QEMU_TEST_TIMEOUT_EXPECTED'), - 'Test might timeout due to PAuth emulation') + @skipSlowTest() # Test might timeout due to PAuth emulation def test_sbsaref_alpine_linux_max(self): self.boot_alpine_linux("max") diff --git a/tests/functional/test_aarch64_sbsaref_freebsd.py b/tests/functional/test_aarch64_sbsaref_freebsd.py index 77ba2ba1da..bd6728dc70 100755 --- a/tests/functional/test_aarch64_sbsaref_freebsd.py +++ b/tests/functional/test_aarch64_sbsaref_freebsd.py @@ -2,17 +2,18 @@ # # Functional test that boots a kernel and checks the console # -# SPDX-FileCopyrightText: 2023-2024 Linaro Ltd. -# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> -# SPDX-FileContributor: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> +# Copyright (c) 2023-2024 Linaro Ltd. +# +# Authors: +# Philippe Mathieu-Daudé +# Marcin Juszkiewicz # # SPDX-License-Identifier: GPL-2.0-or-later import os -from qemu_test import QemuSystemTest, Asset +from qemu_test import QemuSystemTest, Asset, skipSlowTest from qemu_test import wait_for_console_pattern -from unittest import skipUnless from test_aarch64_sbsaref import fetch_firmware @@ -50,13 +51,11 @@ class Aarch64SbsarefFreeBSD(QemuSystemTest): def test_sbsaref_freebsd14_max_pauth_off(self): self.boot_freebsd14("max,pauth=off") - @skipUnless(os.getenv('QEMU_TEST_TIMEOUT_EXPECTED'), - 'Test might timeout due to PAuth emulation') + @skipSlowTest() # Test might timeout due to PAuth emulation def test_sbsaref_freebsd14_max_pauth_impdef(self): self.boot_freebsd14("max,pauth-impdef=on") - @skipUnless(os.getenv('QEMU_TEST_TIMEOUT_EXPECTED'), - 'Test might timeout due to PAuth emulation') + @skipSlowTest() # Test might timeout due to PAuth emulation def test_sbsaref_freebsd14_max(self): self.boot_freebsd14("max") diff --git a/tests/functional/test_aarch64_tcg_plugins.py b/tests/functional/test_aarch64_tcg_plugins.py index 01660eb090..7e8beacc83 100755 --- a/tests/functional/test_aarch64_tcg_plugins.py +++ b/tests/functional/test_aarch64_tcg_plugins.py @@ -15,6 +15,7 @@ import tempfile import mmap import re +from qemu.machine.machine import VMLaunchFailure from qemu_test import LinuxKernelTest, Asset @@ -43,10 +44,12 @@ class PluginKernelBase(LinuxKernelTest): try: vm.launch() - except: - # TODO: probably fails because plugins not enabled but we - # can't currently probe for the feature. - self.cancel("TCG Plugins not enabled?") + except VMLaunchFailure as excp: + if "plugin interface not enabled in this build" in excp.output: + self.skipTest("TCG plugins not enabled") + else: + self.log.info(f"unhandled launch failure: {excp.output}") + raise excp self.wait_for_console_pattern(console_pattern, vm) # ensure logs are flushed @@ -65,7 +68,7 @@ class PluginKernelNormal(PluginKernelBase): kernel_path = self.ASSET_KERNEL.fetch() kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyAMA0') - console_pattern = 'Kernel panic - not syncing: VFS:' + console_pattern = 'Please append a correct "root=" boot option' plugin_log = tempfile.NamedTemporaryFile(mode="r+t", prefix="plugin", suffix=".log") @@ -91,7 +94,7 @@ class PluginKernelNormal(PluginKernelBase): kernel_path = self.ASSET_KERNEL.fetch() kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyAMA0') - console_pattern = 'Kernel panic - not syncing: VFS:' + console_pattern = 'Please append a correct "root=" boot option' plugin_log = tempfile.NamedTemporaryFile(mode="r+t", prefix="plugin", suffix=".log") diff --git a/tests/functional/test_arm_microbit.py b/tests/functional/test_arm_microbit.py new file mode 100755 index 0000000000..68ea4e73d6 --- /dev/null +++ b/tests/functional/test_arm_microbit.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright 2025, The QEMU Project Developers. +# +# A functional test that runs MicroPython on the arm microbit machine. + +from qemu_test import QemuSystemTest, Asset, exec_command_and_wait_for_pattern +from qemu_test import wait_for_console_pattern + + +class MicrobitMachine(QemuSystemTest): + + ASSET_MICRO = Asset('https://ozlabs.org/~joel/microbit-micropython.hex', + '021641f93dfb11767d4978dbb3ca7f475d1b13c69e7f4aec3382f212636bffd6') + + def test_arm_microbit(self): + self.set_machine('microbit') + + micropython = self.ASSET_MICRO.fetch() + self.vm.set_console() + self.vm.add_args('-device', f'loader,file={micropython}') + self.vm.launch() + wait_for_console_pattern(self, 'Type "help()" for more information.') + exec_command_and_wait_for_pattern(self, 'import machine as mch', '>>>') + exec_command_and_wait_for_pattern(self, 'mch.reset()', 'MicroPython') + wait_for_console_pattern(self, '>>>') + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/test_arm_quanta_gsj.py b/tests/functional/test_arm_quanta_gsj.py index 7b82e2185c..da60aeb659 100755 --- a/tests/functional/test_arm_quanta_gsj.py +++ b/tests/functional/test_arm_quanta_gsj.py @@ -7,8 +7,8 @@ import os from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern -from qemu_test import interrupt_interactive_console_until_pattern -from unittest import skipUnless +from qemu_test import interrupt_interactive_console_until_pattern, skipSlowTest + class EmcraftSf2Machine(LinuxKernelTest): @@ -32,7 +32,7 @@ class EmcraftSf2Machine(LinuxKernelTest): '20200711-gsj-qemu-0/nuvoton-npcm730-gsj.dtb'), '3249b2da787d4b9ad4e61f315b160abfceb87b5e1895a7ce898ce7f40c8d4045') - @skipUnless(os.getenv('QEMU_TEST_TIMEOUT_EXPECTED'), 'Test might timeout') + @skipSlowTest() def test_arm_quanta_gsj(self): self.set_machine('quanta-gsj') image_path = self.uncompress(self.ASSET_IMAGE, format='gz') diff --git a/tests/functional/test_migration.py b/tests/functional/test_migration.py new file mode 100755 index 0000000000..44804113cf --- /dev/null +++ b/tests/functional/test_migration.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# +# Migration test +# +# Copyright (c) 2019 Red Hat, Inc. +# +# Authors: +# Cleber Rosa <crosa@redhat.com> +# Caio Carrara <ccarrara@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + + +import tempfile +import os +import time + +from qemu_test import QemuSystemTest, skipIfMissingCommands +from qemu_test.ports import Ports + +class MigrationTest(QemuSystemTest): + + timeout = 10 + + @staticmethod + def migration_finished(vm): + return vm.cmd('query-migrate')['status'] in ('completed', 'failed') + + def assert_migration(self, src_vm, dst_vm): + + end = time.monotonic() + self.timeout + while time.monotonic() < end and not self.migration_finished(src_vm): + time.sleep(0.1) + + end = time.monotonic() + self.timeout + while time.monotonic() < end and not self.migration_finished(dst_vm): + time.sleep(0.1) + + self.assertEqual(src_vm.cmd('query-migrate')['status'], 'completed') + self.assertEqual(dst_vm.cmd('query-migrate')['status'], 'completed') + self.assertEqual(dst_vm.cmd('query-status')['status'], 'running') + self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate') + + def select_machine(self): + target_machine = { + 'aarch64': 'quanta-gsj', + 'alpha': 'clipper', + 'arm': 'npcm750-evb', + 'i386': 'isapc', + 'ppc': 'sam460ex', + 'ppc64': 'mac99', + 'riscv32': 'spike', + 'riscv64': 'virt', + 'sparc': 'SS-4', + 'sparc64': 'sun4u', + 'x86_64': 'microvm', + } + self.set_machine(target_machine[self.arch]) + + def do_migrate(self, dest_uri, src_uri=None): + self.select_machine() + dest_vm = self.get_vm('-incoming', dest_uri, name="dest-qemu") + dest_vm.add_args('-nodefaults') + dest_vm.launch() + if src_uri is None: + src_uri = dest_uri + source_vm = self.get_vm(name="source-qemu") + source_vm.add_args('-nodefaults') + source_vm.launch() + source_vm.qmp('migrate', uri=src_uri) + self.assert_migration(source_vm, dest_vm) + + def _get_free_port(self, ports): + port = ports.find_free_port() + if port is None: + self.skipTest('Failed to find a free port') + return port + + def test_migration_with_tcp_localhost(self): + with Ports() as ports: + dest_uri = 'tcp:localhost:%u' % self._get_free_port(ports) + self.do_migrate(dest_uri) + + def test_migration_with_unix(self): + with tempfile.TemporaryDirectory(prefix='socket_') as socket_path: + dest_uri = 'unix:%s/qemu-test.sock' % socket_path + self.do_migrate(dest_uri) + + @skipIfMissingCommands('nc') + def test_migration_with_exec(self): + """The test works for both netcat-traditional and netcat-openbsd packages.""" + with Ports() as ports: + free_port = self._get_free_port(ports) + dest_uri = 'exec:nc -l localhost %u' % free_port + src_uri = 'exec:nc localhost %u' % free_port + self.do_migrate(dest_uri, src_uri) + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/test_mips64el_replay.py b/tests/functional/test_mips64el_replay.py new file mode 100755 index 0000000000..4f63d7fb34 --- /dev/null +++ b/tests/functional/test_mips64el_replay.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Replay tests for the little-endian 64-bit MIPS Malta board +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import logging + +from qemu_test import Asset, exec_command_and_wait_for_pattern +from qemu_test import skipIfMissingImports, skipFlakyTest, skipUntrustedTest +from replay_kernel import ReplayKernelBase + + +class Mips64elReplay(ReplayKernelBase): + + ASSET_KERNEL_2_63_2 = Asset( + ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-5kc-malta_2.6.32-48_mipsel.deb'), + '35eb476f03be589824b0310358f1c447d85e645b88cbcd2ac02b97ef560f9f8d') + + def test_replay_mips64el_malta(self): + self.set_machine('malta') + kernel_path = self.archive_extract(self.ASSET_KERNEL_2_63_2, + member='boot/vmlinux-2.6.32-5-5kc-malta') + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + + ASSET_KERNEL_3_19_3 = Asset( + ('https://github.com/philmd/qemu-testing-blob/' + 'raw/9ad2df38/mips/malta/mips64el/' + 'vmlinux-3.19.3.mtoman.20150408'), + '8d3beb003bc66051ead98e7172139017fcf9ce2172576541c57e86418dfa5ab8') + + ASSET_CPIO_R1 = Asset( + ('https://github.com/groeck/linux-build-test/' + 'raw/8584a59e/rootfs/mipsel64/' + 'rootfs.mipsel64r1.cpio.gz'), + '75ba10cd35fb44e32948eeb26974f061b703c81c4ba2fab1ebcacf1d1bec3b61') + + @skipUntrustedTest() + def test_replay_mips64el_malta_5KEc_cpio(self): + self.set_machine('malta') + self.cpu = '5KEc' + kernel_path = self.ASSET_KERNEL_3_19_3.fetch() + initrd_path = self.uncompress(self.ASSET_CPIO_R1) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + 'rdinit=/sbin/init noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, + args=('-initrd', initrd_path)) + + +if __name__ == '__main__': + ReplayKernelBase.main() diff --git a/tests/functional/test_mips_malta.py b/tests/functional/test_mips_malta.py index 3b15038d89..eaf372255b 100755 --- a/tests/functional/test_mips_malta.py +++ b/tests/functional/test_mips_malta.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Functional tests for the little-endian 32-bit MIPS Malta board +# Functional tests for the big-endian 32-bit MIPS Malta board # # Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org> # diff --git a/tests/functional/test_mips_replay.py b/tests/functional/test_mips_replay.py new file mode 100755 index 0000000000..eda031ccad --- /dev/null +++ b/tests/functional/test_mips_replay.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Replay tests for the big-endian 32-bit MIPS Malta board +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, skipSlowTest, exec_command_and_wait_for_pattern +from replay_kernel import ReplayKernelBase + + +class MipsReplay(ReplayKernelBase): + + ASSET_KERNEL_2_63_2 = Asset( + ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-4kc-malta_2.6.32-48_mips.deb'), + '16ca524148afb0626f483163e5edf352bc1ab0e4fc7b9f9d473252762f2c7a43') + + def test_replay_mips_malta(self): + self.set_machine('malta') + kernel_path = self.archive_extract(self.ASSET_KERNEL_2_63_2, + member='boot/vmlinux-2.6.32-5-4kc-malta') + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + ASSET_KERNEL_4_5_0 = Asset( + ('http://snapshot.debian.org/archive/debian/' + '20160601T041800Z/pool/main/l/linux/' + 'linux-image-4.5.0-2-4kc-malta_4.5.5-1_mips.deb'), + '526b17d5889840888b76fc2c36a0ebde182c9b1410a3a1e68203c3b160eb2027') + + ASSET_INITRD = Asset( + ('https://github.com/groeck/linux-build-test/raw/' + '8584a59ed9e5eb5ee7ca91f6d74bbb06619205b8/rootfs/' + 'mips/rootfs.cpio.gz'), + 'dcfe3a7fe3200da3a00d176b95caaa086495eb158f2bff64afc67d7e1eb2cddc') + + @skipSlowTest() + def test_replay_mips_malta_cpio(self): + self.set_machine('malta') + kernel_path = self.archive_extract(self.ASSET_KERNEL_4_5_0, + member='boot/vmlinux-4.5.0-2-4kc-malta') + initrd_path = self.uncompress(self.ASSET_INITRD) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + 'rdinit=/sbin/init noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, + args=('-initrd', initrd_path)) + + +if __name__ == '__main__': + ReplayKernelBase.main() diff --git a/tests/functional/test_mipsel_replay.py b/tests/functional/test_mipsel_replay.py new file mode 100644 index 0000000000..0a330de43f --- /dev/null +++ b/tests/functional/test_mipsel_replay.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Replay tests for the little-endian 32-bit MIPS Malta board +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, wait_for_console_pattern, skipSlowTest +from replay_kernel import ReplayKernelBase + + +class MipselReplay(ReplayKernelBase): + + ASSET_KERNEL_4K = Asset( + ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page4k.xz'), + '019e034094ac6cf3aa77df5e130fb023ce4dbc804b04bfcc560c6403e1ae6bdb') + ASSET_KERNEL_16K = Asset( + ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page16k_up.xz'), + '3a54a10b3108c16a448dca9ea3db378733a27423befc2a45a5bdf990bd85e12c') + ASSET_KERNEL_64K = Asset( + ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page64k_dbg.xz'), + 'ce21ff4b07a981ecb8a39db2876616f5a2473eb2ab459c6f67465b9914b0c6b6') + + def do_test_replay_mips_malta32el_nanomips(self, kernel_asset): + self.set_machine('malta') + self.cpu = 'I7200' + kernel_path = self.uncompress(kernel_asset) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'mem=256m@@0x0 ' + 'console=ttyS0') + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + @skipSlowTest() + def test_replay_mips_malta32el_nanomips_4k(self): + self.do_test_replay_mips_malta32el_nanomips(self.ASSET_KERNEL_4K) + + @skipSlowTest() + def test_replay_mips_malta32el_nanomips_16k_up(self): + self.do_test_replay_mips_malta32el_nanomips(self.ASSET_KERNEL_16K) + + @skipSlowTest() + def test_replay_mips_malta32el_nanomips_64k_dbg(self): + self.do_test_replay_mips_malta32el_nanomips(self.ASSET_KERNEL_64K) + + +if __name__ == '__main__': + ReplayKernelBase.main() diff --git a/tests/functional/test_ppc64_mac99.py b/tests/functional/test_ppc64_mac99.py new file mode 100755 index 0000000000..dfd9c01371 --- /dev/null +++ b/tests/functional/test_ppc64_mac99.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Functional test that boots a mac99 machine with a PPC970 CPU +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset +from qemu_test import exec_command_and_wait_for_pattern + +class mac99Test(LinuxKernelTest): + + ASSET_BR2_MAC99_LINUX = Asset( + 'https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main/buildroot/qemu_ppc64_mac99-2023.11-8-gdcd9f0f6eb-20240105/vmlinux', + 'd59307437e4365f2cced0bbd1b04949f7397b282ef349b7cafd894d74aadfbff') + + ASSET_BR2_MAC99_ROOTFS = Asset( + 'https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main//buildroot/qemu_ppc64_mac99-2023.11-8-gdcd9f0f6eb-20240105/rootfs.ext2', + 'bbd5fd8af62f580bc4e585f326fe584e22856572633a8333178ea6d4ed4955a4') + + def test_ppc64_mac99_buildroot(self): + self.set_machine('mac99') + + linux_path = self.ASSET_BR2_MAC99_LINUX.fetch() + rootfs_path = self.ASSET_BR2_MAC99_ROOTFS.fetch() + + self.vm.set_console() + + # Note: We need '-nographic' to get a serial console + self.vm.add_args('-kernel', linux_path, + '-append', 'root=/dev/sda', + '-drive', f'file={rootfs_path},format=raw', + '-snapshot', '-nographic') + self.vm.launch() + + self.wait_for_console_pattern('>> OpenBIOS') + self.wait_for_console_pattern('Linux version') + self.wait_for_console_pattern('/init as init process') + self.wait_for_console_pattern('gem 0000:f0:0e.0 eth0: Link is up at 100 Mbps') + self.wait_for_console_pattern('buildroot login:') + exec_command_and_wait_for_pattern(self, 'root', '#') + exec_command_and_wait_for_pattern(self, 'poweroff', 'Power down') + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/functional/test_ppc_40p.py b/tests/functional/test_ppc_40p.py index 7a74e0cca7..614972a7eb 100755 --- a/tests/functional/test_ppc_40p.py +++ b/tests/functional/test_ppc_40p.py @@ -9,6 +9,7 @@ from qemu_test import QemuSystemTest, Asset from qemu_test import wait_for_console_pattern, skipUntrustedTest +from qemu_test import exec_command_and_wait_for_pattern class IbmPrep40pMachine(QemuSystemTest): @@ -72,5 +73,22 @@ class IbmPrep40pMachine(QemuSystemTest): self.vm.launch() wait_for_console_pattern(self, 'NetBSD/prep BOOT, Revision 1.9') + ASSET_40P_SANDALFOOT = Asset( + 'http://www.juneau-lug.org/zImage.initrd.sandalfoot', + '749ab02f576c6dc8f33b9fb022ecb44bf6a35a0472f2ea6a5e9956bc15933901') + + def test_openbios_and_linux(self): + self.set_machine('40p') + self.require_accelerator("tcg") + drive_path = self.ASSET_40P_SANDALFOOT.fetch() + self.vm.set_console() + self.vm.add_args('-cdrom', drive_path, + '-boot', 'd') + + self.vm.launch() + wait_for_console_pattern(self, 'Please press Enter to activate this console.') + exec_command_and_wait_for_pattern(self, '\012', '#') + exec_command_and_wait_for_pattern(self, 'uname -a', 'Linux ppc 2.4.18') + if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/qemu-iotests/194.out b/tests/qemu-iotests/194.out index 376ed1d2e6..6940e809cd 100644 --- a/tests/qemu-iotests/194.out +++ b/tests/qemu-iotests/194.out @@ -14,6 +14,7 @@ Starting migration... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} Gracefully ending the `drive-mirror` job on source... {"return": {}} diff --git a/tests/qemu-iotests/203.out b/tests/qemu-iotests/203.out index 9d4abba8c5..8e58705e51 100644 --- a/tests/qemu-iotests/203.out +++ b/tests/qemu-iotests/203.out @@ -8,4 +8,5 @@ Starting migration... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} diff --git a/tests/qemu-iotests/234.out b/tests/qemu-iotests/234.out index ac8b64350c..be3e138b58 100644 --- a/tests/qemu-iotests/234.out +++ b/tests/qemu-iotests/234.out @@ -10,6 +10,7 @@ Starting migration to B... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} @@ -27,6 +28,7 @@ Starting migration back to A... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} diff --git a/tests/qemu-iotests/262.out b/tests/qemu-iotests/262.out index b8a2d3598d..bd7706b84b 100644 --- a/tests/qemu-iotests/262.out +++ b/tests/qemu-iotests/262.out @@ -8,6 +8,7 @@ Starting migration to B... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} diff --git a/tests/qemu-iotests/280.out b/tests/qemu-iotests/280.out index 546dbb4a68..37411144ca 100644 --- a/tests/qemu-iotests/280.out +++ b/tests/qemu-iotests/280.out @@ -7,6 +7,7 @@ Enabling migration QMP events on VM... {"return": {}} {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"status": "device"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} VM is now stopped: diff --git a/tests/qtest/libqos/libqos.c b/tests/qtest/libqos/libqos.c index 5c0fa1f7c5..28a0901a0a 100644 --- a/tests/qtest/libqos/libqos.c +++ b/tests/qtest/libqos/libqos.c @@ -117,13 +117,14 @@ void migrate(QOSState *from, QOSState *to, const char *uri) g_assert(qdict_haskey(sub, "status")); st = qdict_get_str(sub, "status"); - /* "setup", "active", "completed", "failed", "cancelled" */ + /* "setup", "active", "device", "completed", "failed", "cancelled" */ if (strcmp(st, "completed") == 0) { qobject_unref(rsp); break; } if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0) + || (strcmp(st, "device") == 0) || (strcmp(st, "wait-unplug") == 0)) { qobject_unref(rsp); g_usleep(5000); diff --git a/tests/qtest/libqos/virtio-scmi.c b/tests/qtest/libqos/virtio-scmi.c index ce8f4d5c06..6b5bd4db42 100644 --- a/tests/qtest/libqos/virtio-scmi.c +++ b/tests/qtest/libqos/virtio-scmi.c @@ -1,7 +1,7 @@ /* * virtio-scmi nodes for testing * - * SPDX-FileCopyrightText: Linaro Ltd + * Copyright (c) Linaro Ltd. * SPDX-FileCopyrightText: Red Hat, Inc. * SPDX-License-Identifier: GPL-2.0-or-later * diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index a1e105f27f..437b24fa2e 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -75,6 +75,8 @@ struct QTestState { int fd; int qmp_fd; + int sock; + int qmpsock; pid_t qemu_pid; /* our child QEMU process */ int wstatus; #ifdef _WIN32 @@ -458,18 +460,19 @@ static QTestState *G_GNUC_PRINTF(2, 3) qtest_spawn_qemu(const char *qemu_bin, return s; } +static char *qtest_socket_path(const char *suffix) +{ + return g_strdup_printf("%s/qtest-%d.%s", g_get_tmp_dir(), getpid(), suffix); +} + static QTestState *qtest_init_internal(const char *qemu_bin, - const char *extra_args) + const char *extra_args, + bool do_connect) { QTestState *s; int sock, qmpsock, i; - gchar *socket_path; - gchar *qmp_socket_path; - - socket_path = g_strdup_printf("%s/qtest-%d.sock", - g_get_tmp_dir(), getpid()); - qmp_socket_path = g_strdup_printf("%s/qtest-%d.qmp", - g_get_tmp_dir(), getpid()); + g_autofree gchar *socket_path = qtest_socket_path("sock"); + g_autofree gchar *qmp_socket_path = qtest_socket_path("qmp"); /* * It's possible that if an earlier test run crashed it might @@ -501,22 +504,19 @@ static QTestState *qtest_init_internal(const char *qemu_bin, qtest_client_set_rx_handler(s, qtest_client_socket_recv_line); qtest_client_set_tx_handler(s, qtest_client_socket_send); - s->fd = socket_accept(sock); - if (s->fd >= 0) { - s->qmp_fd = socket_accept(qmpsock); - } - unlink(socket_path); - unlink(qmp_socket_path); - g_free(socket_path); - g_free(qmp_socket_path); - - g_assert(s->fd >= 0 && s->qmp_fd >= 0); - s->rx = g_string_new(""); for (i = 0; i < MAX_IRQ; i++) { s->irq_level[i] = false; } + s->fd = -1; + s->qmp_fd = -1; + s->sock = sock; + s->qmpsock = qmpsock; + if (do_connect) { + qtest_connect(s); + } + /* * Stopping QEMU for debugging is not supported on Windows. * @@ -531,28 +531,38 @@ static QTestState *qtest_init_internal(const char *qemu_bin, } #endif - /* ask endianness of the target */ + return s; +} - s->big_endian = qtest_query_target_endianness(s); +void qtest_connect(QTestState *s) +{ + g_autofree gchar *socket_path = qtest_socket_path("sock"); + g_autofree gchar *qmp_socket_path = qtest_socket_path("qmp"); - return s; + g_assert(s->sock >= 0 && s->qmpsock >= 0); + s->fd = socket_accept(s->sock); + if (s->fd >= 0) { + s->qmp_fd = socket_accept(s->qmpsock); + } + unlink(socket_path); + unlink(qmp_socket_path); + g_assert(s->fd >= 0 && s->qmp_fd >= 0); + s->sock = s->qmpsock = -1; + /* ask endianness of the target */ + s->big_endian = qtest_query_target_endianness(s); } QTestState *qtest_init_without_qmp_handshake(const char *extra_args) { - return qtest_init_internal(qtest_qemu_binary(NULL), extra_args); + return qtest_init_internal(qtest_qemu_binary(NULL), extra_args, true); } -QTestState *qtest_init_with_env_and_capabilities(const char *var, - const char *extra_args, - QList *capabilities) +void qtest_qmp_handshake(QTestState *s, QList *capabilities) { - QTestState *s = qtest_init_internal(qtest_qemu_binary(var), extra_args); - QDict *greeting; - /* Read the QMP greeting and then do the handshake */ - greeting = qtest_qmp_receive(s); + QDict *greeting = qtest_qmp_receive(s); qobject_unref(greeting); + if (capabilities) { qtest_qmp_assert_success(s, "{ 'execute': 'qmp_capabilities', " @@ -561,18 +571,37 @@ QTestState *qtest_init_with_env_and_capabilities(const char *var, } else { qtest_qmp_assert_success(s, "{ 'execute': 'qmp_capabilities' }"); } +} + +QTestState *qtest_init_with_env_and_capabilities(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); + if (do_connect) { + qtest_qmp_handshake(s, capabilities); + } else { + /* + * If the connection is delayed, the capabilities must be set + * at that moment. + */ + assert(!capabilities); + } return s; } -QTestState *qtest_init_with_env(const char *var, const char *extra_args) +QTestState *qtest_init_with_env(const char *var, const char *extra_args, + bool do_connect) { - return qtest_init_with_env_and_capabilities(var, extra_args, NULL); + return qtest_init_with_env_and_capabilities(var, extra_args, NULL, true); } QTestState *qtest_init(const char *extra_args) { - return qtest_init_with_env(NULL, extra_args); + return qtest_init_with_env(NULL, extra_args, true); } QTestState *qtest_vinitf(const char *fmt, va_list ap) @@ -782,6 +811,7 @@ QDict *qtest_qmp_receive(QTestState *s) QDict *qtest_qmp_receive_dict(QTestState *s) { + g_assert(s->qmp_fd >= 0); return qmp_fd_receive(s->qmp_fd); } @@ -809,12 +839,14 @@ int qtest_socket_server(const char *socket_path) void qtest_qmp_vsend_fds(QTestState *s, int *fds, size_t fds_num, const char *fmt, va_list ap) { + g_assert(s->qmp_fd >= 0); qmp_fd_vsend_fds(s->qmp_fd, fds, fds_num, fmt, ap); } #endif void qtest_qmp_vsend(QTestState *s, const char *fmt, va_list ap) { + g_assert(s->qmp_fd >= 0); qmp_fd_vsend(s->qmp_fd, fmt, ap); } @@ -875,6 +907,7 @@ void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...) { va_list ap; + g_assert(s->qmp_fd >= 0); va_start(ap, fmt); qmp_fd_vsend_raw(s->qmp_fd, fmt, ap); va_end(ap); @@ -1580,7 +1613,7 @@ static struct MachInfo *qtest_get_machines(const char *var) silence_spawn_log = !g_test_verbose(); - qts = qtest_init_with_env(qemu_var, "-machine none"); + qts = qtest_init_with_env(qemu_var, "-machine none", true); response = qtest_qmp(qts, "{ 'execute': 'query-machines' }"); g_assert(response); list = qdict_get_qlist(response, "return"); @@ -1635,7 +1668,7 @@ static struct CpuModel *qtest_get_cpu_models(void) silence_spawn_log = !g_test_verbose(); - qts = qtest_init_with_env(NULL, "-machine none"); + qts = qtest_init_with_env(NULL, "-machine none", true); response = qtest_qmp(qts, "{ 'execute': 'query-cpu-definitions' }"); g_assert(response); list = qdict_get_qlist(response, "return"); diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h index ce88d23eae..29f123e281 100644 --- a/tests/qtest/libqtest.h +++ b/tests/qtest/libqtest.h @@ -61,13 +61,15 @@ QTestState *qtest_init(const char *extra_args); * @var: Environment variable from where to take the QEMU binary * @extra_args: Other arguments to pass to QEMU. CAUTION: these * arguments are subject to word splitting and shell evaluation. + * @do_connect: connect to qemu monitor and qtest socket. * * Like qtest_init(), but use a different environment variable for the * QEMU binary. * * Returns: #QTestState instance. */ -QTestState *qtest_init_with_env(const char *var, const char *extra_args); +QTestState *qtest_init_with_env(const char *var, const char *extra_args, + bool do_connect); /** * qtest_init_with_env_and_capabilities: @@ -75,6 +77,7 @@ QTestState *qtest_init_with_env(const char *var, const char *extra_args); * @extra_args: Other arguments to pass to QEMU. CAUTION: these * arguments are subject to word splitting and shell evaluation. * @capabilities: list of QMP capabilities (strings) to enable + * @do_connect: connect to qemu monitor and qtest socket. * * Like qtest_init_with_env(), but enable specified capabilities during * hadshake. @@ -83,7 +86,8 @@ QTestState *qtest_init_with_env(const char *var, const char *extra_args); */ QTestState *qtest_init_with_env_and_capabilities(const char *var, const char *extra_args, - QList *capabilities); + QList *capabilities, + bool do_connect); /** * qtest_init_without_qmp_handshake: @@ -95,6 +99,22 @@ QTestState *qtest_init_with_env_and_capabilities(const char *var, QTestState *qtest_init_without_qmp_handshake(const char *extra_args); /** + * qtest_connect + * @s: #QTestState instance to connect + * Connect to qemu monitor and qtest socket, after skipping them in + * qtest_init_with_env. Does not handshake with the monitor. + */ +void qtest_connect(QTestState *s); + +/** + * qtest_qmp_handshake: + * @s: #QTestState instance to operate on. + * @capabilities: list of QMP capabilities (strings) to enable + * Perform handshake after connecting to qemu monitor. + */ +void qtest_qmp_handshake(QTestState *s, QList *capabilities); + +/** * qtest_init_with_serial: * @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/cpr-tests.c b/tests/qtest/migration/cpr-tests.c index 44ce89aa5b..215b0df8c0 100644 --- a/tests/qtest/migration/cpr-tests.c +++ b/tests/qtest/migration/cpr-tests.c @@ -44,6 +44,62 @@ static void test_mode_reboot(void) test_file_common(&args, true); } +static void *test_mode_transfer_start(QTestState *from, QTestState *to) +{ + migrate_set_parameter_str(from, "mode", "cpr-transfer"); + return NULL; +} + +/* + * cpr-transfer mode cannot use the target monitor prior to starting the + * migration, and cannot connect synchronously to the monitor, so defer + * the target connection. + */ +static void test_mode_transfer_common(bool incoming_defer) +{ + g_autofree char *cpr_path = g_strdup_printf("%s/cpr.sock", tmpfs); + g_autofree char *mig_path = g_strdup_printf("%s/migsocket", tmpfs); + g_autofree char *uri = g_strdup_printf("unix:%s", mig_path); + + const char *opts = "-machine aux-ram-share=on -nodefaults"; + g_autofree const char *cpr_channel = g_strdup_printf( + "cpr,addr.transport=socket,addr.type=unix,addr.path=%s", + cpr_path); + g_autofree char *opts_target = g_strdup_printf("-incoming %s %s", + cpr_channel, opts); + + g_autofree char *connect_channels = g_strdup_printf( + "[ { 'channel-type': 'main'," + " 'addr': { 'transport': 'socket'," + " 'type': 'unix'," + " 'path': '%s' } } ]", + mig_path); + + MigrateCommon args = { + .start.opts_source = opts, + .start.opts_target = opts_target, + .start.defer_target_connect = true, + .start.memory_backend = "-object memory-backend-memfd,id=pc.ram,size=%s" + " -machine memory-backend=pc.ram", + .listen_uri = incoming_defer ? "defer" : uri, + .connect_channels = connect_channels, + .cpr_channel = cpr_channel, + .start_hook = test_mode_transfer_start, + }; + + test_precopy_common(&args); +} + +static void test_mode_transfer(void) +{ + test_mode_transfer_common(NULL); +} + +static void test_mode_transfer_defer(void) +{ + test_mode_transfer_common(true); +} + void migration_test_add_cpr(MigrationTestEnv *env) { tmpfs = env->tmpfs; @@ -55,4 +111,10 @@ void migration_test_add_cpr(MigrationTestEnv *env) if (getenv("QEMU_TEST_FLAKY_TESTS")) { migration_test_add("/migration/mode/reboot", test_mode_reboot); } + + if (env->has_kvm) { + migration_test_add("/migration/mode/transfer", test_mode_transfer); + migration_test_add("/migration/mode/transfer/defer", + test_mode_transfer_defer); + } } diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c index 4550cda129..de65bfe40d 100644 --- a/tests/qtest/migration/framework.c +++ b/tests/qtest/migration/framework.c @@ -18,6 +18,8 @@ #include "migration/migration-qmp.h" #include "migration/migration-util.h" #include "ppc-util.h" +#include "qapi/error.h" +#include "qapi/qmp/qjson.h" #include "qapi/qmp/qlist.h" #include "qemu/module.h" #include "qemu/option.h" @@ -196,9 +198,10 @@ static void cleanup(const char *filename) static QList *migrate_start_get_qmp_capabilities(const MigrateStart *args) { - QList *capabilities = qlist_new(); + QList *capabilities = NULL; if (args->oob) { + capabilities = qlist_new(); qlist_append_str(capabilities, "oob"); } return capabilities; @@ -221,6 +224,8 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri, g_autofree char *machine = NULL; const char *bootpath; g_autoptr(QList) capabilities = migrate_start_get_qmp_capabilities(args); + g_autofree char *memory_backend = NULL; + const char *events; if (args->use_shmem) { if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) { @@ -296,6 +301,12 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri, memory_size, shmem_path); } + if (args->memory_backend) { + memory_backend = g_strdup_printf(args->memory_backend, memory_size); + } else { + memory_backend = g_strdup_printf("-m %s ", memory_size); + } + if (args->use_dirty_ring) { kvm_opts = ",dirty-ring-size=4096"; } @@ -314,40 +325,48 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri, cmd_source = g_strdup_printf("-accel kvm%s -accel tcg " "-machine %s,%s " "-name source,debug-threads=on " - "-m %s " + "%s " "-serial file:%s/src_serial " "%s %s %s %s", kvm_opts ? kvm_opts : "", machine, machine_opts, - memory_size, tmpfs, + memory_backend, tmpfs, arch_opts ? arch_opts : "", shmem_opts ? shmem_opts : "", args->opts_source ? args->opts_source : "", ignore_stderr); if (!args->only_target) { *from = qtest_init_with_env_and_capabilities(QEMU_ENV_SRC, cmd_source, - capabilities); + 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 + * so none are missed. This is for testing only, do not set migration + * options like this in general. + */ + events = args->defer_target_connect ? "-global migration.x-events=on" : ""; + cmd_target = g_strdup_printf("-accel kvm%s -accel tcg " "-machine %s,%s " "-name target,debug-threads=on " - "-m %s " + "%s " "-serial file:%s/dest_serial " "-incoming %s " - "%s %s %s %s", + "%s %s %s %s %s", kvm_opts ? kvm_opts : "", machine, machine_opts, - memory_size, tmpfs, uri, + memory_backend, tmpfs, uri, + events, arch_opts ? arch_opts : "", shmem_opts ? shmem_opts : "", args->opts_target ? args->opts_target : "", ignore_stderr); *to = qtest_init_with_env_and_capabilities(QEMU_ENV_DST, cmd_target, - capabilities); + capabilities, !args->defer_target_connect); qtest_qmp_set_event_callback(*to, migrate_watch_for_events, &dst_state); @@ -365,7 +384,9 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri, * to mimic as closer as that. */ migrate_set_capability(*from, "events", true); - migrate_set_capability(*to, "events", true); + if (!args->defer_target_connect) { + migrate_set_capability(*to, "events", true); + } return 0; } @@ -399,6 +420,7 @@ void migrate_end(QTestState *from, QTestState *to, bool test_dest) qtest_quit(to); cleanup("migsocket"); + cleanup("cpr.sock"); cleanup("src_serial"); cleanup("dest_serial"); cleanup(FILE_TEST_FILENAME); @@ -686,6 +708,10 @@ void test_precopy_common(MigrateCommon *args) { QTestState *from, *to; void *data_hook = NULL; + QObject *in_channels = NULL; + QObject *out_channels = NULL; + + g_assert(!args->cpr_channel || args->connect_channels); if (migrate_start(&from, &to, args->listen_uri, &args->start)) { return; @@ -718,12 +744,40 @@ void test_precopy_common(MigrateCommon *args) } } + /* + * The cpr channel must be included in outgoing channels, but not in + * migrate-incoming channels. + */ + if (args->connect_channels) { + if (args->start.defer_target_connect && + !strcmp(args->listen_uri, "defer")) { + in_channels = qobject_from_json(args->connect_channels, + &error_abort); + } + out_channels = qobject_from_json(args->connect_channels, &error_abort); + + if (args->cpr_channel) { + QList *channels_list = qobject_to(QList, out_channels); + QObject *obj = migrate_str_to_channel(args->cpr_channel); + + qlist_append(channels_list, obj); + } + } + if (args->result == MIG_TEST_QMP_ERROR) { - migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}"); + migrate_qmp_fail(from, args->connect_uri, out_channels, "{}"); goto finish; } - migrate_qmp(from, to, args->connect_uri, args->connect_channels, "{}"); + migrate_qmp(from, to, args->connect_uri, out_channels, "{}"); + + if (args->start.defer_target_connect) { + qtest_connect(to); + qtest_qmp_handshake(to, NULL); + if (!strcmp(args->listen_uri, "defer")) { + migrate_incoming_qmp(to, args->connect_uri, in_channels, "{}"); + } + } if (args->result != MIG_TEST_SUCCEED) { bool allow_active = args->result == MIG_TEST_FAIL; @@ -868,7 +922,7 @@ void test_file_common(MigrateCommon *args, bool stop_src) * We need to wait for the source to finish before starting the * destination. */ - migrate_incoming_qmp(to, args->connect_uri, "{}"); + migrate_incoming_qmp(to, args->connect_uri, NULL, "{}"); wait_for_migration_complete(to); if (stop_src) { @@ -904,7 +958,7 @@ void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from, migrate_set_capability(to, "multifd", true); /* Start incoming migration from the 1st socket */ - migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}"); + migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}"); return NULL; } diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h index 7991ee56b6..cb4a984700 100644 --- a/tests/qtest/migration/framework.h +++ b/tests/qtest/migration/framework.h @@ -111,6 +111,14 @@ typedef struct { bool suspend_me; /* enable OOB QMP capability */ bool oob; + /* + * Format string for the main memory backend, containing one %s where the + * size is plugged in. If omitted, "-m %s" is used. + */ + const char *memory_backend; + + /* Do not connect to target monitor and qtest sockets in qtest_init */ + bool defer_target_connect; } MigrateStart; typedef enum PostcopyRecoveryFailStage { @@ -146,6 +154,9 @@ typedef struct { */ const char *connect_channels; + /* Optional: the cpr migration channel, in JSON or dotted keys format */ + const char *cpr_channel; + /* Optional: callback to run at start to set migration parameters */ TestMigrateStartHook start_hook; /* Optional: callback to run at finish to cleanup */ diff --git a/tests/qtest/migration/migration-qmp.c b/tests/qtest/migration/migration-qmp.c index 9431d2beda..5610f6d15d 100644 --- a/tests/qtest/migration/migration-qmp.c +++ b/tests/qtest/migration/migration-qmp.c @@ -15,9 +15,13 @@ #include "migration-qmp.h" #include "migration-util.h" #include "qapi/error.h" +#include "qapi/qapi-types-migration.h" +#include "qapi/qapi-visit-migration.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qjson.h" #include "qapi/qmp/qlist.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qobject-output-visitor.h" /* * Number of seconds we wait when looking for migration @@ -47,8 +51,33 @@ void migration_event_wait(QTestState *s, const char *target) } while (!found); } +/* + * Convert a string representing a single channel to an object. + * @str may be in JSON or dotted keys format. + */ +QObject *migrate_str_to_channel(const char *str) +{ + Visitor *v; + MigrationChannel *channel; + QObject *obj; + + /* Create the channel */ + v = qobject_input_visitor_new_str(str, "channel-type", &error_abort); + visit_type_MigrationChannel(v, NULL, &channel, &error_abort); + visit_free(v); + + /* Create the object */ + v = qobject_output_visitor_new(&obj); + visit_type_MigrationChannel(v, NULL, &channel, &error_abort); + visit_complete(v, &obj); + visit_free(v); + + qapi_free_MigrationChannel(channel); + return obj; +} + void migrate_qmp_fail(QTestState *who, const char *uri, - const char *channels, const char *fmt, ...) + QObject *channels, const char *fmt, ...) { va_list ap; QDict *args, *err; @@ -64,8 +93,7 @@ void migrate_qmp_fail(QTestState *who, const char *uri, g_assert(!qdict_haskey(args, "channels")); if (channels) { - QObject *channels_obj = qobject_from_json(channels, &error_abort); - qdict_put_obj(args, "channels", channels_obj); + qdict_put_obj(args, "channels", channels); } err = qtest_qmp_assert_failure_ref( @@ -82,7 +110,7 @@ void migrate_qmp_fail(QTestState *who, const char *uri, * qobject_from_jsonf_nofail()) with "uri": @uri spliced in. */ void migrate_qmp(QTestState *who, QTestState *to, const char *uri, - const char *channels, const char *fmt, ...) + QObject *channels, const char *fmt, ...) { va_list ap; QDict *args; @@ -102,10 +130,9 @@ void migrate_qmp(QTestState *who, QTestState *to, const char *uri, g_assert(!qdict_haskey(args, "channels")); if (channels) { - QObject *channels_obj = qobject_from_json(channels, &error_abort); - QList *channel_list = qobject_to(QList, channels_obj); + QList *channel_list = qobject_to(QList, channels); migrate_set_ports(to, channel_list); - qdict_put_obj(args, "channels", channels_obj); + qdict_put_obj(args, "channels", channels); } qtest_qmp_assert_success(who, @@ -123,7 +150,8 @@ void migrate_set_capability(QTestState *who, const char *capability, capability, value); } -void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) +void migrate_incoming_qmp(QTestState *to, const char *uri, QObject *channels, + const char *fmt, ...) { va_list ap; QDict *args, *rsp; @@ -133,7 +161,14 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) va_end(ap); g_assert(!qdict_haskey(args, "uri")); - qdict_put_str(args, "uri", uri); + if (uri) { + qdict_put_str(args, "uri", uri); + } + + g_assert(!qdict_haskey(args, "channels")); + if (channels) { + qdict_put_obj(args, "channels", channels); + } /* This function relies on the event to work, make sure it's enabled */ migrate_set_capability(to, "events", true); diff --git a/tests/qtest/migration/migration-qmp.h b/tests/qtest/migration/migration-qmp.h index caaa78722a..faa8181d91 100644 --- a/tests/qtest/migration/migration-qmp.h +++ b/tests/qtest/migration/migration-qmp.h @@ -4,17 +4,19 @@ #include "migration-util.h" +QObject *migrate_str_to_channel(const char *str); + G_GNUC_PRINTF(4, 5) void migrate_qmp_fail(QTestState *who, const char *uri, - const char *channels, const char *fmt, ...); + QObject *channels, const char *fmt, ...); G_GNUC_PRINTF(5, 6) void migrate_qmp(QTestState *who, QTestState *to, const char *uri, - const char *channels, const char *fmt, ...); + QObject *channels, const char *fmt, ...); -G_GNUC_PRINTF(3, 4) +G_GNUC_PRINTF(4, 5) void migrate_incoming_qmp(QTestState *who, const char *uri, - const char *fmt, ...); + QObject *channels, const char *fmt, ...); void migration_event_wait(QTestState *s, const char *target); void migrate_set_capability(QTestState *who, const char *capability, diff --git a/tests/qtest/migration/migration-util.c b/tests/qtest/migration/migration-util.c index 526bed74ea..0ce1413b6c 100644 --- a/tests/qtest/migration/migration-util.c +++ b/tests/qtest/migration/migration-util.c @@ -135,25 +135,32 @@ migrate_get_connect_qdict(QTestState *who) void migrate_set_ports(QTestState *to, QList *channel_list) { - QDict *addr; + g_autoptr(QDict) addr = NULL; QListEntry *entry; const char *addr_port = NULL; - addr = migrate_get_connect_qdict(to); - QLIST_FOREACH_ENTRY(channel_list, entry) { QDict *channel = qobject_to(QDict, qlist_entry_obj(entry)); QDict *addrdict = qdict_get_qdict(channel, "addr"); - if (qdict_haskey(addrdict, "port") && - qdict_haskey(addr, "port") && - (strcmp(qdict_get_str(addrdict, "port"), "0") == 0)) { + if (!qdict_haskey(addrdict, "port") || + strcmp(qdict_get_str(addrdict, "port"), "0")) { + continue; + } + + /* + * Fetch addr only if needed, so tests that are not yet connected to + * the monitor do not query it. Such tests cannot use port=0. + */ + if (!addr) { + addr = migrate_get_connect_qdict(to); + } + + if (qdict_haskey(addr, "port")) { addr_port = qdict_get_str(addr, "port"); qdict_put_str(addrdict, "port", addr_port); } } - - qobject_unref(addr); } bool migrate_watch_for_events(QTestState *who, const char *name, diff --git a/tests/qtest/migration/misc-tests.c b/tests/qtest/migration/misc-tests.c index 6173430748..dda3707cf3 100644 --- a/tests/qtest/migration/misc-tests.c +++ b/tests/qtest/migration/misc-tests.c @@ -11,6 +11,8 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qmp/qjson.h" #include "libqtest.h" #include "migration/framework.h" #include "migration/migration-qmp.h" @@ -205,6 +207,7 @@ static void test_validate_uuid_dst_not_set(void) static void do_test_validate_uri_channel(MigrateCommon *args) { QTestState *from, *to; + QObject *channels; if (migrate_start(&from, &to, args->listen_uri, &args->start)) { return; @@ -217,7 +220,11 @@ static void do_test_validate_uri_channel(MigrateCommon *args) * 'uri' and 'channels' validation is checked even before the migration * starts. */ - migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}"); + channels = args->connect_channels ? + qobject_from_json(args->connect_channels, &error_abort) : + NULL; + migrate_qmp_fail(from, args->connect_uri, channels, "{}"); + migrate_end(from, to, false); } diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c index 23599b29ee..436dbd98e8 100644 --- a/tests/qtest/migration/precopy-tests.c +++ b/tests/qtest/migration/precopy-tests.c @@ -152,7 +152,7 @@ static void *migrate_hook_start_fd(QTestState *from, close(pair[0]); /* Start incoming migration from the 1st socket */ - migrate_incoming_qmp(to, "fd:fd-mig", "{}"); + migrate_incoming_qmp(to, "fd:fd-mig", NULL, "{}"); /* Send the 2nd socket to the target */ qtest_qmp_fds_assert_success(from, &pair[1], 1, @@ -479,7 +479,7 @@ static void test_multifd_tcp_cancel(void) migrate_set_capability(to, "multifd", true); /* Start incoming migration from the 1st socket */ - migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}"); + migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}"); /* Wait for the first serial output from the source */ wait_for_serial("src_serial"); @@ -518,7 +518,7 @@ static void test_multifd_tcp_cancel(void) migrate_set_capability(to2, "multifd", true); /* Start incoming migration from the 1st socket */ - migrate_incoming_qmp(to2, "tcp:127.0.0.1:0", "{}"); + migrate_incoming_qmp(to2, "tcp:127.0.0.1:0", NULL, "{}"); migrate_ensure_non_converge(from); diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c index 08365ffa11..f04573f98c 100644 --- a/tests/qtest/virtio-net-failover.c +++ b/tests/qtest/virtio-net-failover.c @@ -773,7 +773,7 @@ static void test_migrate_in(gconstpointer opaque) check_one_card(qts, true, "standby0", MAC_STANDBY0); check_one_card(qts, false, "primary0", MAC_PRIMARY0); - migrate_incoming_qmp(qts, uri, "{}"); + migrate_incoming_qmp(qts, uri, NULL, "{}"); resp = get_failover_negociated_event(qts); g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0"); @@ -895,7 +895,7 @@ static void test_off_migrate_in(gconstpointer opaque) check_one_card(qts, true, "standby0", MAC_STANDBY0); check_one_card(qts, true, "primary0", MAC_PRIMARY0); - migrate_incoming_qmp(qts, uri, "{}"); + migrate_incoming_qmp(qts, uri, NULL, "{}"); check_one_card(qts, true, "standby0", MAC_STANDBY0); check_one_card(qts, true, "primary0", MAC_PRIMARY0); @@ -1022,7 +1022,7 @@ static void test_guest_off_migrate_in(gconstpointer opaque) check_one_card(qts, true, "standby0", MAC_STANDBY0); check_one_card(qts, false, "primary0", MAC_PRIMARY0); - migrate_incoming_qmp(qts, uri, "{}"); + migrate_incoming_qmp(qts, uri, NULL, "{}"); check_one_card(qts, true, "standby0", MAC_STANDBY0); check_one_card(qts, false, "primary0", MAC_PRIMARY0); @@ -1747,7 +1747,7 @@ static void test_multi_in(gconstpointer opaque) check_one_card(qts, true, "standby1", MAC_STANDBY1); check_one_card(qts, false, "primary1", MAC_PRIMARY1); - migrate_incoming_qmp(qts, uri, "{}"); + migrate_incoming_qmp(qts, uri, NULL, "{}"); resp = get_failover_negociated_event(qts); g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0"); diff --git a/tests/tcg/s390x/Makefile.softmmu-target b/tests/tcg/s390x/Makefile.softmmu-target index 7adde2fa08..8cd4667c63 100644 --- a/tests/tcg/s390x/Makefile.softmmu-target +++ b/tests/tcg/s390x/Makefile.softmmu-target @@ -42,6 +42,7 @@ $(ASM_TESTS): LDFLAGS += -Wl,-T$(LINK_SCRIPT) -Wl,--build-id=none $(ASM_TESTS): $(LINK_SCRIPT) TESTS += $(ASM_TESTS) +MULTIARCH_TESTS += mvc-smc S390X_MULTIARCH_RUNTIME_OBJS = head64.o console.o $(MINILIB_OBJS) $(MULTIARCH_TESTS): $(S390X_MULTIARCH_RUNTIME_OBJS) $(MULTIARCH_TESTS): LDFLAGS += $(S390X_MULTIARCH_RUNTIME_OBJS) diff --git a/tests/tcg/s390x/mvc-smc.c b/tests/tcg/s390x/mvc-smc.c new file mode 100644 index 0000000000..d68f60caa8 --- /dev/null +++ b/tests/tcg/s390x/mvc-smc.c @@ -0,0 +1,82 @@ +/* + * Test modifying code using the MVC instruction. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <minilib.h> + +#define PAGE_SIZE 4096 +#define BR_14_SIZE 2 +#define RWX_OFFSET 2 + +static unsigned char rw[PAGE_SIZE + BR_14_SIZE]; +static unsigned char rwx[RWX_OFFSET + sizeof(rw)] + __attribute__((aligned(PAGE_SIZE))); + +typedef unsigned long (*function_t)(unsigned long); + +static int emit_function(unsigned char *p, int n) +{ + int i = 0, val = 0; + + while (i < n - 2) { + /* aghi %r2,1 */ + p[i++] = 0xa7; + p[i++] = 0x2b; + p[i++] = 0x00; + p[i++] = 0x01; + val++; + } + + /* br %r14 */ + p[i++] = 0x07; + p[i++] = 0xfe; + + return val; +} + +static void memcpy_mvc(void *dest, void *src, unsigned long n) +{ + while (n >= 256) { + asm("mvc 0(256,%[dest]),0(%[src])" + : + : [dest] "a" (dest) + , [src] "a" (src) + : "memory"); + dest += 256; + src += 256; + n -= 256; + } + asm("exrl %[n],0f\n" + "j 1f\n" + "0: mvc 0(1,%[dest]),0(%[src])\n" + "1:" + : + : [dest] "a" (dest) + , [src] "a" (src) + , [n] "a" (n) + : "memory"); +} + +int main(void) +{ + int expected, size; + + /* Create a TB. */ + size = sizeof(rwx) - RWX_OFFSET - 4; + expected = emit_function(rwx + RWX_OFFSET, size); + if (((function_t)(rwx + RWX_OFFSET))(0) != expected) { + return 1; + } + + /* Overwrite the TB. */ + size += 4; + expected = emit_function(rw, size); + memcpy_mvc(rwx + RWX_OFFSET, rw, size); + if (((function_t)(rwx + RWX_OFFSET))(0) != expected) { + return 2; + } + + return 0; +} diff --git a/util/memfd.c b/util/memfd.c index 8a2e906962..07beab174d 100644 --- a/util/memfd.c +++ b/util/memfd.c @@ -194,17 +194,27 @@ bool qemu_memfd_alloc_check(void) /** * qemu_memfd_check(): * - * Check if host supports memfd. + * Check if host supports memfd. Cache the answer for the common case flags=0. */ bool qemu_memfd_check(unsigned int flags) { #ifdef CONFIG_LINUX - int mfd = memfd_create("test", flags | MFD_CLOEXEC); + int mfd; + static int memfd_check = MEMFD_TODO; + + if (!flags && memfd_check != MEMFD_TODO) { + return memfd_check; + } + mfd = memfd_create("test", flags | MFD_CLOEXEC); if (mfd >= 0) { close(mfd); - return true; } + if (!flags) { + memfd_check = (mfd >= 0) ? MEMFD_OK : MEMFD_KO; + } + return (mfd >= 0); + #endif return false; diff --git a/util/oslib-posix.c b/util/oslib-posix.c index 7a542cb50b..2bb34dade3 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -931,3 +931,55 @@ void qemu_close_all_open_fd(const int *skip, unsigned int nskip) qemu_close_all_open_fd_fallback(skip, nskip, open_max); } } + +int qemu_shm_alloc(size_t size, Error **errp) +{ + g_autoptr(GString) shm_name = g_string_new(NULL); + int fd, oflag, cur_sequence; + static int sequence; + mode_t mode; + + cur_sequence = qatomic_fetch_inc(&sequence); + + /* + * Let's use `mode = 0` because we don't want other processes to open our + * memory unless we share the file descriptor with them. + */ + mode = 0; + oflag = O_RDWR | O_CREAT | O_EXCL; + + /* + * Some operating systems allow creating anonymous POSIX shared memory + * objects (e.g. FreeBSD provides the SHM_ANON constant), but this is not + * defined by POSIX, so let's create a unique name. + * + * From Linux's shm_open(3) man-page: + * For portable use, a shared memory object should be identified + * by a name of the form /somename;" + */ + g_string_printf(shm_name, "/qemu-" FMT_pid "-shm-%d", getpid(), + cur_sequence); + + fd = shm_open(shm_name->str, oflag, mode); + if (fd < 0) { + error_setg_errno(errp, errno, + "failed to create POSIX shared memory"); + return -1; + } + + /* + * We have the file descriptor, so we no longer need to expose the + * POSIX shared memory object. However it will remain allocated as long as + * there are file descriptors pointing to it. + */ + shm_unlink(shm_name->str); + + if (ftruncate(fd, size) == -1) { + error_setg_errno(errp, errno, + "failed to resize POSIX shared memory to %zu", size); + close(fd); + return -1; + } + + return fd; +} diff --git a/util/oslib-win32.c b/util/oslib-win32.c index b623830d62..b7351634ec 100644 --- a/util/oslib-win32.c +++ b/util/oslib-win32.c @@ -877,3 +877,9 @@ void qemu_win32_map_free(void *ptr, HANDLE h, Error **errp) } CloseHandle(h); } + +int qemu_shm_alloc(size_t size, Error **errp) +{ + error_setg(errp, "Shared memory is not supported."); + return -1; +} |