diff options
188 files changed, 3562 insertions, 2160 deletions
diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml index 83c2867295..f01978fb40 100644 --- a/.gitlab-ci.d/buildtest.yml +++ b/.gitlab-ci.d/buildtest.yml @@ -39,9 +39,9 @@ build-system-ubuntu: job: amd64-ubuntu2204-container variables: IMAGE: ubuntu2204 - CONFIGURE_ARGS: --enable-docs --enable-rust + CONFIGURE_ARGS: --enable-docs TARGETS: alpha-softmmu microblazeel-softmmu mips64el-softmmu - MAKE_CHECK_ARGS: check-build check-doc + MAKE_CHECK_ARGS: check-build check-system-ubuntu: extends: .native_test_job_template diff --git a/MAINTAINERS b/MAINTAINERS index f8cd513d8b..bd417e96f7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3515,9 +3515,17 @@ F: include/hw/registerfields.h Rust M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org> S: Maintained -F: rust/qemu-api -F: rust/qemu-api-macros +F: rust/bql/ +F: rust/chardev/ +F: rust/common/ +F: rust/hw/core/ +F: rust/migration/ +F: rust/qemu-macros/ +F: rust/qom/ F: rust/rustfmt.toml +F: rust/system/ +F: rust/tests/ +F: rust/util/ F: scripts/get-wraps-from-cargo-registry.py Rust-related patches CC here diff --git a/accel/dummy-cpus.c b/accel/dummy-cpus.c index 03cfc0fa01..5752f6302c 100644 --- a/accel/dummy-cpus.c +++ b/accel/dummy-cpus.c @@ -43,6 +43,7 @@ static void *dummy_cpu_thread_fn(void *arg) qemu_guest_random_seed_thread_part2(cpu->random_seed); do { + qemu_process_cpu_events(cpu); bql_unlock(); #ifndef _WIN32 do { @@ -57,7 +58,6 @@ static void *dummy_cpu_thread_fn(void *arg) qemu_sem_wait(&cpu->sem); #endif bql_lock(); - qemu_wait_io_event(cpu); } while (!cpu->unplug); bql_unlock(); diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c index d488d6afba..8b794c2d41 100644 --- a/accel/hvf/hvf-accel-ops.c +++ b/accel/hvf/hvf-accel-ops.c @@ -192,13 +192,13 @@ static void *hvf_cpu_thread_fn(void *arg) qemu_guest_random_seed_thread_part2(cpu->random_seed); do { + qemu_process_cpu_events(cpu); if (cpu_can_run(cpu)) { r = hvf_vcpu_exec(cpu); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(cpu); } } - qemu_wait_io_event(cpu); } while (!cpu->unplug || cpu_can_run(cpu)); hvf_vcpu_destroy(cpu); diff --git a/accel/kvm/kvm-accel-ops.c b/accel/kvm/kvm-accel-ops.c index b709187c7d..8ed6945c2f 100644 --- a/accel/kvm/kvm-accel-ops.c +++ b/accel/kvm/kvm-accel-ops.c @@ -47,13 +47,14 @@ static void *kvm_vcpu_thread_fn(void *arg) qemu_guest_random_seed_thread_part2(cpu->random_seed); do { + qemu_process_cpu_events(cpu); + if (cpu_can_run(cpu)) { r = kvm_cpu_exec(cpu); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(cpu); } } - qemu_wait_io_event(cpu); } while (!cpu->unplug || cpu_can_run(cpu)); kvm_destroy_vcpu(cpu); diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index f36dfe3349..9060599cd7 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -358,7 +358,7 @@ int kvm_physical_memory_addr_from_host(KVMState *s, void *ram, static int kvm_set_user_memory_region(KVMMemoryListener *kml, KVMSlot *slot, bool new) { KVMState *s = kvm_state; - struct kvm_userspace_memory_region2 mem; + struct kvm_userspace_memory_region2 mem = {}; int ret; mem.slot = slot->slot | (kml->as_id << 16); @@ -1595,7 +1595,8 @@ static void kvm_set_phys_mem(KVMMemoryListener *kml, mem->ram = ram; mem->flags = kvm_mem_flags(mr); mem->guest_memfd = mr->ram_block->guest_memfd; - mem->guest_memfd_offset = (uint8_t*)ram - mr->ram_block->host; + mem->guest_memfd_offset = mem->guest_memfd >= 0 ? + (uint8_t*)ram - mr->ram_block->host : 0; kvm_slot_init_dirty_bitmap(mem); err = kvm_set_user_memory_region(kml, mem, true); @@ -2776,8 +2777,8 @@ static int kvm_init(AccelState *as, MachineState *ms) kvm_supported_memory_attributes = kvm_vm_check_extension(s, KVM_CAP_MEMORY_ATTRIBUTES); kvm_guest_memfd_supported = - kvm_check_extension(s, KVM_CAP_GUEST_MEMFD) && - kvm_check_extension(s, KVM_CAP_USER_MEMORY2) && + kvm_vm_check_extension(s, KVM_CAP_GUEST_MEMFD) && + kvm_vm_check_extension(s, KVM_CAP_USER_MEMORY2) && (kvm_supported_memory_attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE); kvm_pre_fault_memory_supported = kvm_vm_check_extension(s, KVM_CAP_PRE_FAULT_MEMORY); @@ -3029,10 +3030,6 @@ static void kvm_eat_signals(CPUState *cpu) if (kvm_immediate_exit) { qatomic_set(&cpu->kvm_run->immediate_exit, 0); - /* Write kvm_run->immediate_exit before the cpu->exit_request - * write in kvm_cpu_exec. - */ - smp_wmb(); return; } @@ -3159,7 +3156,6 @@ int kvm_cpu_exec(CPUState *cpu) trace_kvm_cpu_exec(); if (kvm_arch_process_async_events(cpu)) { - qatomic_set(&cpu->exit_request, 0); return EXCP_HLT; } @@ -3187,7 +3183,8 @@ int kvm_cpu_exec(CPUState *cpu) } kvm_arch_pre_run(cpu, run); - if (qatomic_read(&cpu->exit_request)) { + /* Corresponding store-release is in cpu_exit. */ + if (qatomic_load_acquire(&cpu->exit_request)) { trace_kvm_interrupt_exit_request(); /* * KVM requires us to reenter the kernel after IO exits to complete @@ -3197,13 +3194,15 @@ int kvm_cpu_exec(CPUState *cpu) kvm_cpu_kick_self(); } - /* Read cpu->exit_request before KVM_RUN reads run->immediate_exit. - * Matching barrier in kvm_eat_signals. - */ - smp_rmb(); - run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0); + /* + * After writing cpu->exit_request, cpu_exit() sends a signal that writes + * kvm->run->immediate_exit. The signal is already happening after the + * write to cpu->exit_request so, if KVM read kvm->run->immediate_exit + * as true, cpu->exit_request will always read as true. + */ + attrs = kvm_arch_post_run(cpu, run); #ifdef KVM_HAVE_MCE_INJECTION @@ -3346,7 +3345,6 @@ int kvm_cpu_exec(CPUState *cpu) vm_stop(RUN_STATE_INTERNAL_ERROR); } - qatomic_set(&cpu->exit_request, 0); return ret; } @@ -3731,7 +3729,7 @@ int kvm_on_sigbus_vcpu(CPUState *cpu, int code, void *addr) have_sigbus_pending = true; pending_sigbus_addr = addr; pending_sigbus_code = code; - qatomic_set(&cpu->exit_request, 1); + qatomic_set(&cpu->exit_request, true); return 0; #else return 1; diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 8491e5badd..7c20d9db12 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -40,6 +40,7 @@ #include "exec/replay-core.h" #include "system/tcg.h" #include "exec/helper-proto-common.h" +#include "tcg-accel-ops.h" #include "tb-jmp-cache.h" #include "tb-hash.h" #include "tb-context.h" @@ -748,6 +749,22 @@ static inline bool cpu_handle_exception(CPUState *cpu, int *ret) return false; } +void tcg_kick_vcpu_thread(CPUState *cpu) +{ +#ifndef CONFIG_USER_ONLY + /* + * Ensure cpu_exec will see the reason why the exit request was set. + * FIXME: this is not always needed. Other accelerators instead + * read interrupt_request and set exit_request on demand from the + * CPU thread; see kvm_arch_pre_run() for example. + */ + qatomic_store_release(&cpu->exit_request, true); +#endif + + /* Ensure cpu_exec will see the exit request after TCG has exited. */ + qatomic_store_release(&cpu->neg.icount_decr.u16.high, -1); +} + static inline bool icount_exit_request(CPUState *cpu) { if (!icount_enabled()) { @@ -774,7 +791,8 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, /* Clear the interrupt flag now since we're processing * cpu->interrupt_request and cpu->exit_request. * Ensure zeroing happens before reading cpu->exit_request or - * cpu->interrupt_request (see also smp_wmb in cpu_exit()) + * cpu->interrupt_request (see also store-release in + * tcg_kick_vcpu_thread()) */ qatomic_set_mb(&cpu->neg.icount_decr.u16.high, 0); @@ -784,7 +802,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, if (unlikely(cpu_test_interrupt(cpu, ~0))) { bql_lock(); if (cpu_test_interrupt(cpu, CPU_INTERRUPT_DEBUG)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_DEBUG; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_DEBUG); cpu->exception_index = EXCP_DEBUG; bql_unlock(); return true; @@ -793,7 +811,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, /* Do nothing */ } else if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HALT)) { replay_interrupt(); - cpu->interrupt_request &= ~CPU_INTERRUPT_HALT; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HALT); cpu->halted = 1; cpu->exception_index = EXCP_HLT; bql_unlock(); @@ -840,7 +858,7 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, } } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_EXITTB)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_EXITTB; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_EXITTB); /* ensure that no TB jump will be modified as the program flow was changed */ *last_tb = NULL; @@ -851,9 +869,11 @@ static inline bool cpu_handle_interrupt(CPUState *cpu, } #endif /* !CONFIG_USER_ONLY */ - /* Finally, check if we need to exit to the main loop. */ - if (unlikely(qatomic_read(&cpu->exit_request)) || icount_exit_request(cpu)) { - qatomic_set(&cpu->exit_request, 0); + /* + * Finally, check if we need to exit to the main loop. + * The corresponding store-release is in cpu_exit. + */ + if (unlikely(qatomic_load_acquire(&cpu->exit_request)) || icount_exit_request(cpu)) { if (cpu->exception_index == -1) { cpu->exception_index = EXCP_INTERRUPT; } diff --git a/accel/tcg/tcg-accel-ops-mttcg.c b/accel/tcg/tcg-accel-ops-mttcg.c index 337b993d3d..cf1ee7ac25 100644 --- a/accel/tcg/tcg-accel-ops-mttcg.c +++ b/accel/tcg/tcg-accel-ops-mttcg.c @@ -84,10 +84,9 @@ static void *mttcg_cpu_thread_fn(void *arg) cpu_thread_signal_created(cpu); qemu_guest_random_seed_thread_part2(cpu->random_seed); - /* process any pending work */ - cpu->exit_request = 1; - do { + qemu_process_cpu_events(cpu); + if (cpu_can_run(cpu)) { int r; bql_unlock(); @@ -112,8 +111,6 @@ static void *mttcg_cpu_thread_fn(void *arg) break; } } - - qemu_wait_io_event(cpu); } while (!cpu->unplug || cpu_can_run(cpu)); tcg_cpu_destroy(cpu); @@ -123,11 +120,6 @@ static void *mttcg_cpu_thread_fn(void *arg) return NULL; } -void mttcg_kick_vcpu_thread(CPUState *cpu) -{ - cpu_exit(cpu); -} - void mttcg_start_vcpu_thread(CPUState *cpu) { char thread_name[VCPU_THREAD_NAME_SIZE]; diff --git a/accel/tcg/tcg-accel-ops-mttcg.h b/accel/tcg/tcg-accel-ops-mttcg.h index 8ffa7a9a9f..5c145cc859 100644 --- a/accel/tcg/tcg-accel-ops-mttcg.h +++ b/accel/tcg/tcg-accel-ops-mttcg.h @@ -10,9 +10,6 @@ #ifndef TCG_ACCEL_OPS_MTTCG_H #define TCG_ACCEL_OPS_MTTCG_H -/* kick MTTCG vCPU thread */ -void mttcg_kick_vcpu_thread(CPUState *cpu); - /* start an mttcg vCPU thread */ void mttcg_start_vcpu_thread(CPUState *cpu); diff --git a/accel/tcg/tcg-accel-ops-rr.c b/accel/tcg/tcg-accel-ops-rr.c index 6eec5c9eee..2fb4643997 100644 --- a/accel/tcg/tcg-accel-ops-rr.c +++ b/accel/tcg/tcg-accel-ops-rr.c @@ -43,7 +43,7 @@ void rr_kick_vcpu_thread(CPUState *unused) CPUState *cpu; CPU_FOREACH(cpu) { - cpu_exit(cpu); + tcg_kick_vcpu_thread(cpu); }; } @@ -117,7 +117,7 @@ static void rr_wait_io_event(void) rr_start_kick_timer(); CPU_FOREACH(cpu) { - qemu_wait_io_event_common(cpu); + qemu_process_cpu_events_common(cpu); } } @@ -203,7 +203,7 @@ static void *rr_cpu_thread_fn(void *arg) /* process any pending work */ CPU_FOREACH(cpu) { current_cpu = cpu; - qemu_wait_io_event_common(cpu); + qemu_process_cpu_events_common(cpu); } } @@ -211,13 +211,30 @@ static void *rr_cpu_thread_fn(void *arg) cpu = first_cpu; - /* process any pending work */ - cpu->exit_request = 1; - while (1) { /* Only used for icount_enabled() */ int64_t cpu_budget = 0; + if (cpu) { + /* + * This could even reset exit_request for all CPUs, but in practice + * races between CPU exits and changes to "cpu" are so rare that + * there's no advantage in doing so. + */ + qatomic_set(&cpu->exit_request, false); + } + + if (icount_enabled() && all_cpu_threads_idle()) { + /* + * When all cpus are sleeping (e.g in WFI), to avoid a deadlock + * in the main_loop, wake it up in order to start the warp timer. + */ + qemu_notify_event(); + } + + rr_wait_io_event(); + rr_deal_with_unplugged_cpus(); + bql_unlock(); replay_mutex_lock(); bql_lock(); @@ -242,10 +259,17 @@ static void *rr_cpu_thread_fn(void *arg) cpu = first_cpu; } - while (cpu && cpu_work_list_empty(cpu) && !cpu->exit_request) { - /* Store rr_current_cpu before evaluating cpu_can_run(). */ + while (cpu && cpu_work_list_empty(cpu)) { + /* + * Store rr_current_cpu before evaluating cpu->exit_request. + * Pairs with rr_kick_next_cpu(). + */ qatomic_set_mb(&rr_current_cpu, cpu); + /* Pairs with store-release in cpu_exit. */ + if (qatomic_load_acquire(&cpu->exit_request)) { + break; + } current_cpu = cpu; qemu_clock_enable(QEMU_CLOCK_VIRTUAL, @@ -285,21 +309,6 @@ static void *rr_cpu_thread_fn(void *arg) /* Does not need a memory barrier because a spurious wakeup is okay. */ qatomic_set(&rr_current_cpu, NULL); - - if (cpu && cpu->exit_request) { - qatomic_set_mb(&cpu->exit_request, 0); - } - - if (icount_enabled() && all_cpu_threads_idle()) { - /* - * When all cpus are sleeping (e.g in WFI), to avoid a deadlock - * in the main_loop, wake it up in order to start the warp timer. - */ - qemu_notify_event(); - } - - rr_wait_io_event(); - rr_deal_with_unplugged_cpus(); } g_assert_not_reached(); diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c index 9c37266c1e..3bd9800504 100644 --- a/accel/tcg/tcg-accel-ops.c +++ b/accel/tcg/tcg-accel-ops.c @@ -82,8 +82,6 @@ int tcg_cpu_exec(CPUState *cpu) ret = cpu_exec(cpu); cpu_exec_end(cpu); - qatomic_set_mb(&cpu->exit_request, 0); - return ret; } @@ -206,7 +204,7 @@ static void tcg_accel_ops_init(AccelClass *ac) if (qemu_tcg_mttcg_enabled()) { ops->create_vcpu_thread = mttcg_start_vcpu_thread; - ops->kick_vcpu_thread = mttcg_kick_vcpu_thread; + ops->kick_vcpu_thread = tcg_kick_vcpu_thread; ops->handle_interrupt = tcg_handle_interrupt; } else { ops->create_vcpu_thread = rr_start_vcpu_thread; diff --git a/accel/tcg/tcg-accel-ops.h b/accel/tcg/tcg-accel-ops.h index 6feeb3f3e9..aecce605d7 100644 --- a/accel/tcg/tcg-accel-ops.h +++ b/accel/tcg/tcg-accel-ops.h @@ -18,5 +18,6 @@ void tcg_cpu_destroy(CPUState *cpu); int tcg_cpu_exec(CPUState *cpu); void tcg_handle_interrupt(CPUState *cpu, int mask); void tcg_cpu_init_cflags(CPUState *cpu, bool parallel); +void tcg_kick_vcpu_thread(CPUState *cpu); #endif /* TCG_ACCEL_OPS_H */ diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index 748bfab04a..916f18754f 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -38,6 +38,7 @@ #include "qemu/int128.h" #include "trace.h" #include "tcg/tcg-ldst.h" +#include "tcg-accel-ops.h" #include "backend-ldst.h" #include "internal-common.h" #include "tb-internal.h" @@ -46,9 +47,15 @@ __thread uintptr_t helper_retaddr; //#define DEBUG_SIGNAL -void cpu_interrupt(CPUState *cpu, int mask) +void qemu_cpu_kick(CPUState *cpu) { - g_assert_not_reached(); + tcg_kick_vcpu_thread(cpu); +} + +void qemu_process_cpu_events(CPUState *cpu) +{ + qatomic_set(&cpu->exit_request, false); + process_queued_cpu_work(cpu); } /* diff --git a/bsd-user/aarch64/target_arch_cpu.h b/bsd-user/aarch64/target_arch_cpu.h index 87fbf6d677..15df84fda2 100644 --- a/bsd-user/aarch64/target_arch_cpu.h +++ b/bsd-user/aarch64/target_arch_cpu.h @@ -54,7 +54,7 @@ static inline G_NORETURN void target_cpu_loop(CPUARMState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_SWI: diff --git a/bsd-user/arm/target_arch_cpu.h b/bsd-user/arm/target_arch_cpu.h index bc2eaa0bf4..9a952ef0ff 100644 --- a/bsd-user/arm/target_arch_cpu.h +++ b/bsd-user/arm/target_arch_cpu.h @@ -46,7 +46,7 @@ static inline G_NORETURN void target_cpu_loop(CPUARMState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_UDEF: case EXCP_NOCP: diff --git a/bsd-user/i386/target_arch_cpu.h b/bsd-user/i386/target_arch_cpu.h index 5d4c931dec..f147d5b6f8 100644 --- a/bsd-user/i386/target_arch_cpu.h +++ b/bsd-user/i386/target_arch_cpu.h @@ -113,7 +113,7 @@ static inline G_NORETURN void target_cpu_loop(CPUX86State *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case 0x80: { diff --git a/bsd-user/main.c b/bsd-user/main.c index 9ba69642f5..73aae8c327 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -214,11 +214,6 @@ bool qemu_cpu_is_self(CPUState *cpu) return thread_cpu == cpu; } -void qemu_cpu_kick(CPUState *cpu) -{ - cpu_exit(cpu); -} - /* Assumes contents are already zeroed. */ static void init_task_state(TaskState *ts) { diff --git a/bsd-user/riscv/target_arch_cpu.h b/bsd-user/riscv/target_arch_cpu.h index ef92f00480..ad428d0263 100644 --- a/bsd-user/riscv/target_arch_cpu.h +++ b/bsd-user/riscv/target_arch_cpu.h @@ -49,7 +49,7 @@ static inline G_NORETURN void target_cpu_loop(CPURISCVState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); signo = 0; diff --git a/bsd-user/x86_64/target_arch_cpu.h b/bsd-user/x86_64/target_arch_cpu.h index f82042e30a..1fa71d87f1 100644 --- a/bsd-user/x86_64/target_arch_cpu.h +++ b/bsd-user/x86_64/target_arch_cpu.h @@ -121,7 +121,7 @@ static inline G_NORETURN void target_cpu_loop(CPUX86State *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_SYSCALL: diff --git a/clippy.toml b/clippy.toml index 9016172983..204f5713c0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,3 +1,3 @@ doc-valid-idents = ["IrDA", "PrimeCell", ".."] allow-mixed-uninlined-format-args = false -msrv = "1.77.0" +msrv = "1.83.0" diff --git a/configure b/configure index 9aea02cf6a..0f7eb95586 100755 --- a/configure +++ b/configure @@ -1184,12 +1184,12 @@ fi # detect rust triple meson_version=$($meson --version) -if test "$rust" != disabled && ! version_ge "$meson_version" 1.8.1; then +if test "$rust" != disabled && ! version_ge "$meson_version" 1.9.0; then if test "$rust" = enabled; then $mkvenv ensuregroup --dir "${source_path}/python/wheels" \ ${source_path}/pythondeps.toml meson-rust || exit 1 else - echo "Rust needs Meson 1.8.1, disabling" 2>&1 + echo "Rust needs Meson 1.9.0, disabling" 2>&1 rust=disabled fi fi diff --git a/cpu-common.c b/cpu-common.c index ef5757d23b..0eb5c7b8f2 100644 --- a/cpu-common.c +++ b/cpu-common.c @@ -137,7 +137,8 @@ static void queue_work_on_cpu(CPUState *cpu, struct qemu_work_item *wi) wi->done = false; qemu_mutex_unlock(&cpu->work_mutex); - qemu_cpu_kick(cpu); + /* exit the inner loop and reach qemu_process_cpu_events_common(). */ + cpu_exit(cpu); } void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, run_on_cpu_data data, diff --git a/docs/about/build-platforms.rst b/docs/about/build-platforms.rst index 8671c3be9c..0160d3adb8 100644 --- a/docs/about/build-platforms.rst +++ b/docs/about/build-platforms.rst @@ -118,14 +118,15 @@ Rust build dependencies include bindgen or have an older version, it is recommended to install a newer version using ``cargo install bindgen-cli``. - QEMU requires Rust 1.77.0. This is available on all supported platforms - with one exception, namely the ``mips64el`` architecture on Debian bookworm. - For all other architectures, Debian bookworm provides a new-enough Rust - compiler in the ``rustc-web`` package. - - Also, on Ubuntu 22.04 or 24.04 this requires the ``rustc-1.77`` - (or newer) package. The path to ``rustc`` and ``rustdoc`` must be - provided manually to the configure script. + QEMU requires Rust 1.83.0. This is available on all supported platforms + with two exception: Ubuntu LTS releases 22.04 and 24.04, and the + ``mips64el`` architecture on Debian bookworm. For all other + architectures, Debian bookworm provides a new-enough Rust compiler + in the ``rustc-web`` package. + + It is expected that in the future Ubuntu will provide updated packages + like the existing ``rustc-1.82`` package. The path to ``rustc`` and + ``rustdoc`` will have to be provided manually to the configure script. Some distros prefer to avoid vendored crate sources, and instead use local sources from e.g. ``/usr/share/cargo/registry``. QEMU includes a diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index b6737536c6..13a20e86a1 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -1,4 +1,4 @@ -.. |msrv| replace:: 1.63.0 +.. |msrv| replace:: 1.83.0 Rust in QEMU ============ @@ -75,35 +75,23 @@ Note that doctests require all ``.o`` files from the build to be available. Supported tools ''''''''''''''' -QEMU supports rustc version 1.77.0 and newer. Notably, the following features -are missing: +QEMU supports rustc version 1.83.0 and newer. The following features +from relatively new versions of Rust are not used for historical reasons; +patches are welcome: -* inline const expression (stable in 1.79.0), currently worked around with - associated constants in the ``FnCall`` trait. - -* associated constants have to be explicitly marked ``'static`` (`changed in +* associated constants are still explicitly marked ``'static`` (`changed in 1.81.0`__) -* ``&raw`` (stable in 1.82.0). Use ``addr_of!`` and ``addr_of_mut!`` instead, - though hopefully the need for raw pointers will go down over time. - -* ``new_uninit`` (stable in 1.82.0). This is used internally by the ``pinned_init`` - crate, which is planned for inclusion in QEMU, but it can be easily patched - out. - -* referencing statics in constants (stable in 1.83.0). For now use a const - function; this is an important limitation for QEMU's migration stream - architecture (VMState). Right now, VMState lacks type safety because - it is hard to place the ``VMStateField`` definitions in traits. +* ``&raw`` (stable in 1.82.0). * NUL-terminated file names with ``#[track_caller]`` are scheduled for inclusion as ``#![feature(location_file_nul)]``, but it will be a while before QEMU can use them. For now, there is special code in ``util/error.c`` to support non-NUL-terminated file names. -* associated const equality would be nice to have for some users of - ``callbacks::FnCall``, but is still experimental. ``ASSERT_IS_SOME`` - replaces it. +Associated const equality would be nice to have for some users of +``callbacks::FnCall``, but is still experimental. Const assertions +are used instead. __ https://github.com/rust-lang/rust/pull/125258 @@ -115,15 +103,18 @@ anymore. Writing Rust code in QEMU ------------------------- -QEMU includes four crates: +QEMU includes several crates: + +* ``common`` provides Rust-only utilities -* ``qemu_api`` for bindings to C code and useful functionality +* ``bql``, ``chardev``, ``hw/core``, ``migration``, ``qom``, ``system``, + ``util`` for bindings to respective QEMU C library APIs -* ``qemu_api_macros`` defines several procedural macros that are useful when +* ``qemu_macros`` defines several procedural macros that are useful when writing C code * ``pl011`` (under ``rust/hw/char/pl011``) and ``hpet`` (under ``rust/hw/timer/hpet``) - are sample devices that demonstrate ``qemu_api`` and ``qemu_api_macros``, and are + are sample devices that demonstrate Rust binding usage and ``qemu_macros``, and are used to further develop them. These two crates are functional\ [#issues]_ replacements for the ``hw/char/pl011.c`` and ``hw/timer/hpet.c`` files. @@ -136,7 +127,7 @@ This section explains how to work with them. Status '''''' -Modules of ``qemu_api`` can be defined as: +The stability of the modules can be defined as: - *complete*: ready for use in new devices; if applicable, the API supports the full functionality available in C @@ -152,26 +143,26 @@ Modules of ``qemu_api`` can be defined as: The status of the modules is as follows: -================ ====================== -module status -================ ====================== -``assertions`` stable -``bitops`` complete -``callbacks`` complete -``cell`` stable -``errno`` complete -``error`` stable -``irq`` complete -``log`` proof of concept -``memory`` stable -``module`` complete -``qdev`` stable -``qom`` stable -``sysbus`` stable -``timer`` stable -``vmstate`` proof of concept -``zeroable`` stable -================ ====================== +========================== ====================== +module status +========================== ====================== +``bql::cell`` stable +``common::assertions`` stable +``common::bitops`` complete +``common::callbacks`` complete +``common::errno`` complete +``common::zeroable`` stable +``hwcore::irq`` complete +``hwcore::qdev`` stable +``hwcore::sysbus`` stable +``migration::vmstate`` stable +``qom`` stable +``system::memory`` stable +``util::error`` stable +``util::log`` proof of concept +``util::module`` complete +``util::timer`` stable +========================== ====================== .. note:: API stability is not a promise, if anything because the C APIs are not a stable @@ -272,7 +263,7 @@ to go from a shared reference to a ``&mut``. Whenever C code provides you with an opaque ``void *``, avoid converting it to a Rust mutable reference, and use a shared reference instead. The -``qemu_api::cell`` module provides wrappers that can be used to tell the +``bql::cell`` module provides wrappers that can be used to tell the Rust compiler about interior mutability, and optionally to enforce locking rules for the "Big QEMU Lock". In the future, similar cell types might also be provided for ``AioContext``-based locking as well. @@ -290,7 +281,7 @@ a raw pointer, for use in calls to C functions. It can be used for example as follows:: #[repr(transparent)] - #[derive(Debug, qemu_api_macros::Wrapper)] + #[derive(Debug, common::Wrapper)] pub struct Object(Opaque<bindings::Object>); where the special ``derive`` macro provides useful methods such as @@ -304,7 +295,7 @@ the wrapper to be declared thread-safe:: Writing bindings to C code '''''''''''''''''''''''''' -Here are some things to keep in mind when working on the ``qemu_api`` crate. +Here are some things to keep in mind when working on the QEMU Rust crate. **Look at existing code** Very often, similar idioms in C code correspond to similar tricks in @@ -367,7 +358,7 @@ from the type after ``as`` in the invocation of ``parse_macro_input!``:: .into() } -The ``qemu_api_macros`` crate has utility functions to examine a +The ``qemu_macros`` crate has utility functions to examine a ``DeriveInput`` and perform common checks (e.g. looking for a struct with named fields). These functions return ``Result<..., syn::Error>`` and can be used easily in the procedural macro function:: @@ -408,7 +399,7 @@ Right now, only the nightly version of ``rustfmt`` is supported. This might change in the future. While CI checks for correct formatting via ``cargo fmt --check``, maintainers can fix this for you when applying patches. -It is expected that ``qemu_api`` provides full ``rustdoc`` documentation for +It is expected that QEMU Rust crates provides full ``rustdoc`` documentation for bindings that are in their final shape or close. Adding dependencies diff --git a/docs/devel/tcg-icount.rst b/docs/devel/tcg-icount.rst index 7df883446a..a1dcd79e0f 100644 --- a/docs/devel/tcg-icount.rst +++ b/docs/devel/tcg-icount.rst @@ -37,7 +37,7 @@ translator starts by allocating a budget of instructions to be executed. The budget of instructions is limited by how long it will be until the next timer will expire. We store this budget as part of a vCPU icount_decr field which shared with the machinery for handling -cpu_exit(). The whole field is checked at the start of every +qemu_cpu_kick(). The whole field is checked at the start of every translated block and will cause a return to the outer loop to deal with whatever caused the exit. diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 259cf2a3c3..41a339903c 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -67,27 +67,16 @@ CPUState *cpu_create(const char *typename) return cpu; } -/* Resetting the IRQ comes from across the code base so we take the - * BQL here if we need to. cpu_interrupt assumes it is held.*/ void cpu_reset_interrupt(CPUState *cpu, int mask) { - bool need_lock = !bql_locked(); - - if (need_lock) { - bql_lock(); - } - cpu->interrupt_request &= ~mask; - if (need_lock) { - bql_unlock(); - } + qatomic_and(&cpu->interrupt_request, ~mask); } void cpu_exit(CPUState *cpu) { - qatomic_set(&cpu->exit_request, 1); - /* Ensure cpu_exec will see the exit request after TCG has exited. */ - smp_wmb(); - qatomic_set(&cpu->neg.icount_decr.u16.high, -1); + /* Ensure cpu_exec will see the reason why the exit request was set. */ + qatomic_store_release(&cpu->exit_request, true); + qemu_cpu_kick(cpu); } static int cpu_common_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index a975405d3a..09c928c1f9 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -204,7 +204,7 @@ static int cpu_common_post_load(void *opaque, int version_id) * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the * version_id is increased. */ - cpu->interrupt_request &= ~0x01; + cpu_reset_interrupt(cpu, 0x01); tlb_flush(cpu); diff --git a/hw/ppc/ppc.c b/hw/ppc/ppc.c index 43d0d0e755..3e436c7041 100644 --- a/hw/ppc/ppc.c +++ b/hw/ppc/ppc.c @@ -190,6 +190,7 @@ static void ppc970_set_irq(void *opaque, int pin, int level) if (level) { trace_ppc_irq_cpu("stop"); cs->halted = 1; + cpu_exit(cs); } else { trace_ppc_irq_cpu("restart"); cs->halted = 0; @@ -386,6 +387,7 @@ static void ppc40x_set_irq(void *opaque, int pin, int level) if (level) { trace_ppc_irq_cpu("stop"); cs->halted = 1; + cpu_exit(cs); } else { trace_ppc_irq_cpu("restart"); cs->halted = 0; diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index 1e936f35e4..c594d4b916 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -509,8 +509,8 @@ static target_ulong h_cede(PowerPCCPU *cpu, SpaprMachineState *spapr, if (!cpu_has_work(cs)) { cs->halted = 1; cs->exception_index = EXCP_HLT; - cs->exit_request = 1; ppc_maybe_interrupt(env); + cpu_exit(cs); } return H_SUCCESS; @@ -531,8 +531,8 @@ static target_ulong h_confer_self(PowerPCCPU *cpu) } cs->halted = 1; cs->exception_index = EXCP_HALTED; - cs->exit_request = 1; ppc_maybe_interrupt(&cpu->env); + cpu_exit(cs); return H_SUCCESS; } @@ -624,8 +624,7 @@ static target_ulong h_confer(PowerPCCPU *cpu, SpaprMachineState *spapr, } cs->exception_index = EXCP_YIELD; - cs->exit_request = 1; - cpu_loop_exit(cs); + cpu_exit(cs); return H_SUCCESS; } diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c index 78309dbb09..143bc8c379 100644 --- a/hw/ppc/spapr_rtas.c +++ b/hw/ppc/spapr_rtas.c @@ -221,7 +221,7 @@ static void rtas_stop_self(PowerPCCPU *cpu, SpaprMachineState *spapr, cs->halted = 1; ppc_store_lpcr(cpu, env->spr[SPR_LPCR] & ~pcc->lpcr_pm); kvmppc_set_reg_ppc_online(cpu, 0); - qemu_cpu_kick(cs); + cpu_exit(cs); } static void rtas_ibm_suspend_me(PowerPCCPU *cpu, SpaprMachineState *spapr, diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c index 789a31d0a0..1acba4fa9d 100644 --- a/hw/timer/hpet.c +++ b/hw/timer/hpet.c @@ -40,6 +40,7 @@ #include "qom/object.h" #include "qemu/lockable.h" #include "qemu/seqlock.h" +#include "qemu/main-loop.h" #include "trace.h" struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX}; @@ -222,12 +223,15 @@ static void update_irq(struct HPETTimer *timer, int set) timer->fsb & 0xffffffff, MEMTXATTRS_UNSPECIFIED, NULL); } else if (timer->config & HPET_TN_TYPE_LEVEL) { + BQL_LOCK_GUARD(); qemu_irq_raise(s->irqs[route]); } else { + BQL_LOCK_GUARD(); qemu_irq_pulse(s->irqs[route]); } } else { if (!timer_fsb_route(timer)) { + BQL_LOCK_GUARD(); qemu_irq_lower(s->irqs[route]); } } @@ -534,10 +538,12 @@ static void hpet_ram_write(void *opaque, hwaddr addr, /* i8254 and RTC output pins are disabled * when HPET is in legacy mode */ if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + BQL_LOCK_GUARD(); qemu_set_irq(s->pit_enabled, 0); qemu_irq_lower(s->irqs[0]); qemu_irq_lower(s->irqs[RTC_ISA_IRQ]); } else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + BQL_LOCK_GUARD(); qemu_irq_lower(s->irqs[0]); qemu_set_irq(s->pit_enabled, 1); qemu_set_irq(s->irqs[RTC_ISA_IRQ], s->rtc_irq_level); @@ -683,11 +689,13 @@ static void hpet_handle_legacy_irq(void *opaque, int n, int level) if (n == HPET_LEGACY_PIT_INT) { if (!hpet_in_legacy_mode(s)) { + BQL_LOCK_GUARD(); qemu_set_irq(s->irqs[0], level); } } else { s->rtc_irq_level = level; if (!hpet_in_legacy_mode(s)) { + BQL_LOCK_GUARD(); qemu_set_irq(s->irqs[RTC_ISA_IRQ], level); } } diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index b01a0cffd6..fb788ca110 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -422,6 +422,15 @@ struct qemu_work_item; * valid under cpu_list_lock. * @created: Indicates whether the CPU thread has been successfully created. * @halt_cond: condition variable sleeping threads can wait on. + * @exit_request: Another thread requests the CPU to call qemu_process_cpu_events(). + * Should be read only by CPU thread with load-acquire, to synchronize with + * other threads' store-release operation. + * + * In some cases, accelerator-specific code will write exit_request from + * within the same thread, to "bump" the effect of qemu_cpu_kick() to + * the one provided by cpu_exit(), especially when processing interrupt + * flags. In this case, the write and read happen in the same thread + * and the write therefore can use qemu_atomic_set(). * @interrupt_request: Indicates a pending interrupt request. * Only used by system emulation. * @halted: Nonzero if the CPU is in suspended state. @@ -495,7 +504,6 @@ struct CPUState { bool exit_request; int exclusive_context_count; uint32_t cflags_next_tb; - /* updates protected by BQL */ uint32_t interrupt_request; int singlestep_enabled; int64_t icount_budget; @@ -830,7 +838,8 @@ bool qemu_cpu_is_self(CPUState *cpu); * qemu_cpu_kick: * @cpu: The vCPU to kick. * - * Kicks @cpu's thread. + * Kicks @cpu's thread to exit the accelerator. For accelerators that + * can do that, the target vCPU thread will try not to take the BQL. */ void qemu_cpu_kick(CPUState *cpu); @@ -1136,6 +1145,15 @@ AddressSpace *cpu_get_address_space(CPUState *cpu, int asidx); G_NORETURN void cpu_abort(CPUState *cpu, const char *fmt, ...) G_GNUC_PRINTF(2, 3); +/** + * qemu_process_cpu_events: + * @cpu: CPU that left the execution loop + * + * Perform accelerator-independent work after the CPU has left + * the inner execution loop. + */ +void qemu_process_cpu_events(CPUState *cpu); + /* $(top_srcdir)/cpu.c */ void cpu_class_init_props(DeviceClass *dc); void cpu_exec_class_post_init(CPUClass *cc); diff --git a/include/system/cpus.h b/include/system/cpus.h index 69be6a77a7..508444ccf1 100644 --- a/include/system/cpus.h +++ b/include/system/cpus.h @@ -17,8 +17,7 @@ bool cpu_work_list_empty(CPUState *cpu); bool cpu_thread_is_idle(CPUState *cpu); bool all_cpu_threads_idle(void); bool cpu_can_run(CPUState *cpu); -void qemu_wait_io_event_common(CPUState *cpu); -void qemu_wait_io_event(CPUState *cpu); +void qemu_process_cpu_events_common(CPUState *cpu); void cpu_thread_signal_created(CPUState *cpu); void cpu_thread_signal_destroyed(CPUState *cpu); void cpu_handle_guest_debug(CPUState *cpu); diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index 6060572eed..50a4c99535 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -159,7 +159,7 @@ void cpu_loop(CPUARMState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_SWI: diff --git a/linux-user/alpha/cpu_loop.c b/linux-user/alpha/cpu_loop.c index 728b64906d..bb8346b509 100644 --- a/linux-user/alpha/cpu_loop.c +++ b/linux-user/alpha/cpu_loop.c @@ -35,7 +35,7 @@ void cpu_loop(CPUAlphaState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_RESET: diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c index 9aeb9b0087..cd89b7d6f5 100644 --- a/linux-user/arm/cpu_loop.c +++ b/linux-user/arm/cpu_loop.c @@ -295,7 +295,7 @@ void cpu_loop(CPUARMState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch(trapnr) { case EXCP_UDEF: diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c index 25c97edcae..1941f4c9c1 100644 --- a/linux-user/hexagon/cpu_loop.c +++ b/linux-user/hexagon/cpu_loop.c @@ -36,7 +36,7 @@ void cpu_loop(CPUHexagonState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_INTERRUPT: diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c index 3af50653bb..356cb48acc 100644 --- a/linux-user/hppa/cpu_loop.c +++ b/linux-user/hppa/cpu_loop.c @@ -119,7 +119,7 @@ void cpu_loop(CPUHPPAState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_SYSCALL: diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index 7b2d8b03d8..f3f58576af 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -214,7 +214,7 @@ void cpu_loop(CPUX86State *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch(trapnr) { case 0x80: diff --git a/linux-user/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c index a0a4cbb7cc..26a5ce3a93 100644 --- a/linux-user/loongarch64/cpu_loop.c +++ b/linux-user/loongarch64/cpu_loop.c @@ -27,7 +27,7 @@ void cpu_loop(CPULoongArchState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_INTERRUPT: diff --git a/linux-user/m68k/cpu_loop.c b/linux-user/m68k/cpu_loop.c index aca0bf23dc..2c9f628241 100644 --- a/linux-user/m68k/cpu_loop.c +++ b/linux-user/m68k/cpu_loop.c @@ -33,7 +33,7 @@ void cpu_loop(CPUM68KState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch(trapnr) { case EXCP_ILLEGAL: diff --git a/linux-user/main.c b/linux-user/main.c index 7b0ccb6fd6..4ddfc9a619 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -189,11 +189,6 @@ bool qemu_cpu_is_self(CPUState *cpu) return thread_cpu == cpu; } -void qemu_cpu_kick(CPUState *cpu) -{ - cpu_exit(cpu); -} - void task_settid(TaskState *ts) { if (ts->ts_tid == 0) { diff --git a/linux-user/microblaze/cpu_loop.c b/linux-user/microblaze/cpu_loop.c index d8277961c7..78506ab23d 100644 --- a/linux-user/microblaze/cpu_loop.c +++ b/linux-user/microblaze/cpu_loop.c @@ -32,7 +32,7 @@ void cpu_loop(CPUMBState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_INTERRUPT: diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c index e67b8a2e46..2365de1de1 100644 --- a/linux-user/mips/cpu_loop.c +++ b/linux-user/mips/cpu_loop.c @@ -74,7 +74,7 @@ void cpu_loop(CPUMIPSState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch(trapnr) { case EXCP_SYSCALL: diff --git a/linux-user/openrisc/cpu_loop.c b/linux-user/openrisc/cpu_loop.c index 8c72347a99..2167d880d5 100644 --- a/linux-user/openrisc/cpu_loop.c +++ b/linux-user/openrisc/cpu_loop.c @@ -33,7 +33,7 @@ void cpu_loop(CPUOpenRISCState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_SYSCALL: diff --git a/linux-user/ppc/cpu_loop.c b/linux-user/ppc/cpu_loop.c index 22885ffd90..b0b0cb14b4 100644 --- a/linux-user/ppc/cpu_loop.c +++ b/linux-user/ppc/cpu_loop.c @@ -77,7 +77,7 @@ void cpu_loop(CPUPPCState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); arch_interrupt = true; switch (trapnr) { diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c index b316281532..ce542540c2 100644 --- a/linux-user/riscv/cpu_loop.c +++ b/linux-user/riscv/cpu_loop.c @@ -36,7 +36,7 @@ void cpu_loop(CPURISCVState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_INTERRUPT: diff --git a/linux-user/s390x/cpu_loop.c b/linux-user/s390x/cpu_loop.c index 49e44548f8..4929b32e1f 100644 --- a/linux-user/s390x/cpu_loop.c +++ b/linux-user/s390x/cpu_loop.c @@ -64,7 +64,7 @@ void cpu_loop(CPUS390XState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case EXCP_INTERRUPT: diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c index 259ea1cc8b..0c9d7e9c46 100644 --- a/linux-user/sh4/cpu_loop.c +++ b/linux-user/sh4/cpu_loop.c @@ -34,7 +34,7 @@ void cpu_loop(CPUSH4State *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case 0x160: diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c index 7d30cd1ff2..7391e2add8 100644 --- a/linux-user/sparc/cpu_loop.c +++ b/linux-user/sparc/cpu_loop.c @@ -220,7 +220,7 @@ void cpu_loop (CPUSPARCState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); switch (trapnr) { case TARGET_TT_SYSCALL: diff --git a/linux-user/xtensa/cpu_loop.c b/linux-user/xtensa/cpu_loop.c index 43a194fc4a..a0ff10eff8 100644 --- a/linux-user/xtensa/cpu_loop.c +++ b/linux-user/xtensa/cpu_loop.c @@ -133,7 +133,7 @@ void cpu_loop(CPUXtensaState *env) cpu_exec_start(cs); trapnr = cpu_exec(cs); cpu_exec_end(cs); - process_queued_cpu_work(cs); + qemu_process_cpu_events(cs); env->sregs[PS] &= ~PS_EXCM; switch (trapnr) { diff --git a/meson.build b/meson.build index 3d73873356..6ade30f36a 100644 --- a/meson.build +++ b/meson.build @@ -94,12 +94,12 @@ have_rust = have_rust and add_languages('rust', native: true, required: get_option('rust').disable_auto_if(not have_system)) if have_rust rustc = meson.get_compiler('rust') - if rustc.version().version_compare('<1.77.0') + if rustc.version().version_compare('<1.83.0') if get_option('rust').enabled() - error('rustc version ' + rustc.version() + ' is unsupported. Please upgrade to at least 1.77.0') + error('rustc version ' + rustc.version() + ' is unsupported. Please upgrade to at least 1.83.0') else warning('rustc version ' + rustc.version() + ' is unsupported, disabling Rust compilation.') - message('Please upgrade to at least 1.77.0 to use Rust.') + message('Please upgrade to at least 1.83.0 to use Rust.') have_rust = false endif endif @@ -1086,9 +1086,6 @@ glib = declare_dependency(dependencies: [glib_pc, gmodule], # TODO: remove this check and the corresponding workaround (qtree) when # the minimum supported glib is >= 2.75.3 glib_has_gslice = glib.version().version_compare('<2.75.3') -# Check whether glib has the aligned_alloc family of functions. -# <https://docs.gtk.org/glib/func.aligned_alloc.html> -glib_has_aligned_alloc = glib.version().version_compare('>=2.72.0') # override glib dep to include the above refinements meson.override_dependency('glib-2.0', glib) @@ -2702,7 +2699,6 @@ config_host_data.set('CONFIG_GETLOADAVG', cc.has_function('getloadavg')) config_host_data.set('HAVE_COPY_FILE_RANGE', cc.has_function('copy_file_range')) config_host_data.set('HAVE_GETIFADDRS', cc.has_function('getifaddrs')) config_host_data.set('HAVE_GLIB_WITH_SLICE_ALLOCATOR', glib_has_gslice) -config_host_data.set('HAVE_GLIB_WITH_ALIGNED_ALLOC', glib_has_aligned_alloc) config_host_data.set('HAVE_OPENPTY', cc.has_function('openpty', dependencies: util)) config_host_data.set('HAVE_STRCHRNUL', cc.has_function('strchrnul', prefix: osdep_prefix)) config_host_data.set('HAVE_SYSTEM_FUNCTION', cc.has_function('system', prefix: '#include <stdlib.h>')) diff --git a/pc-bios/multiboot_dma.bin b/pc-bios/multiboot_dma.bin index c0e2c3102a..e6d0c97093 100644 --- a/pc-bios/multiboot_dma.bin +++ b/pc-bios/multiboot_dma.bin Binary files differdiff --git a/pc-bios/optionrom/multiboot.S b/pc-bios/optionrom/multiboot.S index 181a4b03a3..c95e35c9cb 100644 --- a/pc-bios/optionrom/multiboot.S +++ b/pc-bios/optionrom/multiboot.S @@ -208,7 +208,7 @@ ljmp2: prot_jump: .long prot_mode .short 8 -.align 4, 0 +.align 8, 0 gdt: /* 0x00 */ .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 diff --git a/python/scripts/vendor.py b/python/scripts/vendor.py index b47db00743..33ac7a45de 100755 --- a/python/scripts/vendor.py +++ b/python/scripts/vendor.py @@ -41,8 +41,8 @@ def main() -> int: parser.parse_args() packages = { - "meson==1.8.1": - "374bbf71247e629475fc10b0bd2ef66fc418c2d8f4890572f74de0f97d0d42da", + "meson==1.9.0": + "45e51ddc41e37d961582d06e78c48e0f9039011587f3495c4d6b0781dad92357", } vendor_dir = Path(__file__, "..", "..", "wheels").resolve() diff --git a/python/wheels/meson-1.8.1-py3-none-any.whl b/python/wheels/meson-1.8.1-py3-none-any.whl deleted file mode 100644 index a885f0e18c..0000000000 --- a/python/wheels/meson-1.8.1-py3-none-any.whl +++ /dev/null Binary files differdiff --git a/python/wheels/meson-1.9.0-py3-none-any.whl b/python/wheels/meson-1.9.0-py3-none-any.whl new file mode 100644 index 0000000000..57cc75cb13 --- /dev/null +++ b/python/wheels/meson-1.9.0-py3-none-any.whl Binary files differdiff --git a/pythondeps.toml b/pythondeps.toml index d0f52b14f7..16fb2a989c 100644 --- a/pythondeps.toml +++ b/pythondeps.toml @@ -19,12 +19,12 @@ [meson] # The install key should match the version in python/wheels/ -meson = { accepted = ">=1.5.0", installed = "1.8.1", canary = "meson" } +meson = { accepted = ">=1.5.0", installed = "1.9.0", canary = "meson" } pycotap = { accepted = ">=1.1.0", installed = "1.3.1" } [meson-rust] # The install key should match the version in python/wheels/ -meson = { accepted = ">=1.8.1", installed = "1.8.1", canary = "meson" } +meson = { accepted = ">=1.9.0", installed = "1.9.0", canary = "meson" } [docs] # Please keep the installed versions in sync with docs/requirements.txt diff --git a/replay/replay-events.c b/replay/replay-events.c index 8959da9f1f..a96e47e774 100644 --- a/replay/replay-events.c +++ b/replay/replay-events.c @@ -118,7 +118,8 @@ void replay_add_event(ReplayAsyncEventKind event_kind, g_assert(replay_mutex_locked()); QTAILQ_INSERT_TAIL(&events_list, event, events); - qemu_cpu_kick(first_cpu); + /* Kick the TCG thread out of tcg_cpu_exec(). */ + cpu_exit(first_cpu); } void replay_bh_schedule_event(QEMUBH *bh) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4baf6ba663..eea928621a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -41,7 +41,33 @@ dependencies = [ name = "bits" version = "0.1.0" dependencies = [ - "qemu_api_macros", + "qemu_macros", +] + +[[package]] +name = "bql" +version = "0.1.0" +dependencies = [ + "migration", +] + +[[package]] +name = "chardev" +version = "0.1.0" +dependencies = [ + "bql", + "common", + "migration", + "qom", + "util", +] + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "libc", + "qemu_macros", ] [[package]] @@ -63,8 +89,27 @@ dependencies = [ name = "hpet" version = "0.1.0" dependencies = [ - "qemu_api", - "qemu_api_macros", + "bql", + "common", + "hwcore", + "migration", + "qom", + "system", + "util", +] + +[[package]] +name = "hwcore" +version = "0.1.0" +dependencies = [ + "bql", + "chardev", + "common", + "migration", + "qemu_macros", + "qom", + "system", + "util", ] [[package]] @@ -83,14 +128,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] +name = "migration" +version = "0.1.0" +dependencies = [ + "common", + "util", +] + +[[package]] name = "pl011" version = "0.1.0" dependencies = [ "bilge", "bilge-impl", "bits", - "qemu_api", - "qemu_api_macros", + "bql", + "chardev", + "common", + "hwcore", + "migration", + "qom", + "system", + "util", ] [[package]] @@ -126,22 +185,23 @@ dependencies = [ ] [[package]] -name = "qemu_api" +name = "qemu_macros" version = "0.1.0" dependencies = [ - "anyhow", - "foreign", - "libc", - "qemu_api_macros", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "qemu_api_macros" +name = "qom" version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bql", + "common", + "migration", + "qemu_macros", + "util", ] [[package]] @@ -165,12 +225,45 @@ dependencies = [ ] [[package]] +name = "system" +version = "0.1.0" +dependencies = [ + "common", + "qom", + "util", +] + +[[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "bql", + "chardev", + "common", + "hwcore", + "migration", + "qom", + "system", + "util", +] + +[[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] +name = "util" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "foreign", + "libc", +] + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6f8884eb30..d8183c614d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -2,10 +2,17 @@ resolver = "2" members = [ "bits", - "qemu-api-macros", - "qemu-api", + "bql", + "common", + "migration", + "qemu-macros", + "qom", + "system", + "hw/core", "hw/char/pl011", "hw/timer/hpet", + "util", + "tests", ] [workspace.package] @@ -13,7 +20,9 @@ edition = "2021" homepage = "https://www.qemu.org" license = "GPL-2.0-or-later" repository = "https://gitlab.com/qemu-project/qemu/" -rust-version = "1.77.0" +# don't forget to update docs/devel/rust.rst msrv +rust-version = "1.83.0" +authors = ["The QEMU Project Developers <qemu-devel@nongnu.org>"] [workspace.dependencies] anyhow = "~1.0" @@ -21,9 +30,7 @@ foreign = "~0.3.1" libc = "0.2.162" [workspace.lints.rust] -unexpected_cfgs = { level = "deny", check-cfg = [ - 'cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)', -] } +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(MESON)'] } # Occasionally, we may need to silence warnings and clippy lints that # were only introduced in newer Rust compiler versions. Do not croak diff --git a/rust/qemu-api/src/bindings.rs b/rust/bindings/src/lib.rs index b8104dea8b..5bf03b1370 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/bindings/src/lib.rs @@ -6,7 +6,6 @@ non_camel_case_types, non_snake_case, non_upper_case_globals, - unnecessary_transmutes, unsafe_op_in_unsafe_fn, clippy::pedantic, clippy::restriction, @@ -14,8 +13,7 @@ clippy::missing_const_for_fn, clippy::ptr_offset_with_cast, clippy::useless_transmute, - clippy::missing_safety_doc, - clippy::too_many_arguments + clippy::missing_safety_doc )] //! `bindgen`-generated declarations. @@ -56,3 +54,11 @@ unsafe impl Sync for VMStateField {} unsafe impl Send for VMStateInfo {} unsafe impl Sync for VMStateInfo {} + +// bindgen does not derive Default here +#[allow(clippy::derivable_impls)] +impl Default for VMStateFlags { + fn default() -> Self { + Self(0) + } +} diff --git a/rust/bits/Cargo.toml b/rust/bits/Cargo.toml index 1ff38a4117..7fce972b27 100644 --- a/rust/bits/Cargo.toml +++ b/rust/bits/Cargo.toml @@ -13,7 +13,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/bits/meson.build b/rust/bits/meson.build index 2a41e138c5..359ca86f15 100644 --- a/rust/bits/meson.build +++ b/rust/bits/meson.build @@ -3,7 +3,7 @@ _bits_rs = static_library( 'src/lib.rs', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', - dependencies: [qemu_api_macros], + dependencies: [qemu_macros], ) bits_rs = declare_dependency(link_with: _bits_rs) diff --git a/rust/bits/src/lib.rs b/rust/bits/src/lib.rs index d485d6bd11..d1141f7c88 100644 --- a/rust/bits/src/lib.rs +++ b/rust/bits/src/lib.rs @@ -165,19 +165,19 @@ macro_rules! bits { #[allow(dead_code)] #[inline(always)] - pub fn set(&mut self, rhs: Self) { + pub const fn set(&mut self, rhs: Self) { self.0 |= rhs.0; } #[allow(dead_code)] #[inline(always)] - pub fn clear(&mut self, rhs: Self) { + pub const fn clear(&mut self, rhs: Self) { self.0 &= !rhs.0; } #[allow(dead_code)] #[inline(always)] - pub fn toggle(&mut self, rhs: Self) { + pub const fn toggle(&mut self, rhs: Self) { self.0 ^= rhs.0; } @@ -380,14 +380,17 @@ macro_rules! bits { }; { $type:ty: $expr:expr } => { - ::qemu_api_macros::bits_const_internal! { $type @ ($expr) } + $crate::bits_const_internal! { $type @ ($expr) } }; { $type:ty as $int_type:ty: $expr:expr } => { - (::qemu_api_macros::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type + ($crate::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type }; } +#[doc(hidden)] +pub use qemu_macros::bits_const_internal; + #[cfg(test)] mod test { bits! { diff --git a/rust/bql/Cargo.toml b/rust/bql/Cargo.toml new file mode 100644 index 0000000000..1041bd4ea9 --- /dev/null +++ b/rust/bql/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bql" +version = "0.1.0" +description = "Rust bindings for QEMU/BQL" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +migration = { path = "../migration" } + +[features] +default = ["debug_cell"] +debug_cell = [] + +[lints] +workspace = true diff --git a/rust/bql/build.rs b/rust/bql/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/bql/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/bql/meson.build b/rust/bql/meson.build new file mode 100644 index 0000000000..f369209dfd --- /dev/null +++ b/rust/bql/meson.build @@ -0,0 +1,52 @@ +_bql_cfg = run_command(rustc_args, + '--config-headers', config_host_h, '--features', files('Cargo.toml'), + capture: true, check: true).stdout().strip().splitlines() + +if get_option('debug_mutex') + _bql_cfg += ['--cfg', 'feature="debug_cell"'] +endif + +# +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_bql_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common, +) + +_bql_rs = static_library( + 'bql', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/cell.rs', + ], + {'.': _bql_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + rust_args: _bql_cfg, + link_with: [_migration_rs], +) + +bql_rs = declare_dependency(link_with: [_bql_rs], + dependencies: [qemuutil]) + +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-bql-rs-doctests', + _bql_rs, + protocol: 'rust', + dependencies: bql_rs, + suite: ['doc', 'rust']) diff --git a/rust/bql/src/bindings.rs b/rust/bql/src/bindings.rs new file mode 100644 index 0000000000..9ffff12cde --- /dev/null +++ b/rust/bql/src/bindings.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); diff --git a/rust/qemu-api/src/cell.rs b/rust/bql/src/cell.rs index 27063b049d..24ab294b60 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/bql/src/cell.rs @@ -75,10 +75,10 @@ //! //! ### Example //! -//! ``` -//! # use qemu_api::prelude::*; -//! # use qemu_api::{cell::BqlRefCell, irq::InterruptSource, irq::IRQState}; -//! # use qemu_api::{sysbus::SysBusDevice, qom::Owned, qom::ParentField}; +//! ```ignore +//! # use bql::BqlRefCell; +//! # use qom::{Owned, ParentField}; +//! # use system::{InterruptSource, IRQState, SysBusDevice}; //! # const N_GPIOS: usize = 8; //! # struct PL061Registers { /* ... */ } //! # unsafe impl ObjectType for PL061State { @@ -141,109 +141,17 @@ //! Multiple immutable borrows are allowed via [`borrow`](BqlRefCell::borrow), //! or a single mutable borrow via [`borrow_mut`](BqlRefCell::borrow_mut). The //! thread will panic if these rules are violated or if the BQL is not held. -//! -//! ## Opaque wrappers -//! -//! The cell types from the previous section are useful at the boundaries -//! of code that requires interior mutability. When writing glue code that -//! interacts directly with C structs, however, it is useful to operate -//! at a lower level. -//! -//! C functions often violate Rust's fundamental assumptions about memory -//! safety by modifying memory even if it is shared. Furthermore, C structs -//! often start their life uninitialized and may be populated lazily. -//! -//! For this reason, this module provides the [`Opaque<T>`] type to opt out -//! of Rust's usual guarantees about the wrapped type. Access to the wrapped -//! value is always through raw pointers, obtained via methods like -//! [`as_mut_ptr()`](Opaque::as_mut_ptr) and [`as_ptr()`](Opaque::as_ptr). These -//! pointers can then be passed to C functions or dereferenced; both actions -//! require `unsafe` blocks, making it clear where safety guarantees must be -//! manually verified. For example -//! -//! ```ignore -//! unsafe { -//! let state = Opaque::<MyStruct>::uninit(); -//! qemu_struct_init(state.as_mut_ptr()); -//! } -//! ``` -//! -//! [`Opaque<T>`] will usually be wrapped one level further, so that -//! bridge methods can be added to the wrapper: -//! -//! ```ignore -//! pub struct MyStruct(Opaque<bindings::MyStruct>); -//! -//! impl MyStruct { -//! fn new() -> Pin<Box<MyStruct>> { -//! let result = Box::pin(unsafe { Opaque::uninit() }); -//! unsafe { qemu_struct_init(result.as_mut_ptr()) }; -//! result -//! } -//! } -//! ``` -//! -//! This pattern of wrapping bindgen-generated types in [`Opaque<T>`] provides -//! several advantages: -//! -//! * The choice of traits to be implemented is not limited by the -//! bindgen-generated code. For example, [`Drop`] can be added without -//! disabling [`Copy`] on the underlying bindgen type -//! -//! * [`Send`] and [`Sync`] implementations can be controlled by the wrapper -//! type rather than being automatically derived from the C struct's layout -//! -//! * Methods can be implemented in a separate crate from the bindgen-generated -//! bindings -//! -//! * [`Debug`](std::fmt::Debug) and [`Display`](std::fmt::Display) -//! implementations can be customized to be more readable than the raw C -//! struct representation -//! -//! The [`Opaque<T>`] type does not include BQL validation; it is possible to -//! assert in the code that the right lock is taken, to use it together -//! with a custom lock guard type, or to let C code take the lock, as -//! appropriate. It is also possible to use it with non-thread-safe -//! types, since by default (unlike [`BqlCell`] and [`BqlRefCell`] -//! it is neither `Sync` nor `Send`. -//! -//! While [`Opaque<T>`] is necessary for C interop, it should be used sparingly -//! and only at FFI boundaries. For QEMU-specific types that need interior -//! mutability, prefer [`BqlCell`] or [`BqlRefCell`]. - use std::{ cell::{Cell, UnsafeCell}, cmp::Ordering, fmt, - marker::{PhantomData, PhantomPinned}, - mem::{self, MaybeUninit}, + marker::PhantomData, + mem, ops::{Deref, DerefMut}, ptr::NonNull, }; -use crate::bindings; - -/// An internal function that is used by doctests. -pub fn bql_start_test() { - // SAFETY: integration tests are run with --test-threads=1, while - // unit tests and doctests are not multithreaded and do not have - // any BQL-protected data. Just set bql_locked to true. - unsafe { - bindings::rust_bql_mock_lock(); - } -} - -pub fn bql_locked() -> bool { - // SAFETY: the function does nothing but return a thread-local bool - unsafe { bindings::bql_locked() } -} - -fn bql_block_unlock(increase: bool) { - // SAFETY: this only adjusts a counter - unsafe { - bindings::bql_block_unlock(increase); - } -} +use migration::impl_vmstate_transparent; /// A mutable memory location that is protected by the Big QEMU Lock. /// @@ -323,8 +231,8 @@ impl<T> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// ``` @@ -340,8 +248,8 @@ impl<T> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// @@ -358,8 +266,8 @@ impl<T> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let cell = BqlCell::new(5); /// assert_eq!(cell.get(), 5); @@ -368,7 +276,7 @@ impl<T> BqlCell<T> { /// ``` #[inline] pub fn replace(&self, val: T) -> T { - assert!(bql_locked()); + assert!(crate::is_locked()); // SAFETY: This can cause data races if called from multiple threads, // but it won't happen as long as C code accesses the value // under BQL protection only. @@ -380,8 +288,8 @@ impl<T> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// let five = c.into_inner(); @@ -389,7 +297,7 @@ impl<T> BqlCell<T> { /// assert_eq!(five, 5); /// ``` pub fn into_inner(self) -> T { - assert!(bql_locked()); + assert!(crate::is_locked()); self.value.into_inner() } } @@ -400,8 +308,8 @@ impl<T: Copy> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// @@ -409,7 +317,7 @@ impl<T: Copy> BqlCell<T> { /// ``` #[inline] pub fn get(&self) -> T { - assert!(bql_locked()); + assert!(crate::is_locked()); // SAFETY: This can cause data races if called from multiple threads, // but it won't happen as long as C code accesses the value // under BQL protection only. @@ -423,8 +331,8 @@ impl<T> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// @@ -442,8 +350,8 @@ impl<T: Default> BqlCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlCell; + /// # bql::start_test(); /// /// let c = BqlCell::new(5); /// let five = c.take(); @@ -456,6 +364,8 @@ impl<T: Default> BqlCell<T> { } } +impl_vmstate_transparent!(crate::cell::BqlCell<T> where T: VMState); + /// A mutable memory location with dynamically checked borrow rules, /// protected by the Big QEMU Lock. /// @@ -512,7 +422,7 @@ impl<T> BqlRefCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlRefCell; + /// use bql::BqlRefCell; /// /// let c = BqlRefCell::new(5); /// ``` @@ -571,8 +481,8 @@ impl<T> BqlRefCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlRefCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlRefCell; + /// # bql::start_test(); /// /// let c = BqlRefCell::new(5); /// @@ -583,8 +493,8 @@ impl<T> BqlRefCell<T> { /// An example of panic: /// /// ```should_panic - /// use qemu_api::cell::BqlRefCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlRefCell; + /// # bql::start_test(); /// /// let c = BqlRefCell::new(5); /// @@ -601,7 +511,7 @@ impl<T> BqlRefCell<T> { self.borrowed_at.set(Some(std::panic::Location::caller())); } - bql_block_unlock(true); + crate::block_unlock(true); // SAFETY: `BorrowRef` ensures that there is only immutable access // to the value while borrowed. @@ -625,8 +535,8 @@ impl<T> BqlRefCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlRefCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlRefCell; + /// # bql::start_test(); /// /// let c = BqlRefCell::new("hello".to_owned()); /// @@ -638,8 +548,8 @@ impl<T> BqlRefCell<T> { /// An example of panic: /// /// ```should_panic - /// use qemu_api::cell::BqlRefCell; - /// # qemu_api::cell::bql_start_test(); + /// use bql::BqlRefCell; + /// # bql::start_test(); /// /// let c = BqlRefCell::new(5); /// let m = c.borrow(); @@ -656,7 +566,7 @@ impl<T> BqlRefCell<T> { } // SAFETY: this only adjusts a counter - bql_block_unlock(true); + crate::block_unlock(true); // SAFETY: `BorrowRefMut` guarantees unique access. let value = unsafe { NonNull::new_unchecked(self.value.get()) }; @@ -675,7 +585,7 @@ impl<T> BqlRefCell<T> { /// # Examples /// /// ``` - /// use qemu_api::cell::BqlRefCell; + /// use bql::BqlRefCell; /// /// let c = BqlRefCell::new(5); /// @@ -764,6 +674,8 @@ impl<T> From<T> for BqlRefCell<T> { } } +impl_vmstate_transparent!(crate::cell::BqlRefCell<T> where T: VMState); + struct BorrowRef<'b> { borrow: &'b Cell<BorrowFlag>, } @@ -800,7 +712,7 @@ impl Drop for BorrowRef<'_> { let borrow = self.borrow.get(); debug_assert!(is_reading(borrow)); self.borrow.set(borrow - 1); - bql_block_unlock(false) + crate::block_unlock(false) } } @@ -890,7 +802,7 @@ impl Drop for BorrowRefMut<'_> { let borrow = self.borrow.get(); debug_assert!(is_writing(borrow)); self.borrow.set(borrow + 1); - bql_block_unlock(false) + crate::block_unlock(false) } } @@ -935,167 +847,3 @@ impl<T: fmt::Display> fmt::Display for BqlRefMut<'_, T> { (**self).fmt(f) } } - -/// Stores an opaque value that is shared with C code. -/// -/// Often, C structs can changed when calling a C function even if they are -/// behind a shared Rust reference, or they can be initialized lazily and have -/// invalid bit patterns (e.g. `3` for a [`bool`]). This goes against Rust's -/// strict aliasing rules, which normally prevent mutation through shared -/// references. -/// -/// Wrapping the struct with `Opaque<T>` ensures that the Rust compiler does not -/// assume the usual constraints that Rust structs require, and allows using -/// shared references on the Rust side. -/// -/// `Opaque<T>` is `#[repr(transparent)]`, so that it matches the memory layout -/// of `T`. -#[repr(transparent)] -pub struct Opaque<T> { - value: UnsafeCell<MaybeUninit<T>>, - // PhantomPinned also allows multiple references to the `Opaque<T>`, i.e. - // one `&mut Opaque<T>` can coexist with a `&mut T` or any number of `&T`; - // see https://docs.rs/pinned-aliasable/latest/pinned_aliasable/. - _pin: PhantomPinned, -} - -impl<T> Opaque<T> { - /// Creates a new shared reference from a C pointer - /// - /// # Safety - /// - /// The pointer must be valid, though it need not point to a valid value. - pub unsafe fn from_raw<'a>(ptr: *mut T) -> &'a Self { - let ptr = NonNull::new(ptr).unwrap().cast::<Self>(); - // SAFETY: Self is a transparent wrapper over T - unsafe { ptr.as_ref() } - } - - /// Creates a new opaque object with uninitialized contents. - /// - /// # Safety - /// - /// Ultimately the pointer to the returned value will be dereferenced - /// in another `unsafe` block, for example when passing it to a C function, - /// but the functions containing the dereference are usually safe. The - /// value returned from `uninit()` must be initialized and pinned before - /// calling them. - #[allow(clippy::missing_const_for_fn)] - pub unsafe fn uninit() -> Self { - Self { - value: UnsafeCell::new(MaybeUninit::uninit()), - _pin: PhantomPinned, - } - } - - /// Creates a new opaque object with zeroed contents. - /// - /// # Safety - /// - /// Ultimately the pointer to the returned value will be dereferenced - /// in another `unsafe` block, for example when passing it to a C function, - /// but the functions containing the dereference are usually safe. The - /// value returned from `uninit()` must be pinned (and possibly initialized) - /// before calling them. - #[allow(clippy::missing_const_for_fn)] - pub unsafe fn zeroed() -> Self { - Self { - value: UnsafeCell::new(MaybeUninit::zeroed()), - _pin: PhantomPinned, - } - } - - /// Returns a raw mutable pointer to the opaque data. - pub const fn as_mut_ptr(&self) -> *mut T { - UnsafeCell::get(&self.value).cast() - } - - /// Returns a raw pointer to the opaque data. - pub const fn as_ptr(&self) -> *const T { - self.as_mut_ptr().cast_const() - } - - /// Returns a raw pointer to the opaque data that can be passed to a - /// C function as `void *`. - pub const fn as_void_ptr(&self) -> *mut std::ffi::c_void { - UnsafeCell::get(&self.value).cast() - } - - /// Converts a raw pointer to the wrapped type. - pub const fn raw_get(slot: *mut Self) -> *mut T { - // Compare with Linux's raw_get method, which goes through an UnsafeCell - // because it takes a *const Self instead. - slot.cast() - } -} - -impl<T> fmt::Debug for Opaque<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut name: String = "Opaque<".to_string(); - name += std::any::type_name::<T>(); - name += ">"; - f.debug_tuple(&name).field(&self.as_ptr()).finish() - } -} - -impl<T: Default> Opaque<T> { - /// Creates a new opaque object with default contents. - /// - /// # Safety - /// - /// Ultimately the pointer to the returned value will be dereferenced - /// in another `unsafe` block, for example when passing it to a C function, - /// but the functions containing the dereference are usually safe. The - /// value returned from `uninit()` must be pinned before calling them. - pub unsafe fn new() -> Self { - Self { - value: UnsafeCell::new(MaybeUninit::new(T::default())), - _pin: PhantomPinned, - } - } -} - -/// Annotates [`Self`] as a transparent wrapper for another type. -/// -/// Usually defined via the [`qemu_api_macros::Wrapper`] derive macro. -/// -/// # Examples -/// -/// ``` -/// # use std::mem::ManuallyDrop; -/// # use qemu_api::cell::Wrapper; -/// #[repr(transparent)] -/// pub struct Example { -/// inner: ManuallyDrop<String>, -/// } -/// -/// unsafe impl Wrapper for Example { -/// type Wrapped = String; -/// } -/// ``` -/// -/// # Safety -/// -/// `Self` must be a `#[repr(transparent)]` wrapper for the `Wrapped` type, -/// whether directly or indirectly. -/// -/// # Methods -/// -/// By convention, types that implement Wrapper also implement the following -/// methods: -/// -/// ```ignore -/// pub const unsafe fn from_raw<'a>(value: *mut Self::Wrapped) -> &'a Self; -/// pub const unsafe fn as_mut_ptr(&self) -> *mut Self::Wrapped; -/// pub const unsafe fn as_ptr(&self) -> *const Self::Wrapped; -/// pub const unsafe fn raw_get(slot: *mut Self) -> *const Self::Wrapped; -/// ``` -/// -/// They are not defined here to allow them to be `const`. -pub unsafe trait Wrapper { - type Wrapped; -} - -unsafe impl<T> Wrapper for Opaque<T> { - type Wrapped = T; -} diff --git a/rust/bql/src/lib.rs b/rust/bql/src/lib.rs new file mode 100644 index 0000000000..ef08221e9c --- /dev/null +++ b/rust/bql/src/lib.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +mod bindings; +use bindings::{bql_block_unlock, bql_locked, rust_bql_mock_lock}; + +mod cell; +pub use cell::*; + +/// An internal function that is used by doctests. +pub fn start_test() { + // SAFETY: integration tests are run with --test-threads=1, while + // unit tests and doctests are not multithreaded and do not have + // any BQL-protected data. Just set bql_locked to true. + unsafe { + rust_bql_mock_lock(); + } +} + +pub fn is_locked() -> bool { + // SAFETY: the function does nothing but return a thread-local bool + unsafe { bql_locked() } +} + +pub fn block_unlock(increase: bool) { + // SAFETY: this only adjusts a counter + unsafe { + bql_block_unlock(increase); + } +} diff --git a/rust/bql/wrapper.h b/rust/bql/wrapper.h new file mode 100644 index 0000000000..2ef9a96e1d --- /dev/null +++ b/rust/bql/wrapper.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "qemu/main-loop.h" diff --git a/rust/chardev/Cargo.toml b/rust/chardev/Cargo.toml new file mode 100644 index 0000000000..3e77972546 --- /dev/null +++ b/rust/chardev/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "chardev" +version = "0.1.0" +description = "Rust bindings for QEMU/chardev" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../common" } +bql = { path = "../bql" } +migration = { path = "../migration" } +qom = { path = "../qom" } +util = { path = "../util" } + +[lints] +workspace = true diff --git a/rust/chardev/build.rs b/rust/chardev/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/chardev/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/chardev/meson.build b/rust/chardev/meson.build new file mode 100644 index 0000000000..370895c111 --- /dev/null +++ b/rust/chardev/meson.build @@ -0,0 +1,41 @@ +c_enums = [ + 'QEMUChrEvent', +] +_chardev_bindgen_args = [] +foreach enum : c_enums + _chardev_bindgen_args += ['--rustified-enum', enum] +endforeach + +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_chardev_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _chardev_bindgen_args, +) + +_chardev_rs = static_library( + 'chardev', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/chardev.rs', + ], + {'.': _chardev_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_bql_rs, _migration_rs, _qom_rs, _util_rs], + dependencies: [common_rs, qemu_macros], +) + +chardev_rs = declare_dependency(link_with: [_chardev_rs], dependencies: [chardev, qemuutil]) diff --git a/rust/chardev/src/bindings.rs b/rust/chardev/src/bindings.rs new file mode 100644 index 0000000000..2d98026d62 --- /dev/null +++ b/rust/chardev/src/bindings.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +use common::Zeroable; + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); + +// SAFETY: these are implemented in C; the bindings need to assert that the +// BQL is taken, either directly or via `BqlCell` and `BqlRefCell`. +// When bindings for character devices are introduced, this can be +// moved to the Opaque<> wrapper in src/chardev.rs. +unsafe impl Send for CharBackend {} +unsafe impl Sync for CharBackend {} + +unsafe impl Zeroable for CharBackend {} diff --git a/rust/qemu-api/src/chardev.rs b/rust/chardev/src/chardev.rs index 6e0590d758..2014479674 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/chardev/src/chardev.rs @@ -18,16 +18,15 @@ use std::{ slice, }; -use crate::{ - bindings, - callbacks::FnCall, - cell::{BqlRefMut, Opaque}, - prelude::*, -}; +use bql::{BqlRefCell, BqlRefMut}; +use common::{callbacks::FnCall, errno, Opaque}; +use qom::prelude::*; + +use crate::bindings; /// A safe wrapper around [`bindings::Chardev`]. #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(common::Wrapper)] pub struct Chardev(Opaque<bindings::Chardev>); pub type ChardevClass = bindings::ChardevClass; @@ -43,13 +42,15 @@ pub struct CharBackend { _pin: PhantomPinned, } -impl Write for BqlRefMut<'_, bindings::CharBackend> { +pub struct CharBackendMut<'a>(BqlRefMut<'a, bindings::CharBackend>); + +impl Write for CharBackendMut<'_> { fn flush(&mut self) -> io::Result<()> { Ok(()) } fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - let chr: &mut bindings::CharBackend = self; + let chr: &mut bindings::CharBackend = &mut self.0; let len = buf.len().try_into().unwrap(); let r = unsafe { bindings::qemu_chr_fe_write(addr_of_mut!(*chr), buf.as_ptr(), len) }; @@ -57,7 +58,7 @@ impl Write for BqlRefMut<'_, bindings::CharBackend> { } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - let chr: &mut bindings::CharBackend = self; + let chr: &mut bindings::CharBackend = &mut self.0; let len = buf.len().try_into().unwrap(); let r = unsafe { bindings::qemu_chr_fe_write_all(addr_of_mut!(*chr), buf.as_ptr(), len) }; @@ -138,7 +139,7 @@ impl CharBackend { F::call((owner, event)) } - let _: () = CanReceiveFn::ASSERT_IS_SOME; + const { assert!(CanReceiveFn::IS_SOME) }; let receive_cb: Option<unsafe extern "C" fn(*mut c_void, *const u8, c_int)> = if ReceiveFn::is_some() { Some(rust_receive_cb::<T, ReceiveFn>) @@ -197,7 +198,7 @@ impl CharBackend { /// the big QEMU lock while the character device is borrowed, as /// that might cause C code to write to the character device. pub fn borrow_mut(&self) -> impl Write + '_ { - self.inner.borrow_mut() + CharBackendMut(self.inner.borrow_mut()) } /// Send a continuous stream of zero bits on the line if `enabled` is diff --git a/rust/chardev/src/lib.rs b/rust/chardev/src/lib.rs new file mode 100644 index 0000000000..2e549f99d9 --- /dev/null +++ b/rust/chardev/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub mod bindings; + +mod chardev; +pub use chardev::*; diff --git a/rust/chardev/wrapper.h b/rust/chardev/wrapper.h new file mode 100644 index 0000000000..65ede6ea6d --- /dev/null +++ b/rust/chardev/wrapper.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "chardev/char-fe.h" +#include "chardev/char-serial.h" diff --git a/rust/common/Cargo.toml b/rust/common/Cargo.toml new file mode 100644 index 0000000000..0e1b4fc505 --- /dev/null +++ b/rust/common/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "common" +version = "0.1.0" +description = "Rust common code for QEMU" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +libc.workspace = true +qemu_macros = { path = "../qemu-macros" } + +[lints] +workspace = true diff --git a/rust/common/meson.build b/rust/common/meson.build new file mode 100644 index 0000000000..b805e0faf5 --- /dev/null +++ b/rust/common/meson.build @@ -0,0 +1,34 @@ +_common_cfg = run_command(rustc_args, + '--config-headers', config_host_h, '--features', files('Cargo.toml'), + capture: true, check: true).stdout().strip().splitlines() + +_common_rs = static_library( + 'common', + structured_sources( + [ + 'src/lib.rs', + 'src/assertions.rs', + 'src/bitops.rs', + 'src/callbacks.rs', + 'src/errno.rs', + 'src/opaque.rs', + 'src/uninit.rs', + 'src/zeroable.rs', + ], + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + rust_args: _common_cfg, + dependencies: [libc_rs, qemu_macros], +) + +common_rs = declare_dependency(link_with: [_common_rs]) + +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-common-doctests', + _common_rs, + protocol: 'rust', + dependencies: common_rs, + suite: ['doc', 'rust']) diff --git a/rust/qemu-api/src/assertions.rs b/rust/common/src/assertions.rs index a2d38c877d..91f83a5d3d 100644 --- a/rust/qemu-api/src/assertions.rs +++ b/rust/common/src/assertions.rs @@ -8,7 +8,7 @@ //! types match the expectations of C code. //! //! Documentation is hidden because it only exposes macros, which -//! are exported directly from `qemu_api`. +//! are exported directly from `common`. // Based on https://stackoverflow.com/questions/64251852/x/70978292#70978292 // (stackoverflow answers are released under MIT license). @@ -27,7 +27,7 @@ impl<T> EqType for T { /// # Examples /// /// ``` -/// # use qemu_api::assert_same_type; +/// # use common::assert_same_type; /// # use std::ops::Deref; /// assert_same_type!(u32, u32); /// assert_same_type!(<Box<u32> as Deref>::Target, u32); @@ -36,7 +36,7 @@ impl<T> EqType for T { /// Different types will cause a compile failure /// /// ```compile_fail -/// # use qemu_api::assert_same_type; +/// # use common::assert_same_type; /// assert_same_type!(&Box<u32>, &u32); /// ``` #[macro_export] @@ -61,7 +61,7 @@ macro_rules! assert_same_type { /// # Examples /// /// ``` -/// # use qemu_api::assert_field_type; +/// # use common::assert_field_type; /// pub struct A { /// field1: u32, /// } @@ -72,7 +72,7 @@ macro_rules! assert_same_type { /// Different types will cause a compile failure /// /// ```compile_fail -/// # use qemu_api::assert_field_type; +/// # use common::assert_field_type; /// # pub struct A { field1: u32 } /// assert_field_type!(A, field1, i32); /// ``` @@ -81,8 +81,8 @@ macro_rules! assert_field_type { (@internal $param_name:ident, $ti:ty, $t:ty, $($field:tt)*) => { const _: () = { #[allow(unused)] - fn assert_field_type($param_name: &$t) { - fn types_must_be_equal<T, U>(_: &T) + const fn assert_field_type($param_name: &$t) { + const fn types_must_be_equal<T, U>(_: &T) where T: $crate::assertions::EqType<Itself = U>, { @@ -95,10 +95,6 @@ macro_rules! assert_field_type { ($t:ty, $i:tt, $ti:ty) => { $crate::assert_field_type!(@internal v, $ti, $t, v.$i); }; - - ($t:ty, $i:tt, $ti:ty, num = $num:ident) => { - $crate::assert_field_type!(@internal v, $ti, $t, v.$i[0]); - }; } /// Assert that an expression matches a pattern. This can also be @@ -107,7 +103,7 @@ macro_rules! assert_field_type { /// # Examples /// /// ``` -/// # use qemu_api::assert_match; +/// # use common::assert_match; /// // JoinHandle does not implement `Eq`, therefore the result /// // does not either. /// let result: Result<std::thread::JoinHandle<()>, u32> = Err(42); @@ -136,12 +132,12 @@ macro_rules! assert_match { /// # Examples /// /// ``` -/// # use qemu_api::static_assert; +/// # use common::static_assert; /// static_assert!("abc".len() == 3); /// ``` /// /// ```compile_fail -/// # use qemu_api::static_assert; +/// # use common::static_assert; /// static_assert!("abc".len() == 2); // does not compile /// ``` #[macro_export] diff --git a/rust/qemu-api/src/bitops.rs b/rust/common/src/bitops.rs index b1e3a530ab..06c78c3b8a 100644 --- a/rust/qemu-api/src/bitops.rs +++ b/rust/common/src/bitops.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later //! This module provides bit operation extensions to integer types. -//! It is usually included via the `qemu_api` prelude. use std::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, diff --git a/rust/qemu-api/src/callbacks.rs b/rust/common/src/callbacks.rs index 9642a16eb8..b8898fe96f 100644 --- a/rust/qemu-api/src/callbacks.rs +++ b/rust/common/src/callbacks.rs @@ -55,7 +55,7 @@ use std::{mem, ptr::NonNull}; /// # Examples /// /// ``` -/// # use qemu_api::callbacks::FnCall; +/// # use common::callbacks::FnCall; /// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { /// F::call((s,)) /// } @@ -71,7 +71,7 @@ use std::{mem, ptr::NonNull}; /// Attempting to pass a non-zero-sized closure causes a compile-time failure: /// /// ```compile_fail -/// # use qemu_api::callbacks::FnCall; +/// # use common::callbacks::FnCall; /// # fn call_it<'a, F: FnCall<(&'a str,), String>>(_f: &F, s: &'a str) -> String { /// # F::call((s,)) /// # } @@ -82,7 +82,7 @@ use std::{mem, ptr::NonNull}; /// `()` can be used to indicate "no function": /// /// ``` -/// # use qemu_api::callbacks::FnCall; +/// # use common::callbacks::FnCall; /// fn optional<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> Option<String> { /// if F::IS_SOME { /// Some(F::call((s,))) @@ -97,7 +97,7 @@ use std::{mem, ptr::NonNull}; /// Invoking `F::call` will then be a run-time error. /// /// ```should_panic -/// # use qemu_api::callbacks::FnCall; +/// # use common::callbacks::FnCall; /// # fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { /// # F::call((s,)) /// # } @@ -113,31 +113,6 @@ use std::{mem, ptr::NonNull}; /// This is always true for zero-capture closures and function pointers, as long /// as the code is able to name the function in the first place. pub unsafe trait FnCall<Args, R = ()>: 'static + Sync + Sized { - /// Referring to this internal constant asserts that the `Self` type is - /// zero-sized. Can be replaced by an inline const expression in - /// Rust 1.79.0+. - const ASSERT_ZERO_SIZED: () = { assert!(mem::size_of::<Self>() == 0) }; - - /// Referring to this constant asserts that the `Self` type is an actual - /// function type, which can be used to catch incorrect use of `()` - /// at compile time. - /// - /// # Examples - /// - /// ```compile_fail - /// # use qemu_api::callbacks::FnCall; - /// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { - /// let _: () = F::ASSERT_IS_SOME; - /// F::call((s,)) - /// } - /// - /// let s: String = call_it((), "hello world"); // does not compile - /// ``` - /// - /// Note that this can be more simply `const { assert!(F::IS_SOME) }` in - /// Rust 1.79.0 or newer. - const ASSERT_IS_SOME: () = { assert!(Self::IS_SOME) }; - /// `true` if `Self` is an actual function type and not `()`. /// /// # Examples @@ -145,7 +120,7 @@ pub unsafe trait FnCall<Args, R = ()>: 'static + Sync + Sized { /// You can use `IS_SOME` to catch this at compile time: /// /// ```compile_fail - /// # use qemu_api::callbacks::FnCall; + /// # use common::callbacks::FnCall; /// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { /// const { assert!(F::IS_SOME) } /// F::call((s,)) @@ -195,7 +170,7 @@ macro_rules! impl_call { #[inline(always)] fn call(a: ($($args,)*)) -> R { - let _: () = Self::ASSERT_ZERO_SIZED; + const { assert!(mem::size_of::<Self>() == 0) }; // SAFETY: the safety of this method is the condition for implementing // `FnCall`. As to the `NonNull` idiom to create a zero-sized type, diff --git a/rust/qemu-api/src/errno.rs b/rust/common/src/errno.rs index 18d101448b..64b2933b07 100644 --- a/rust/qemu-api/src/errno.rs +++ b/rust/common/src/errno.rs @@ -7,7 +7,10 @@ //! convention. This module provides functions to portably convert an integer //! into an [`io::Result`] and back. -use std::{convert::TryFrom, io, io::ErrorKind}; +use std::{ + convert::{self, TryFrom}, + io::{self, ErrorKind}, +}; /// An `errno` value that can be converted into an [`io::Error`] pub struct Errno(pub u16); @@ -99,6 +102,12 @@ impl From<io::Error> for Errno { } } +impl From<convert::Infallible> for Errno { + fn from(_value: convert::Infallible) -> Errno { + panic!("unreachable") + } +} + /// Internal traits; used to enable [`into_io_result`] and [`into_neg_errno`] /// for the "right" set of types. mod traits { @@ -176,7 +185,7 @@ use traits::{GetErrno, MergeErrno}; /// are interpreted as negated `errno` and turned into an `Err`. /// /// ``` -/// # use qemu_api::errno::into_io_result; +/// # use common::errno::into_io_result; /// # use std::io::ErrorKind; /// let ok = into_io_result(1i32).unwrap(); /// assert_eq!(ok, 1u32); @@ -192,7 +201,7 @@ use traits::{GetErrno, MergeErrno}; /// likely overflows and will panic: /// /// ```should_panic -/// # use qemu_api::errno::into_io_result; +/// # use common::errno::into_io_result; /// # #[allow(dead_code)] /// let err = into_io_result(-0x1234_5678i32); // panic /// ``` @@ -204,7 +213,7 @@ pub fn into_io_result<T: GetErrno>(value: T) -> io::Result<T::Out> { /// values to report errors. /// /// ``` -/// # use qemu_api::errno::into_neg_errno; +/// # use common::errno::into_neg_errno; /// # use std::io::{self, ErrorKind}; /// let ok: io::Result<()> = Ok(()); /// assert_eq!(into_neg_errno(ok), 0); @@ -223,7 +232,7 @@ pub fn into_io_result<T: GetErrno>(value: T) -> io::Result<T::Out> { /// positive: /// /// ```should_panic -/// # use qemu_api::errno::into_neg_errno; +/// # use common::errno::into_neg_errno; /// # use std::io; /// let err: io::Result<u32> = Ok(0x8899_AABB); /// into_neg_errno(err) // panic diff --git a/rust/common/src/lib.rs b/rust/common/src/lib.rs new file mode 100644 index 0000000000..8311bf945d --- /dev/null +++ b/rust/common/src/lib.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub use qemu_macros::{TryInto, Wrapper}; + +pub mod assertions; + +pub mod bitops; + +pub mod callbacks; +pub use callbacks::FnCall; + +pub mod errno; +pub use errno::Errno; + +pub mod opaque; +pub use opaque::{Opaque, Wrapper}; + +pub mod uninit; +pub use uninit::MaybeUninitField; + +pub mod zeroable; +pub use zeroable::Zeroable; diff --git a/rust/common/src/opaque.rs b/rust/common/src/opaque.rs new file mode 100644 index 0000000000..c941fb4546 --- /dev/null +++ b/rust/common/src/opaque.rs @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT + +//! ## Opaque wrappers +//! +//! The cell types from the previous section are useful at the boundaries +//! of code that requires interior mutability. When writing glue code that +//! interacts directly with C structs, however, it is useful to operate +//! at a lower level. +//! +//! C functions often violate Rust's fundamental assumptions about memory +//! safety by modifying memory even if it is shared. Furthermore, C structs +//! often start their life uninitialized and may be populated lazily. +//! +//! For this reason, this module provides the [`Opaque<T>`] type to opt out +//! of Rust's usual guarantees about the wrapped type. Access to the wrapped +//! value is always through raw pointers, obtained via methods like +//! [`as_mut_ptr()`](Opaque::as_mut_ptr) and [`as_ptr()`](Opaque::as_ptr). These +//! pointers can then be passed to C functions or dereferenced; both actions +//! require `unsafe` blocks, making it clear where safety guarantees must be +//! manually verified. For example +//! +//! ```ignore +//! unsafe { +//! let state = Opaque::<MyStruct>::uninit(); +//! qemu_struct_init(state.as_mut_ptr()); +//! } +//! ``` +//! +//! [`Opaque<T>`] will usually be wrapped one level further, so that +//! bridge methods can be added to the wrapper: +//! +//! ```ignore +//! pub struct MyStruct(Opaque<bindings::MyStruct>); +//! +//! impl MyStruct { +//! fn new() -> Pin<Box<MyStruct>> { +//! let result = Box::pin(unsafe { Opaque::uninit() }); +//! unsafe { qemu_struct_init(result.as_mut_ptr()) }; +//! result +//! } +//! } +//! ``` +//! +//! This pattern of wrapping bindgen-generated types in [`Opaque<T>`] provides +//! several advantages: +//! +//! * The choice of traits to be implemented is not limited by the +//! bindgen-generated code. For example, [`Drop`] can be added without +//! disabling [`Copy`] on the underlying bindgen type +//! +//! * [`Send`] and [`Sync`] implementations can be controlled by the wrapper +//! type rather than being automatically derived from the C struct's layout +//! +//! * Methods can be implemented in a separate crate from the bindgen-generated +//! bindings +//! +//! * [`Debug`](std::fmt::Debug) and [`Display`](std::fmt::Display) +//! implementations can be customized to be more readable than the raw C +//! struct representation +//! +//! The [`Opaque<T>`] type does not include BQL validation; it is possible to +//! assert in the code that the right lock is taken, to use it together +//! with a custom lock guard type, or to let C code take the lock, as +//! appropriate. It is also possible to use it with non-thread-safe +//! types, since by default (unlike [`BqlCell`] and [`BqlRefCell`] +//! it is neither `Sync` nor `Send`. +//! +//! While [`Opaque<T>`] is necessary for C interop, it should be used sparingly +//! and only at FFI boundaries. For QEMU-specific types that need interior +//! mutability, prefer [`BqlCell`] or [`BqlRefCell`]. +//! +//! [`BqlCell`]: ../../bql/cell/struct.BqlCell.html +//! [`BqlRefCell`]: ../../bql/cell/struct.BqlRefCell.html +use std::{cell::UnsafeCell, fmt, marker::PhantomPinned, mem::MaybeUninit, ptr::NonNull}; + +/// Stores an opaque value that is shared with C code. +/// +/// Often, C structs can changed when calling a C function even if they are +/// behind a shared Rust reference, or they can be initialized lazily and have +/// invalid bit patterns (e.g. `3` for a [`bool`]). This goes against Rust's +/// strict aliasing rules, which normally prevent mutation through shared +/// references. +/// +/// Wrapping the struct with `Opaque<T>` ensures that the Rust compiler does not +/// assume the usual constraints that Rust structs require, and allows using +/// shared references on the Rust side. +/// +/// `Opaque<T>` is `#[repr(transparent)]`, so that it matches the memory layout +/// of `T`. +#[repr(transparent)] +pub struct Opaque<T> { + value: UnsafeCell<MaybeUninit<T>>, + // PhantomPinned also allows multiple references to the `Opaque<T>`, i.e. + // one `&mut Opaque<T>` can coexist with a `&mut T` or any number of `&T`; + // see https://docs.rs/pinned-aliasable/latest/pinned_aliasable/. + _pin: PhantomPinned, +} + +impl<T> Opaque<T> { + /// Creates a new shared reference from a C pointer + /// + /// # Safety + /// + /// The pointer must be valid, though it need not point to a valid value. + pub unsafe fn from_raw<'a>(ptr: *mut T) -> &'a Self { + let ptr = NonNull::new(ptr).unwrap().cast::<Self>(); + // SAFETY: Self is a transparent wrapper over T + unsafe { ptr.as_ref() } + } + + /// Creates a new opaque object with uninitialized contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be initialized and pinned before + /// calling them. + pub const unsafe fn uninit() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::uninit()), + _pin: PhantomPinned, + } + } + + /// Creates a new opaque object with zeroed contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned (and possibly initialized) + /// before calling them. + pub const unsafe fn zeroed() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::zeroed()), + _pin: PhantomPinned, + } + } + + /// Returns a raw mutable pointer to the opaque data. + pub const fn as_mut_ptr(&self) -> *mut T { + UnsafeCell::get(&self.value).cast() + } + + /// Returns a raw pointer to the opaque data. + pub const fn as_ptr(&self) -> *const T { + self.as_mut_ptr().cast_const() + } + + /// Returns a raw pointer to the opaque data that can be passed to a + /// C function as `void *`. + pub const fn as_void_ptr(&self) -> *mut std::ffi::c_void { + UnsafeCell::get(&self.value).cast() + } + + /// Converts a raw pointer to the wrapped type. + pub const fn raw_get(slot: *mut Self) -> *mut T { + // Compare with Linux's raw_get method, which goes through an UnsafeCell + // because it takes a *const Self instead. + slot.cast() + } +} + +impl<T> fmt::Debug for Opaque<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut name: String = "Opaque<".to_string(); + name += std::any::type_name::<T>(); + name += ">"; + f.debug_tuple(&name).field(&self.as_ptr()).finish() + } +} + +impl<T: Default> Opaque<T> { + /// Creates a new opaque object with default contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned before calling them. + pub unsafe fn new() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::new(T::default())), + _pin: PhantomPinned, + } + } +} + +/// Annotates [`Self`] as a transparent wrapper for another type. +/// +/// Usually defined via the [`crate::Wrapper`] derive macro. +/// +/// # Examples +/// +/// ``` +/// # use std::mem::ManuallyDrop; +/// # use common::opaque::Wrapper; +/// #[repr(transparent)] +/// pub struct Example { +/// inner: ManuallyDrop<String>, +/// } +/// +/// unsafe impl Wrapper for Example { +/// type Wrapped = String; +/// } +/// ``` +/// +/// # Safety +/// +/// `Self` must be a `#[repr(transparent)]` wrapper for the `Wrapped` type, +/// whether directly or indirectly. +/// +/// # Methods +/// +/// By convention, types that implement Wrapper also implement the following +/// methods: +/// +/// ```ignore +/// pub const unsafe fn from_raw<'a>(value: *mut Self::Wrapped) -> &'a Self; +/// pub const unsafe fn as_mut_ptr(&self) -> *mut Self::Wrapped; +/// pub const unsafe fn as_ptr(&self) -> *const Self::Wrapped; +/// pub const unsafe fn raw_get(slot: *mut Self) -> *const Self::Wrapped; +/// ``` +/// +/// They are not defined here to allow them to be `const`. +pub unsafe trait Wrapper { + type Wrapped; +} + +unsafe impl<T> Wrapper for Opaque<T> { + type Wrapped = T; +} diff --git a/rust/qemu-api/src/uninit.rs b/rust/common/src/uninit.rs index 04123b4ae9..e7f9fcd2e3 100644 --- a/rust/qemu-api/src/uninit.rs +++ b/rust/common/src/uninit.rs @@ -12,7 +12,7 @@ pub struct MaybeUninitField<'a, T, U> { impl<'a, T, U> MaybeUninitField<'a, T, U> { #[doc(hidden)] - pub fn new(parent: &'a mut MaybeUninit<T>, child: *mut U) -> Self { + pub const fn new(parent: &'a mut MaybeUninit<T>, child: *mut U) -> Self { MaybeUninitField { parent, child } } @@ -21,7 +21,7 @@ impl<'a, T, U> MaybeUninitField<'a, T, U> { /// Because the `MaybeUninitField` remembers the containing object, /// it is possible to use it in foreign APIs that initialize the /// child. - pub fn parent(f: &Self) -> *const T { + pub const fn parent(f: &Self) -> *const T { f.parent.as_ptr() } @@ -30,7 +30,7 @@ impl<'a, T, U> MaybeUninitField<'a, T, U> { /// Because the `MaybeUninitField` remembers the containing object, /// it is possible to use it in foreign APIs that initialize the /// child. - pub fn parent_mut(f: &mut Self) -> *mut T { + pub const fn parent_mut(f: &mut Self) -> *mut T { f.parent.as_mut_ptr() } } @@ -63,7 +63,7 @@ impl<'a, T, U> DerefMut for MaybeUninitField<'a, T, U> { /// } /// /// # use std::mem::MaybeUninit; -/// # use qemu_api::{assert_match, uninit_field_mut}; +/// # use common::{assert_match, uninit_field_mut}; /// /// let mut s: MaybeUninit<S> = MaybeUninit::zeroed(); /// uninit_field_mut!(s, x).write(5); diff --git a/rust/common/src/zeroable.rs b/rust/common/src/zeroable.rs new file mode 100644 index 0000000000..fd056deb1f --- /dev/null +++ b/rust/common/src/zeroable.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Defines a trait for structs that can be safely initialized with zero bytes. + +/// Encapsulates the requirement that +/// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined +/// behavior. +/// +/// # Safety +/// +/// Do not add this trait to a type unless all-zeroes is a valid value for the +/// type. In particular, raw pointers can be zero, but references and +/// `NonNull<T>` cannot. +pub unsafe trait Zeroable: Default { + /// Return a value of Self whose memory representation consists of all + /// zeroes, with the possible exclusion of padding bytes. + const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() }; +} diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml index 88ef110507..b2418abc4b 100644 --- a/rust/hw/char/pl011/Cargo.toml +++ b/rust/hw/char/pl011/Cargo.toml @@ -16,8 +16,14 @@ rust-version.workspace = true bilge = { version = "0.2.0" } bilge-impl = { version = "0.2.0" } bits = { path = "../../../bits" } -qemu_api = { path = "../../../qemu-api" } -qemu_api_macros = { path = "../../../qemu-api-macros" } +common = { path = "../../../common" } +util = { path = "../../../util" } +bql = { path = "../../../bql" } +migration = { path = "../../../migration" } +qom = { path = "../../../qom" } +chardev = { path = "../../../chardev" } +system = { path = "../../../system" } +hwcore = { path = "../../../hw/core" } [lints] workspace = true diff --git a/rust/hw/char/pl011/build.rs b/rust/hw/char/pl011/build.rs new file mode 120000 index 0000000000..5f5060db35 --- /dev/null +++ b/rust/hw/char/pl011/build.rs @@ -0,0 +1 @@ +../../../util/build.rs \ No newline at end of file diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build index 2a1be329ab..ffdc8af53f 100644 --- a/rust/hw/char/pl011/meson.build +++ b/rust/hw/char/pl011/meson.build @@ -1,21 +1,48 @@ +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_libpl011_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common, +) + _libpl011_rs = static_library( 'pl011', - files('src/lib.rs'), + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/device.rs', + 'src/registers.rs', + ], + {'.' : _libpl011_bindings_inc_rs}, + ), override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', dependencies: [ bilge_rs, bilge_impl_rs, bits_rs, - qemu_api, - qemu_api_macros, + common_rs, + util_rs, + migration_rs, + bql_rs, + qom_rs, + chardev_rs, + system_rs, + hwcore_rs, ], ) rust_devices_ss.add(when: 'CONFIG_X_PL011_RUST', if_true: [declare_dependency( link_whole: [_libpl011_rs], - # Putting proc macro crates in `dependencies` is necessary for Meson to find - # them when compiling the root per-target static rust lib. - dependencies: [bilge_impl_rs, qemu_api_macros], variables: {'crate': 'pl011'}, )]) diff --git a/rust/hw/char/pl011/src/bindings.rs b/rust/hw/char/pl011/src/bindings.rs new file mode 100644 index 0000000000..bd5ea840cb --- /dev/null +++ b/rust/hw/char/pl011/src/bindings.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +//! `bindgen`-generated declarations. + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index ceb71dd99b..1b4587d5f6 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,30 +2,22 @@ // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ - ffi::{c_int, c_void, CStr}, - mem::size_of, - ptr::NonNull, +use std::{ffi::CStr, mem::size_of}; + +use bql::BqlRefCell; +use chardev::{CharBackend, Chardev, Event}; +use common::{static_assert, uninit_field_mut}; +use hwcore::{ + Clock, ClockEvent, DeviceImpl, DeviceMethods, DeviceState, IRQState, InterruptSource, + ResetType, ResettablePhasesImpl, SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods, }; - -use qemu_api::{ - bindings::{qdev_prop_bool, qdev_prop_chr}, - chardev::{CharBackend, Chardev, Event}, - impl_vmstate_forward, - irq::{IRQState, InterruptSource}, - log::Log, - log_mask_ln, - memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, - prelude::*, - qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, - qom::{ObjectImpl, Owned, ParentField, ParentInit}, - static_assert, - sysbus::{SysBusDevice, SysBusDeviceImpl}, - uninit_field_mut, - vmstate::VMStateDescription, - vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused, - zeroable::Zeroable, +use migration::{ + self, impl_vmstate_forward, impl_vmstate_struct, vmstate_fields, vmstate_of, + vmstate_subsections, vmstate_unused, VMStateDescription, VMStateDescriptionBuilder, }; +use qom::{prelude::*, ObjectImpl, Owned, ParentField, ParentInit}; +use system::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}; +use util::{log::Log, log_mask_ln}; use crate::registers::{self, Interrupt, RegisterOffset}; @@ -105,12 +97,13 @@ pub struct PL011Registers { } #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qom::Object, hwcore::Device)] /// PL011 Device Model in QEMU pub struct PL011State { pub parent_obj: ParentField<SysBusDevice>, pub iomem: MemoryRegion, #[doc(alias = "chr")] + #[property(rename = "chardev")] pub char_backend: CharBackend, pub regs: BqlRefCell<PL011Registers>, /// QEMU interrupts @@ -129,6 +122,7 @@ pub struct PL011State { #[doc(alias = "clk")] pub clock: Owned<Clock>, #[doc(alias = "migrate_clk")] + #[property(rename = "migrate-clk", default = true)] pub migrate_clock: bool, } @@ -136,7 +130,7 @@ pub struct PL011State { // structs, so the size of the Rust version must not be any larger // than the size of the C one. If this assert triggers you need to // expand the padding_for_rust[] array in the C PL011State struct. -static_assert!(size_of::<PL011State>() <= size_of::<qemu_api::bindings::PL011State>()); +static_assert!(size_of::<PL011State>() <= size_of::<crate::bindings::PL011State>()); qom_isa!(PL011State : SysBusDevice, DeviceState, Object); @@ -176,13 +170,8 @@ impl ObjectImpl for PL011State { } impl DeviceImpl for PL011State { - fn properties() -> &'static [Property] { - &PL011_PROPERTIES - } - fn vmsd() -> Option<&'static VMStateDescription> { - Some(&VMSTATE_PL011) - } - const REALIZE: Option<fn(&Self) -> qemu_api::Result<()>> = Some(Self::realize); + const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_PL011); + const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for PL011State { @@ -469,10 +458,10 @@ impl PL011Registers { false } - pub fn post_load(&mut self) -> Result<(), ()> { + pub fn post_load(&mut self) -> Result<(), migration::InvalidError> { /* Sanity-check input state */ if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() { - return Err(()); + return Err(migration::InvalidError); } if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > 0 { @@ -531,6 +520,10 @@ impl PL011State { /* pl011_trace_baudrate_change(s); */ } + pub fn clock_needed(&self) -> bool { + self.migrate_clock + } + fn post_init(&self) { self.init_mmio(&self.iomem); for irq in self.interrupts.iter() { @@ -629,7 +622,7 @@ impl PL011State { } } - fn realize(&self) -> qemu_api::Result<()> { + fn realize(&self) -> util::Result<()> { self.char_backend .enable_handlers(self, Self::can_receive, Self::receive, Self::event); Ok(()) @@ -647,7 +640,7 @@ impl PL011State { } } - pub fn post_load(&self, _version_id: u32) -> Result<(), ()> { + pub fn post_load(&self, _version_id: u8) -> Result<(), migration::InvalidError> { self.regs.borrow_mut().post_load() } } @@ -690,7 +683,7 @@ pub unsafe extern "C" fn pl011_create( } #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qom::Object, hwcore::Device)] /// PL011 Luminary device model. pub struct PL011Luminary { parent_obj: ParentField<PL011State>, @@ -717,87 +710,55 @@ impl DeviceImpl for PL011Luminary {} impl ResettablePhasesImpl for PL011Luminary {} impl SysBusDeviceImpl for PL011Luminary {} -extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool { - let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); - unsafe { state.as_ref().migrate_clock } -} - /// Migration subsection for [`PL011State`] clock. -static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription { - name: c"pl011/clock".as_ptr(), - version_id: 1, - minimum_version_id: 1, - needed: Some(pl011_clock_needed), - fields: vmstate_fields! { - vmstate_clock!(PL011State, clock), - }, - ..Zeroable::ZERO -}; - -extern "C" fn pl011_post_load(opaque: *mut c_void, version_id: c_int) -> c_int { - 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"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"pl011".as_ptr(), - version_id: 2, - minimum_version_id: 2, - post_load: Some(pl011_post_load), - fields: vmstate_fields! { - vmstate_unused!(core::mem::size_of::<u32>()), - vmstate_struct!(PL011State, regs, &VMSTATE_PL011_REGS, BqlRefCell<PL011Registers>), - }, - subsections: vmstate_subsections! { - VMSTATE_PL011_CLOCK - }, - ..Zeroable::ZERO -}; - -qemu_api::declare_properties! { - PL011_PROPERTIES, - qemu_api::define_property!( - c"chardev", - PL011State, - char_backend, - unsafe { &qdev_prop_chr }, - CharBackend - ), - qemu_api::define_property!( - c"migrate-clk", - PL011State, - migrate_clock, - unsafe { &qdev_prop_bool }, - bool, - default = true - ), -} +static VMSTATE_PL011_CLOCK: VMStateDescription<PL011State> = + VMStateDescriptionBuilder::<PL011State>::new() + .name(c"pl011/clock") + .version_id(1) + .minimum_version_id(1) + .needed(&PL011State::clock_needed) + .fields(vmstate_fields! { + vmstate_of!(PL011State, clock), + }) + .build(); + +impl_vmstate_struct!( + PL011Registers, + VMStateDescriptionBuilder::<PL011Registers>::new() + .name(c"pl011/regs") + .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), + }) + .build() +); + +pub const VMSTATE_PL011: VMStateDescription<PL011State> = + VMStateDescriptionBuilder::<PL011State>::new() + .name(c"pl011") + .version_id(2) + .minimum_version_id(2) + .post_load(&PL011State::post_load) + .fields(vmstate_fields! { + vmstate_unused!(core::mem::size_of::<u32>()), + vmstate_of!(PL011State, regs), + }) + .subsections(vmstate_subsections! { + VMSTATE_PL011_CLOCK + }) + .build(); diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 2b70d2ff56..0c19b708c0 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -12,6 +12,7 @@ //! See [`PL011State`](crate::device::PL011State) for the device model type and //! the [`registers`] module for register types. +mod bindings; mod device; mod registers; diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs index 7ececd39f8..0c3a4d7d21 100644 --- a/rust/hw/char/pl011/src/registers.rs +++ b/rust/hw/char/pl011/src/registers.rs @@ -10,13 +10,13 @@ use bilge::prelude::*; use bits::bits; -use qemu_api::{impl_vmstate_bitsized, impl_vmstate_forward}; +use migration::{impl_vmstate_bitsized, impl_vmstate_forward}; /// Offset of each register from the base memory address of the device. #[doc(alias = "offset")] #[allow(non_camel_case_types)] #[repr(u64)] -#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] +#[derive(Debug, Eq, PartialEq, common::TryInto)] pub enum RegisterOffset { /// Data Register /// diff --git a/rust/hw/char/pl011/wrapper.h b/rust/hw/char/pl011/wrapper.h new file mode 100644 index 0000000000..87a5a589c8 --- /dev/null +++ b/rust/hw/char/pl011/wrapper.h @@ -0,0 +1,51 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2024 Linaro Ltd. + * + * Authors: Manos Pitsidianakis <manos.pitsidianakis@linaro.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" +#include "hw/char/pl011.h" diff --git a/rust/hw/core/Cargo.toml b/rust/hw/core/Cargo.toml new file mode 100644 index 0000000000..9a9aa51708 --- /dev/null +++ b/rust/hw/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "hwcore" +version = "0.1.0" +description = "Rust bindings for QEMU/hwcore" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +qemu_macros = { path = "../../qemu-macros" } +common = { path = "../../common" } +bql = { path = "../../bql" } +qom = { path = "../../qom" } +chardev = { path = "../../chardev" } +migration = { path = "../../migration" } +system = { path = "../../system" } +util = { path = "../../util" } + +[lints] +workspace = true diff --git a/rust/hw/core/build.rs b/rust/hw/core/build.rs new file mode 120000 index 0000000000..2a79ee31b8 --- /dev/null +++ b/rust/hw/core/build.rs @@ -0,0 +1 @@ +../../util/build.rs \ No newline at end of file diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build new file mode 100644 index 0000000000..81d8c77f9a --- /dev/null +++ b/rust/hw/core/meson.build @@ -0,0 +1,80 @@ +_hwcore_bindgen_args = [] +c_enums = [ + 'DeviceCategory', + 'GpioPolarity', + 'MachineInitPhase', + 'ResetType', +] +foreach enum : c_enums + _hwcore_bindgen_args += ['--rustified-enum', enum] +endforeach + +blocked_type = [ + 'Chardev', + 'Error', + 'ObjectClass', + 'MemoryRegion', + 'VMStateDescription', +] +foreach type: blocked_type + _hwcore_bindgen_args += ['--blocklist-type', type] +endforeach + +c_bitfields = [ + 'ClockEvent', +] +foreach enum : c_bitfields + _hwcore_bindgen_args += ['--bitfield-enum', enum] +endforeach + +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_hwcore_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _hwcore_bindgen_args, +) + +_hwcore_rs = static_library( + 'hwcore', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/irq.rs', + 'src/qdev.rs', + 'src/sysbus.rs', + ], + {'.': _hwcore_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_bql_rs, _chardev_rs, _migration_rs, _qom_rs, _system_rs, _util_rs], + dependencies: [qemu_macros, common_rs], +) + +hwcore_rs = declare_dependency(link_with: [_hwcore_rs], + dependencies: [qom_rs, hwcore]) + +test('rust-hwcore-rs-integration', + executable( + 'rust-hwcore-rs-integration', + files('tests/tests.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_args: ['--test'], + install: false, + dependencies: [common_rs, hwcore_rs, bql_rs, migration_rs, util_rs]), + args: [ + '--test', '--test-threads', '1', + '--format', 'pretty', + ], + protocol: 'rust', + suite: ['unit', 'rust']) diff --git a/rust/hw/core/src/bindings.rs b/rust/hw/core/src/bindings.rs new file mode 100644 index 0000000000..919c02b56a --- /dev/null +++ b/rust/hw/core/src/bindings.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +use chardev::bindings::Chardev; +use common::Zeroable; +use migration::bindings::VMStateDescription; +use qom::bindings::ObjectClass; +use system::bindings::MemoryRegion; +use util::bindings::Error; + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); + +unsafe impl Send for Property {} +unsafe impl Sync for Property {} + +unsafe impl Send for TypeInfo {} +unsafe impl Sync for TypeInfo {} + +unsafe impl Zeroable for Property__bindgen_ty_1 {} +unsafe impl Zeroable for Property {} diff --git a/rust/qemu-api/src/irq.rs b/rust/hw/core/src/irq.rs index 1526e6f63a..e0d7784d97 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/hw/core/src/irq.rs @@ -10,16 +10,15 @@ use std::{ ptr, }; -use crate::{ - bindings::{self, qemu_set_irq}, - cell::Opaque, - prelude::*, - qom::ObjectClass, -}; +use bql::BqlCell; +use common::Opaque; +use qom::{prelude::*, ObjectClass}; + +use crate::bindings::{self, qemu_set_irq}; /// An opaque wrapper around [`bindings::IRQState`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct IRQState(Opaque<bindings::IRQState>); /// Interrupt sources are used by devices to pass changes to a value (typically @@ -34,7 +33,7 @@ pub struct IRQState(Opaque<bindings::IRQState>); /// /// Interrupts are implemented as a pointer to the interrupt "sink", which has /// type [`IRQState`]. A device exposes its source as a QOM link property using -/// a function such as [`SysBusDeviceMethods::init_irq`], and +/// a function such as [`crate::sysbus::SysBusDeviceMethods::init_irq`], and /// initially leaves the pointer to a NULL value, representing an unconnected /// interrupt. To connect it, whoever creates the device fills the pointer with /// the sink's `IRQState *`, for example using `sysbus_connect_irq`. Because @@ -112,4 +111,5 @@ unsafe impl ObjectType for IRQState { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_IRQ) }; } + qom_isa!(IRQState: Object); diff --git a/rust/hw/core/src/lib.rs b/rust/hw/core/src/lib.rs new file mode 100644 index 0000000000..b40801eb84 --- /dev/null +++ b/rust/hw/core/src/lib.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub use qemu_macros::Device; +pub use qom; + +pub mod bindings; + +mod irq; +pub use irq::*; + +mod qdev; +pub use qdev::*; + +mod sysbus; +pub use sysbus::*; diff --git a/rust/qemu-api/src/qdev.rs b/rust/hw/core/src/qdev.rs index 36f02fb57d..71b9ef141c 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/hw/core/src/qdev.rs @@ -9,23 +9,21 @@ use std::{ ptr::NonNull, }; -pub use bindings::{ClockEvent, DeviceClass, Property, ResetType}; +use chardev::Chardev; +use common::{callbacks::FnCall, Opaque}; +use migration::{impl_vmstate_c_struct, VMStateDescription}; +use qom::{prelude::*, ObjectClass, ObjectImpl, Owned, ParentInit}; +use util::{Error, Result}; +pub use crate::bindings::{ClockEvent, DeviceClass, Property, ResetType}; use crate::{ bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, ResettableClass}, - callbacks::FnCall, - cell::{bql_locked, Opaque}, - chardev::Chardev, - error::{Error, Result}, irq::InterruptSource, - prelude::*, - qom::{ObjectClass, ObjectImpl, Owned, ParentInit}, - vmstate::VMStateDescription, }; /// A safe wrapper around [`bindings::Clock`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct Clock(Opaque<bindings::Clock>); unsafe impl Send for Clock {} @@ -33,7 +31,7 @@ unsafe impl Sync for Clock {} /// A safe wrapper around [`bindings::DeviceState`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct DeviceState(Opaque<bindings::DeviceState>); unsafe impl Send for DeviceState {} @@ -101,8 +99,64 @@ unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>( T::EXIT.unwrap()(unsafe { state.as_ref() }, typ); } +/// Helper trait to return pointer to a [`bindings::PropertyInfo`] for a type. +/// +/// This trait is used by [`qemu_macros::Device`] derive macro. +/// +/// Base types that already have `qdev_prop_*` globals in the QEMU API should +/// use those values as exported by the [`bindings`] module, instead of +/// redefining them. +/// +/// # Safety +/// +/// This trait is marked as `unsafe` because currently having a `const` refer to +/// an `extern static` as a reference instead of a raw pointer results in this +/// compiler error: +/// +/// ```text +/// constructing invalid value: encountered reference to `extern` static in `const` +/// ``` +/// +/// This is because the compiler generally might dereference a normal reference +/// during const evaluation, but not in this case (if it did, it'd need to +/// dereference the raw pointer so this would fail to compile). +/// +/// It is the implementer's responsibility to provide a valid +/// [`bindings::PropertyInfo`] pointer for the trait implementation to be safe. +pub unsafe trait QDevProp { + const VALUE: *const bindings::PropertyInfo; +} + +/// Use [`bindings::qdev_prop_bool`] for `bool`. +unsafe impl QDevProp for bool { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_bool }; +} + +/// Use [`bindings::qdev_prop_uint64`] for `u64`. +unsafe impl QDevProp for u64 { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_uint64 }; +} + +/// Use [`bindings::qdev_prop_chr`] for [`chardev::CharBackend`]. +unsafe impl QDevProp for chardev::CharBackend { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_chr }; +} + +/// Trait to define device properties. +/// +/// # Safety +/// +/// Caller is responsible for the validity of properties array. +pub unsafe trait DevicePropertiesImpl { + /// An array providing the properties that the user can set on the + /// device. + const PROPERTIES: &'static [Property] = &[]; +} + /// Trait providing the contents of [`DeviceClass`]. -pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { +pub trait DeviceImpl: + ObjectImpl + ResettablePhasesImpl + DevicePropertiesImpl + IsA<DeviceState> +{ /// _Realization_ is the second stage of device creation. It contains /// all operations that depend on device properties and can fail (note: /// this is not yet supported for Rust devices). @@ -111,19 +165,10 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { /// with the function pointed to by `REALIZE`. const REALIZE: Option<fn(&Self) -> Result<()>> = None; - /// An array providing the properties that the user can set on the - /// device. Not a `const` because referencing statics in constants - /// is unstable until Rust 1.83.0. - fn properties() -> &'static [Property] { - &[] - } - /// A `VMStateDescription` providing the migration format for the device /// Not a `const` because referencing statics in constants is unstable /// until Rust 1.83.0. - fn vmsd() -> Option<&'static VMStateDescription> { - None - } + const VMSTATE: Option<VMStateDescription<Self>> = None; } /// # Safety @@ -135,7 +180,7 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>( dev: *mut bindings::DeviceState, - errp: *mut *mut bindings::Error, + errp: *mut *mut util::bindings::Error, ) { let state = NonNull::new(dev).unwrap().cast::<T>(); let result = T::REALIZE.unwrap()(unsafe { state.as_ref() }); @@ -172,10 +217,10 @@ impl DeviceClass { if <T as DeviceImpl>::REALIZE.is_some() { self.realize = Some(rust_realize_fn::<T>); } - if let Some(vmsd) = <T as DeviceImpl>::vmsd() { - self.vmsd = vmsd; + if let Some(ref vmsd) = <T as DeviceImpl>::VMSTATE { + self.vmsd = vmsd.as_ref(); } - let prop = <T as DeviceImpl>::properties(); + let prop = <T as DevicePropertiesImpl>::PROPERTIES; if !prop.is_empty() { unsafe { bindings::device_class_set_props_n(self, prop.as_ptr(), prop.len()); @@ -198,7 +243,7 @@ macro_rules! define_property { bitnr: $bitnr, set_default: true, defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, - ..$crate::zeroable::Zeroable::ZERO + ..::common::zeroable::Zeroable::ZERO } }; ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => { @@ -209,7 +254,7 @@ macro_rules! define_property { offset: ::std::mem::offset_of!($state, $field) as isize, set_default: true, defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, - ..$crate::zeroable::Zeroable::ZERO + ..::common::zeroable::Zeroable::ZERO } }; ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty$(,)*) => { @@ -219,7 +264,7 @@ macro_rules! define_property { info: $prop, offset: ::std::mem::offset_of!($state, $field) as isize, set_default: false, - ..$crate::zeroable::Zeroable::ZERO + ..::common::zeroable::Zeroable::ZERO } }; } @@ -245,6 +290,7 @@ unsafe impl ObjectType for DeviceState { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; } + qom_isa!(DeviceState: Object); /// Initialization methods take a [`ParentInit`] and can be called as @@ -275,7 +321,7 @@ impl DeviceState { cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)>, events: ClockEvent, ) -> Owned<Clock> { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: the clock is heap allocated, but qdev_init_clock_in() // does not gift the reference to its caller; so use Owned::from to @@ -346,7 +392,7 @@ where Self::Target: IsA<DeviceState>, { fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) { - assert!(bql_locked()); + assert!(bql::is_locked()); let c_propname = CString::new(propname).unwrap(); let chr: &Chardev = chr; unsafe { @@ -373,7 +419,7 @@ where } } - let _: () = F::ASSERT_IS_SOME; + const { assert!(F::IS_SOME) }; unsafe extern "C" fn rust_irq_handler<T, F: for<'a> FnCall<(&'a T, u32, u32)>>( opaque: *mut c_void, line: c_int, @@ -407,4 +453,7 @@ unsafe impl ObjectType for Clock { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CLOCK) }; } + qom_isa!(Clock: Object); + +impl_vmstate_c_struct!(Clock, bindings::vmstate_clock); diff --git a/rust/qemu-api/src/sysbus.rs b/rust/hw/core/src/sysbus.rs index e92502a8fe..282315fce9 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/hw/core/src/sysbus.rs @@ -7,20 +7,19 @@ use std::{ffi::CStr, ptr::addr_of_mut}; pub use bindings::SysBusDeviceClass; +use common::Opaque; +use qom::{prelude::*, Owned}; +use system::MemoryRegion; use crate::{ bindings, - cell::{bql_locked, Opaque}, irq::{IRQState, InterruptSource}, - memory::MemoryRegion, - prelude::*, qdev::{DeviceImpl, DeviceState}, - qom::Owned, }; /// A safe wrapper around [`bindings::SysBusDevice`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct SysBusDevice(Opaque<bindings::SysBusDevice>); unsafe impl Send for SysBusDevice {} @@ -31,6 +30,7 @@ unsafe impl ObjectType for SysBusDevice { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) }; } + qom_isa!(SysBusDevice: DeviceState, Object); // TODO: add virtual methods @@ -55,7 +55,7 @@ where /// region with a number that corresponds to the order of calls to /// `init_mmio`. fn init_mmio(&self, iomem: &MemoryRegion) { - assert!(bql_locked()); + assert!(bql::is_locked()); unsafe { bindings::sysbus_init_mmio(self.upcast().as_mut_ptr(), iomem.as_mut_ptr()); } @@ -66,7 +66,7 @@ where /// whoever creates the sysbus device will refer to the interrupts with /// a number that corresponds to the order of calls to `init_irq`. fn init_irq(&self, irq: &InterruptSource) { - assert!(bql_locked()); + assert!(bql::is_locked()); unsafe { bindings::sysbus_init_irq(self.upcast().as_mut_ptr(), irq.as_ptr()); } @@ -74,7 +74,7 @@ where // TODO: do we want a type like GuestAddress here? fn mmio_addr(&self, id: u32) -> Option<u64> { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: the BQL ensures that no one else writes to sbd.mmio[], and // the SysBusDevice must be initialized to get an IsA<SysBusDevice>. let sbd = unsafe { *self.upcast().as_ptr() }; @@ -88,7 +88,7 @@ where // TODO: do we want a type like GuestAddress here? fn mmio_map(&self, id: u32, addr: u64) { - assert!(bql_locked()); + assert!(bql::is_locked()); let id: i32 = id.try_into().unwrap(); unsafe { bindings::sysbus_mmio_map(self.upcast().as_mut_ptr(), id, addr); @@ -99,7 +99,7 @@ where // object_property_set_link) adds a reference to the IRQState, // which can prolong its life fn connect_irq(&self, id: u32, irq: &Owned<IRQState>) { - assert!(bql_locked()); + assert!(bql::is_locked()); let id: i32 = id.try_into().unwrap(); let irq: &IRQState = irq; unsafe { @@ -109,11 +109,11 @@ where fn sysbus_realize(&self) { // TODO: return an Error - assert!(bql_locked()); + assert!(bql::is_locked()); unsafe { bindings::sysbus_realize( self.upcast().as_mut_ptr(), - addr_of_mut!(bindings::error_fatal), + addr_of_mut!(util::bindings::error_fatal), ); } } diff --git a/rust/qemu-api/tests/tests.rs b/rust/hw/core/tests/tests.rs index a658a49fcf..247d812866 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/hw/core/tests/tests.rs @@ -4,31 +4,23 @@ use std::{ffi::CStr, ptr::addr_of}; -use qemu_api::{ - bindings::{module_call_init, module_init_type, qdev_prop_bool}, - cell::{self, BqlCell}, - declare_properties, define_property, - prelude::*, - qdev::{DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, - qom::{ObjectImpl, ParentField}, - sysbus::SysBusDevice, - vmstate::VMStateDescription, - zeroable::Zeroable, -}; - -mod vmstate_tests; +use bql::BqlCell; +use hwcore::{DeviceImpl, DeviceState, ResettablePhasesImpl, SysBusDevice}; +use migration::{VMStateDescription, VMStateDescriptionBuilder}; +use qom::{prelude::*, ObjectImpl, ParentField}; +use util::bindings::{module_call_init, module_init_type}; // Test that macros can compile. -pub static VMSTATE: VMStateDescription = VMStateDescription { - name: c"name".as_ptr(), - unmigratable: true, - ..Zeroable::ZERO -}; +pub const VMSTATE: VMStateDescription<DummyState> = VMStateDescriptionBuilder::<DummyState>::new() + .name(c"name") + .unmigratable() + .build(); #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qom::Object, hwcore::Device)] pub struct DummyState { parent: ParentField<DeviceState>, + #[property(rename = "migrate-clk", default = true)] migrate_clock: bool, } @@ -44,17 +36,6 @@ impl DummyClass { } } -declare_properties! { - DUMMY_PROPERTIES, - define_property!( - c"migrate-clk", - DummyState, - migrate_clock, - unsafe { &qdev_prop_bool }, - bool - ), -} - unsafe impl ObjectType for DummyState { type Class = DummyClass; const TYPE_NAME: &'static CStr = c"dummy"; @@ -69,16 +50,11 @@ impl ObjectImpl for DummyState { impl ResettablePhasesImpl for DummyState {} impl DeviceImpl for DummyState { - fn properties() -> &'static [Property] { - &DUMMY_PROPERTIES - } - fn vmsd() -> Option<&'static VMStateDescription> { - Some(&VMSTATE) - } + const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE); } #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qom::Object, hwcore::Device)] pub struct DummyChildState { parent: ParentField<DummyState>, } @@ -112,7 +88,7 @@ impl DummyChildClass { fn init_qom() { static ONCE: BqlCell<bool> = BqlCell::new(false); - cell::bql_start_test(); + bql::start_test(); if !ONCE.get() { unsafe { module_call_init(module_init_type::MODULE_INIT_QOM); diff --git a/rust/hw/core/wrapper.h b/rust/hw/core/wrapper.h new file mode 100644 index 0000000000..3bdbd1249e --- /dev/null +++ b/rust/hw/core/wrapper.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "hw/sysbus.h" +#include "hw/clock.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/irq.h" diff --git a/rust/hw/timer/hpet/Cargo.toml b/rust/hw/timer/hpet/Cargo.toml index ac5df23c1d..f781b28d8b 100644 --- a/rust/hw/timer/hpet/Cargo.toml +++ b/rust/hw/timer/hpet/Cargo.toml @@ -11,8 +11,13 @@ repository.workspace = true rust-version.workspace = true [dependencies] -qemu_api = { path = "../../../qemu-api" } -qemu_api_macros = { path = "../../../qemu-api-macros" } +common = { path = "../../../common" } +util = { path = "../../../util" } +migration = { path = "../../../migration" } +bql = { path = "../../../bql" } +qom = { path = "../../../qom" } +system = { path = "../../../system" } +hwcore = { path = "../../../hw/core" } [lints] workspace = true diff --git a/rust/hw/timer/hpet/meson.build b/rust/hw/timer/hpet/meson.build index c2d7c0532c..bb64b96672 100644 --- a/rust/hw/timer/hpet/meson.build +++ b/rust/hw/timer/hpet/meson.build @@ -4,15 +4,17 @@ _libhpet_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', dependencies: [ - qemu_api, - qemu_api_macros, + common_rs, + util_rs, + migration_rs, + bql_rs, + qom_rs, + system_rs, + hwcore_rs, ], ) rust_devices_ss.add(when: 'CONFIG_X_HPET_RUST', if_true: [declare_dependency( link_whole: [_libhpet_rs], - # Putting proc macro crates in `dependencies` is necessary for Meson to find - # them when compiling the root per-target static rust lib. - dependencies: [qemu_api_macros], variables: {'crate': 'hpet'}, )]) diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index acf7251029..3cfbe9c32b 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -3,34 +3,30 @@ // SPDX-License-Identifier: GPL-2.0-or-later use std::{ - ffi::{c_int, c_void, CStr}, + ffi::CStr, mem::MaybeUninit, pin::Pin, ptr::{addr_of_mut, null_mut, NonNull}, slice::from_ref, }; -use qemu_api::{ - bindings::{ - address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool, - qdev_prop_uint32, qdev_prop_usize, - }, - cell::{BqlCell, BqlRefCell}, - irq::InterruptSource, - memory::{ - hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED, - }, - prelude::*, - qdev::{DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, - qom::{ObjectImpl, ObjectType, ParentField, ParentInit}, - qom_isa, - sysbus::{SysBusDevice, SysBusDeviceImpl}, - timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND}, - uninit_field_mut, - vmstate::VMStateDescription, - vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_validate, - zeroable::Zeroable, +use bql::{BqlCell, BqlRefCell}; +use common::{bitops::IntegerExt, uninit_field_mut}; +use hwcore::{ + bindings::{qdev_prop_bit, qdev_prop_bool, qdev_prop_uint32, qdev_prop_usize}, + declare_properties, define_property, DeviceImpl, DeviceMethods, DeviceState, InterruptSource, + Property, ResetType, ResettablePhasesImpl, SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods, }; +use migration::{ + self, impl_vmstate_struct, vmstate_fields, vmstate_of, vmstate_subsections, vmstate_validate, + VMStateDescription, VMStateDescriptionBuilder, +}; +use qom::{prelude::*, ObjectImpl, ParentField, ParentInit}; +use system::{ + bindings::{address_space_memory, address_space_stl_le, hwaddr}, + MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED, +}; +use util::timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND}; use crate::fw_cfg::HPETFwConfig; @@ -101,7 +97,7 @@ const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; /// Timer N Interrupt Routing Capability (bits 32:63) const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; -#[derive(qemu_api_macros::TryInto)] +#[derive(common::TryInto)] #[repr(u64)] #[allow(non_camel_case_types)] /// Timer registers, masked by 0x18 @@ -114,7 +110,7 @@ enum TimerRegister { ROUTE = 16, } -#[derive(qemu_api_macros::TryInto)] +#[derive(common::TryInto)] #[repr(u64)] #[allow(non_camel_case_types)] /// Global registers @@ -213,6 +209,10 @@ pub struct HPETTimer { last: u64, } +// SAFETY: Sync is not automatically derived due to the `state` field, +// which is always dereferenced to a shared reference. +unsafe impl Sync for HPETTimer {} + impl HPETTimer { fn new(index: u8, state: *const HPETState) -> HPETTimer { HPETTimer { @@ -520,7 +520,7 @@ impl HPETTimer { /// HPET Event Timer Block Abstraction #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qom::Object)] pub struct HPETState { parent_obj: ParentField<SysBusDevice>, iomem: MemoryRegion, @@ -724,7 +724,7 @@ impl HPETState { } } - fn realize(&self) -> qemu_api::Result<()> { + fn realize(&self) -> util::Result<()> { if self.num_timers < HPET_MIN_TIMERS || self.num_timers > HPET_MAX_TIMERS { Err(format!( "hpet.num_timers must be between {HPET_MIN_TIMERS} and {HPET_MAX_TIMERS}" @@ -841,7 +841,7 @@ impl HPETState { } } - fn pre_save(&self) -> i32 { + fn pre_save(&self) -> Result<(), migration::Infallible> { if self.is_hpet_enabled() { self.counter.set(self.get_ticks()); } @@ -852,10 +852,10 @@ impl HPETState { * that was configured. */ self.num_timers_save.set(self.num_timers as u8); - 0 + Ok(()) } - fn post_load(&self, _version_id: u8) -> i32 { + fn post_load(&self, _version_id: u8) -> Result<(), migration::Infallible> { for timer in self.timers.iter().take(self.num_timers) { let mut t = timer.borrow_mut(); @@ -869,7 +869,7 @@ impl HPETState { .set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns()); } - 0 + Ok(()) } fn is_rtc_irq_level_needed(&self) -> bool { @@ -902,9 +902,9 @@ impl ObjectImpl for HPETState { } // TODO: Make these properties user-configurable! -qemu_api::declare_properties! { +declare_properties! { HPET_PROPERTIES, - qemu_api::define_property!( + define_property!( c"timers", HPETState, num_timers, @@ -912,7 +912,7 @@ qemu_api::declare_properties! { u8, default = HPET_MIN_TIMERS ), - qemu_api::define_property!( + define_property!( c"msi", HPETState, flags, @@ -921,7 +921,7 @@ qemu_api::declare_properties! { bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, default = false, ), - qemu_api::define_property!( + define_property!( c"hpet-intcap", HPETState, int_route_cap, @@ -929,7 +929,7 @@ qemu_api::declare_properties! { u32, default = 0 ), - qemu_api::define_property!( + define_property!( c"hpet-offset-saved", HPETState, hpet_offset_saved, @@ -939,108 +939,77 @@ qemu_api::declare_properties! { ), } -unsafe extern "C" fn hpet_rtc_irq_level_needed(opaque: *mut c_void) -> bool { - // SAFETY: - // the pointer is convertible to a reference - let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() }; - state.is_rtc_irq_level_needed() -} - -unsafe extern "C" fn hpet_offset_needed(opaque: *mut c_void) -> bool { - // SAFETY: - // the pointer is convertible to a reference - let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() }; - state.is_offset_needed() -} - -unsafe extern "C" fn hpet_pre_save(opaque: *mut c_void) -> c_int { - // SAFETY: - // the pointer is convertible to a reference - let state: &mut HPETState = - unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() }; - state.pre_save() as c_int -} - -unsafe extern "C" fn hpet_post_load(opaque: *mut c_void, version_id: c_int) -> c_int { - // SAFETY: - // the pointer is convertible to a reference - let state: &mut HPETState = - unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() }; - let version: u8 = version_id.try_into().unwrap(); - state.post_load(version) as c_int -} - -static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription = VMStateDescription { - name: c"hpet/rtc_irq_level".as_ptr(), - version_id: 1, - minimum_version_id: 1, - needed: Some(hpet_rtc_irq_level_needed), - fields: vmstate_fields! { - vmstate_of!(HPETState, rtc_irq_level), - }, - ..Zeroable::ZERO -}; - -static VMSTATE_HPET_OFFSET: VMStateDescription = VMStateDescription { - name: c"hpet/offset".as_ptr(), - version_id: 1, - minimum_version_id: 1, - needed: Some(hpet_offset_needed), - fields: vmstate_fields! { - vmstate_of!(HPETState, hpet_offset), - }, - ..Zeroable::ZERO -}; - -static VMSTATE_HPET_TIMER: VMStateDescription = VMStateDescription { - name: c"hpet_timer".as_ptr(), - version_id: 1, - minimum_version_id: 1, - fields: vmstate_fields! { - vmstate_of!(HPETTimer, index), - vmstate_of!(HPETTimer, config), - vmstate_of!(HPETTimer, cmp), - vmstate_of!(HPETTimer, fsb), - vmstate_of!(HPETTimer, period), - vmstate_of!(HPETTimer, wrap_flag), - vmstate_of!(HPETTimer, qemu_timer), - }, - ..Zeroable::ZERO -}; +static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription<HPETState> = + VMStateDescriptionBuilder::<HPETState>::new() + .name(c"hpet/rtc_irq_level") + .version_id(1) + .minimum_version_id(1) + .needed(&HPETState::is_rtc_irq_level_needed) + .fields(vmstate_fields! { + vmstate_of!(HPETState, rtc_irq_level), + }) + .build(); + +static VMSTATE_HPET_OFFSET: VMStateDescription<HPETState> = + VMStateDescriptionBuilder::<HPETState>::new() + .name(c"hpet/offset") + .version_id(1) + .minimum_version_id(1) + .needed(&HPETState::is_offset_needed) + .fields(vmstate_fields! { + vmstate_of!(HPETState, hpet_offset), + }) + .build(); + +const VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> = + VMStateDescriptionBuilder::<HPETTimer>::new() + .name(c"hpet_timer") + .version_id(1) + .minimum_version_id(1) + .fields(vmstate_fields! { + vmstate_of!(HPETTimer, index), + vmstate_of!(HPETTimer, config), + vmstate_of!(HPETTimer, cmp), + vmstate_of!(HPETTimer, fsb), + vmstate_of!(HPETTimer, period), + vmstate_of!(HPETTimer, wrap_flag), + vmstate_of!(HPETTimer, qemu_timer), + }) + .build(); +impl_vmstate_struct!(HPETTimer, VMSTATE_HPET_TIMER); const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match"; -static VMSTATE_HPET: VMStateDescription = VMStateDescription { - name: c"hpet".as_ptr(), - version_id: 2, - minimum_version_id: 2, - pre_save: Some(hpet_pre_save), - post_load: Some(hpet_post_load), - fields: vmstate_fields! { - vmstate_of!(HPETState, config), - vmstate_of!(HPETState, int_status), - vmstate_of!(HPETState, counter), - vmstate_of!(HPETState, num_timers_save), - vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers), - vmstate_struct!(HPETState, timers[0 .. num_timers_save], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0), - }, - subsections: vmstate_subsections! { - VMSTATE_HPET_RTC_IRQ_LEVEL, - VMSTATE_HPET_OFFSET, - }, - ..Zeroable::ZERO -}; +const VMSTATE_HPET: VMStateDescription<HPETState> = + VMStateDescriptionBuilder::<HPETState>::new() + .name(c"hpet") + .version_id(2) + .minimum_version_id(2) + .pre_save(&HPETState::pre_save) + .post_load(&HPETState::post_load) + .fields(vmstate_fields! { + vmstate_of!(HPETState, config), + vmstate_of!(HPETState, int_status), + vmstate_of!(HPETState, counter), + vmstate_of!(HPETState, num_timers_save), + vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers), + vmstate_of!(HPETState, timers[0 .. num_timers_save], HPETState::validate_num_timers).with_version_id(0), + }) + .subsections(vmstate_subsections!( + VMSTATE_HPET_RTC_IRQ_LEVEL, + VMSTATE_HPET_OFFSET, + )) + .build(); + +// SAFETY: HPET_PROPERTIES is a valid Property array constructed with the +// hwcore::declare_properties macro. +unsafe impl hwcore::DevicePropertiesImpl for HPETState { + const PROPERTIES: &'static [Property] = &HPET_PROPERTIES; +} impl DeviceImpl for HPETState { - fn properties() -> &'static [Property] { - &HPET_PROPERTIES - } - - fn vmsd() -> Option<&'static VMStateDescription> { - Some(&VMSTATE_HPET) - } - - const REALIZE: Option<fn(&Self) -> qemu_api::Result<()>> = Some(Self::realize); + const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_HPET); + const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for HPETState { diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs index 619d662ee1..e569b57b93 100644 --- a/rust/hw/timer/hpet/src/fw_cfg.rs +++ b/rust/hw/timer/hpet/src/fw_cfg.rs @@ -4,7 +4,7 @@ use std::ptr::addr_of_mut; -use qemu_api::{cell::bql_locked, zeroable::Zeroable}; +use common::Zeroable; /// Each `HPETState` represents a Event Timer Block. The v1 spec supports /// up to 8 blocks. QEMU only uses 1 block (in PC machine). @@ -37,7 +37,7 @@ pub static mut hpet_fw_cfg: HPETFwConfig = HPETFwConfig { impl HPETFwConfig { pub(crate) fn assign_hpet_id() -> Result<usize, &'static str> { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: all accesses go through these methods, which guarantee // that the accesses are protected by the BQL. let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) }; @@ -57,7 +57,7 @@ impl HPETFwConfig { } pub(crate) fn update_hpet_cfg(hpet_id: usize, timer_block_id: u32, address: u64) { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: all accesses go through these methods, which guarantee // that the accesses are protected by the BQL. let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) }; diff --git a/rust/meson.build b/rust/meson.build index 331f11b7e7..c7bd6aba45 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -18,13 +18,20 @@ quote_rs_native = dependency('quote-1-rs', native: true) syn_rs_native = dependency('syn-2-rs', native: true) proc_macro2_rs_native = dependency('proc-macro2-1-rs', native: true) -qemuutil_rs = qemuutil.partial_dependency(link_args: true, links: true) - genrs = [] -subdir('qemu-api-macros') +subdir('qemu-macros') + +subdir('common') subdir('bits') -subdir('qemu-api') +subdir('util') +subdir('migration') +subdir('bql') +subdir('qom') +subdir('system') +subdir('chardev') +subdir('hw/core') +subdir('tests') subdir('hw') diff --git a/rust/migration/Cargo.toml b/rust/migration/Cargo.toml new file mode 100644 index 0000000000..708bfaaa68 --- /dev/null +++ b/rust/migration/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "migration" +version = "0.1.0" +description = "Rust bindings for QEMU/migration" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../common" } +util = { path = "../util" } + +[lints] +workspace = true diff --git a/rust/migration/build.rs b/rust/migration/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/migration/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/migration/meson.build b/rust/migration/meson.build new file mode 100644 index 0000000000..5e820d43f5 --- /dev/null +++ b/rust/migration/meson.build @@ -0,0 +1,53 @@ +_migration_bindgen_args = [] +c_bitfields = [ + 'MigrationPolicy', + 'MigrationPriority', + 'VMStateFlags', +] +foreach enum : c_bitfields + _migration_bindgen_args += ['--bitfield-enum', enum] +endforeach +# +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_migration_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _migration_bindgen_args, + ) + +_migration_rs = static_library( + 'migration', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/vmstate.rs', + ], + {'.' : _migration_bindings_inc_rs}, + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_util_rs], + dependencies: [common_rs], +) + +migration_rs = declare_dependency(link_with: [_migration_rs], + dependencies: [migration, qemuutil]) + +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-migration-rs-doctests', + _migration_rs, + protocol: 'rust', + dependencies: migration_rs, + suite: ['doc', 'rust']) diff --git a/rust/migration/src/bindings.rs b/rust/migration/src/bindings.rs new file mode 100644 index 0000000000..8ce13a9000 --- /dev/null +++ b/rust/migration/src/bindings.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +use common::Zeroable; + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); + +unsafe impl Send for VMStateDescription {} +unsafe impl Sync for VMStateDescription {} + +unsafe impl Send for VMStateField {} +unsafe impl Sync for VMStateField {} + +unsafe impl Send for VMStateInfo {} +unsafe impl Sync for VMStateInfo {} + +// bindgen does not derive Default here +#[allow(clippy::derivable_impls)] +impl Default for VMStateFlags { + fn default() -> Self { + Self(0) + } +} + +unsafe impl Zeroable for VMStateFlags {} +unsafe impl Zeroable for VMStateField {} +unsafe impl Zeroable for VMStateDescription {} diff --git a/rust/migration/src/lib.rs b/rust/migration/src/lib.rs new file mode 100644 index 0000000000..5f51dde440 --- /dev/null +++ b/rust/migration/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub mod bindings; + +pub mod vmstate; +pub use vmstate::*; diff --git a/rust/qemu-api/src/vmstate.rs b/rust/migration/src/vmstate.rs index 812f390d78..c05c4a1fd6 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/migration/src/vmstate.rs @@ -11,10 +11,11 @@ //! migration format for a struct. This is based on the [`VMState`] trait, //! which is defined by all migratable 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) +//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward), +//! [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), and +//! [`impl_vmstate_struct`](crate::impl_vmstate_struct), which help with the +//! definition of the [`VMState`] trait (respectively for transparent structs, +//! nested structs and `bilge`-defined types) //! //! * helper macros to declare a device model state struct, in particular //! [`vmstate_subsections`](crate::vmstate_subsections) and @@ -24,14 +25,25 @@ //! `include/migration/vmstate.h`. These are not type-safe and only provide //! functionality that is missing from `vmstate_of!`. -use core::{marker::PhantomData, mem, ptr::NonNull}; -use std::ffi::{c_int, c_void}; +pub use std::convert::Infallible; +use std::{ + error::Error, + ffi::{c_int, c_void, CStr}, + fmt, io, + marker::PhantomData, + mem, + ptr::{addr_of, NonNull}, +}; -pub use crate::bindings::{VMStateDescription, VMStateField}; -use crate::{ - bindings::VMStateFlags, callbacks::FnCall, prelude::*, qom::Owned, zeroable::Zeroable, +use common::{ + callbacks::FnCall, + errno::{into_neg_errno, Errno}, + Zeroable, }; +use crate::bindings::{self, VMStateFlags}; +pub use crate::bindings::{MigrationPriority, VMStateField}; + /// 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 @@ -40,7 +52,7 @@ use crate::{ /// # Examples /// /// ``` -/// # use qemu_api::call_func_with_field; +/// # use migration::call_func_with_field; /// # use core::marker::PhantomData; /// const fn size_of_field<T>(_: PhantomData<T>) -> usize { /// std::mem::size_of::<T>() @@ -69,70 +81,6 @@ macro_rules! call_func_with_field { }; } -/// 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, -} - -/// Workaround for lack of `const_refs_static`. Converts a `VMStateFieldType` -/// to a `*const VMStateInfo`, for inclusion in a `VMStateField`. -#[macro_export] -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). /// @@ -143,12 +91,6 @@ macro_rules! info_enum_to_ref { /// 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; @@ -163,12 +105,6 @@ pub unsafe trait VMState { }; } -/// 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 { @@ -189,15 +125,19 @@ pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags /// * scalar types (integer and `bool`) /// * the C struct `QEMUTimer` /// * a transparent wrapper for any of the above (`Cell`, `UnsafeCell`, -/// [`BqlCell`], [`BqlRefCell`] +/// [`BqlCell`], [`BqlRefCell`]) /// * a raw pointer to any of the above /// * a `NonNull` pointer, a `Box` or an [`Owned`] 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. +/// for them. The macros [`impl_vmstate_forward`](crate::impl_vmstate_forward), +/// [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), and +/// [`impl_vmstate_struct`](crate::impl_vmstate_struct) help with this. +/// +/// [`BqlCell`]: ../../bql/cell/struct.BqlCell.html +/// [`BqlRefCell`]: ../../bql/cell/struct.BqlRefCell.html +/// [`Owned`]: ../../qom/qom/struct.Owned.html #[macro_export] macro_rules! vmstate_of { ($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])? $(, $test_fn:expr)? $(,)?) => { @@ -210,11 +150,6 @@ macro_rules! vmstate_of { $(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)? // 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, @@ -228,7 +163,11 @@ macro_rules! vmstate_of { }; } -impl VMStateFlags { +pub trait VMStateFlagsExt { + const VMS_VARRAY_FLAGS: VMStateFlags; +} + +impl VMStateFlagsExt for VMStateFlags { const VMS_VARRAY_FLAGS: VMStateFlags = VMStateFlags( VMStateFlags::VMS_VARRAY_INT32.0 | VMStateFlags::VMS_VARRAY_UINT8.0 @@ -274,7 +213,7 @@ impl VMStateField { } #[must_use] - pub const fn with_varray_flag_unchecked(mut self, flag: VMStateFlags) -> VMStateField { + pub const fn with_varray_flag_unchecked(mut self, flag: VMStateFlags) -> Self { self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_ARRAY.0); self.flags = VMStateFlags(self.flags.0 | flag.0); self.num = 0; // varray uses num_offset instead of num. @@ -283,13 +222,13 @@ impl VMStateField { #[must_use] #[allow(unused_mut)] - pub const fn with_varray_flag(mut self, flag: VMStateFlags) -> VMStateField { + pub const fn with_varray_flag(mut self, flag: VMStateFlags) -> Self { assert!((self.flags.0 & VMStateFlags::VMS_ARRAY.0) != 0); self.with_varray_flag_unchecked(flag) } #[must_use] - pub const fn with_varray_multiply(mut self, num: u32) -> VMStateField { + pub const fn with_varray_multiply(mut self, num: u32) -> Self { assert!(num <= 0x7FFF_FFFFu32); self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0); self.num = num as i32; @@ -304,7 +243,7 @@ impl VMStateField { /// # Examples /// /// ``` -/// # use qemu_api::impl_vmstate_forward; +/// # use migration::impl_vmstate_forward; /// pub struct Fifo([u8; 16]); /// impl_vmstate_forward!(Fifo); /// ``` @@ -315,8 +254,6 @@ macro_rules! impl_vmstate_forward { // 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); } @@ -325,15 +262,15 @@ macro_rules! impl_vmstate_forward { // Transparent wrappers: just use the internal type +#[macro_export] 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 { + unsafe impl<$base> $crate::vmstate::VMState for $type where $base: $crate::vmstate::VMState $($where)* { + const BASE: $crate::vmstate::VMStateField = $crate::vmstate::VMStateField { size: mem::size_of::<$type>(), - ..<$base as VMState>::BASE + ..<$base as $crate::vmstate::VMState>::BASE }; - const VARRAY_FLAG: VMStateFlags = <$base as VMState>::VARRAY_FLAG; + const VARRAY_FLAG: $crate::bindings::VMStateFlags = <$base as $crate::vmstate::VMState>::VARRAY_FLAG; } }; } @@ -341,18 +278,12 @@ macro_rules! impl_vmstate_transparent { impl_vmstate_transparent!(std::cell::Cell<T> where T: VMState); impl_vmstate_transparent!(std::cell::UnsafeCell<T> where T: VMState); impl_vmstate_transparent!(std::pin::Pin<T> where T: VMState); -impl_vmstate_transparent!(crate::cell::BqlCell<T> where T: VMState); -impl_vmstate_transparent!(crate::cell::BqlRefCell<T> where T: VMState); -impl_vmstate_transparent!(crate::cell::Opaque<T> where T: VMState); +impl_vmstate_transparent!(common::Opaque<T> where T: VMState); #[macro_export] 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 @@ -369,12 +300,12 @@ macro_rules! impl_vmstate_bitsized { 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 { + unsafe impl $crate::vmstate::VMState for $type { + const BASE: $crate::vmstate::VMStateField = $crate::vmstate::VMStateField { + info: addr_of!(bindings::$info), size: mem::size_of::<$type>(), - flags: VMStateFlags::VMS_SINGLE, - ..Zeroable::ZERO + flags: $crate::vmstate::VMStateFlags::VMS_SINGLE, + ..::common::zeroable::Zeroable::ZERO }; $(const VARRAY_FLAG: VMStateFlags = VMStateFlags::$varray_flag;)? } @@ -390,17 +321,31 @@ 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, crate::timer::Timer); +impl_vmstate_scalar!(vmstate_info_timer, util::timer::Timer); + +#[macro_export] +macro_rules! impl_vmstate_c_struct { + ($type:ty, $vmsd:expr) => { + unsafe impl $crate::vmstate::VMState for $type { + const BASE: $crate::bindings::VMStateField = $crate::bindings::VMStateField { + vmsd: ::std::ptr::addr_of!($vmsd), + size: ::std::mem::size_of::<$type>(), + flags: $crate::bindings::VMStateFlags::VMS_STRUCT, + ..::common::zeroable::Zeroable::ZERO + }; + } + }; +} // 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_export] 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(); + unsafe impl<$base> $crate::vmstate::VMState for $type where $base: $crate::vmstate::VMState $($where)* { + const BASE: $crate::vmstate::VMStateField = <$base as $crate::vmstate::VMState>::BASE.with_pointer_flag(); } }; } @@ -412,13 +357,11 @@ 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); -impl_vmstate_pointer!(Owned<T> where T: VMState + ObjectType); // 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); } @@ -431,7 +374,7 @@ macro_rules! vmstate_unused { size: $size, info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) }, flags: $crate::bindings::VMStateFlags::VMS_BUFFER, - ..$crate::zeroable::Zeroable::ZERO + ..::common::Zeroable::ZERO } }}; } @@ -440,7 +383,7 @@ pub extern "C" fn rust_vms_test_field_exists<T, F: for<'a> FnCall<(&'a T, u8), b opaque: *mut c_void, version_id: c_int, ) -> bool { - // SAFETY: the opaque was passed as a reference to `T`. + // SAFETY: the function is used in T's implementation of VMState let owner: &T = unsafe { &*(opaque.cast::<T>()) }; let version: u8 = version_id.try_into().unwrap(); F::call((owner, version)) @@ -454,10 +397,10 @@ pub type VMSFieldExistCb = unsafe extern "C" fn( #[macro_export] macro_rules! vmstate_exist_fn { ($struct_name:ty, $test_fn:expr) => {{ - const fn test_cb_builder__<T, F: for<'a> $crate::callbacks::FnCall<(&'a T, u8), bool>>( + const fn test_cb_builder__<T, F: for<'a> ::common::FnCall<(&'a T, u8), bool>>( _phantom: ::core::marker::PhantomData<F>, ) -> $crate::vmstate::VMSFieldExistCb { - let _: () = F::ASSERT_IS_SOME; + const { assert!(F::IS_SOME) }; $crate::vmstate::rust_vms_test_field_exists::<T, F> } @@ -468,76 +411,6 @@ macro_rules! vmstate_exist_fn { }}; } -// 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_struct { - ($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?, $vmsd:expr, $type:ty $(, $test_fn:expr)? $(,)?) => { - $crate::bindings::VMStateField { - name: ::core::concat!(::core::stringify!($field_name), "\0") - .as_bytes() - .as_ptr() as *const ::std::os::raw::c_char, - $(num_offset: ::std::mem::offset_of!($struct_name, $num),)? - offset: { - $crate::assert_field_type!($struct_name, $field_name, $type $(, num = $num)?); - ::std::mem::offset_of!($struct_name, $field_name) - }, - size: ::core::mem::size_of::<$type>(), - flags: $crate::bindings::VMStateFlags::VMS_STRUCT, - vmsd: $vmsd, - $(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)? - ..$crate::zeroable::Zeroable::ZERO - } $(.with_varray_flag_unchecked( - $crate::call_func_with_field!( - $crate::vmstate::vmstate_varray_flag, - $struct_name, - $num - ) - ) - $(.with_varray_multiply($factor))?)? - }; -} - -#[doc(alias = "VMSTATE_CLOCK")] -#[macro_export] -macro_rules! vmstate_clock { - ($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?) => {{ - $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, - $crate::qom::Owned<$crate::qdev::Clock> $(, num = $num)? - ); - ::std::mem::offset_of!($struct_name, $field_name) - }, - size: ::core::mem::size_of::<*const $crate::qdev::Clock>(), - flags: $crate::bindings::VMStateFlags( - $crate::bindings::VMStateFlags::VMS_STRUCT.0 - | $crate::bindings::VMStateFlags::VMS_POINTER.0, - ), - vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) }, - ..$crate::zeroable::Zeroable::ZERO - } $(.with_varray_flag_unchecked( - $crate::call_func_with_field!( - $crate::vmstate::vmstate_varray_flag, - $struct_name, - $num - ) - ) - $(.with_varray_multiply($factor))?)? - }}; -} - /// Helper macro to declare a list of /// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and return /// a pointer to the array of values it created. @@ -548,7 +421,7 @@ macro_rules! vmstate_fields { $($field),*, $crate::bindings::VMStateField { flags: $crate::bindings::VMStateFlags::VMS_END, - ..$crate::zeroable::Zeroable::ZERO + ..::common::zeroable::Zeroable::ZERO } ]; _FIELDS.as_ptr() @@ -567,7 +440,31 @@ macro_rules! vmstate_validate { | $crate::bindings::VMStateFlags::VMS_ARRAY.0, ), num: 0, // 0 elements: no data, only run test_fn callback - ..$crate::zeroable::Zeroable::ZERO + ..::common::zeroable::Zeroable::ZERO + } + }; +} + +/// Helper macro to allow using a struct in [`vmstate_of!`] +/// +/// # Safety +/// +/// The [`VMStateDescription`] constant `$vmsd` must be an accurate +/// description of the struct. +#[macro_export] +macro_rules! impl_vmstate_struct { + ($type:ty, $vmsd:expr) => { + unsafe impl $crate::vmstate::VMState for $type { + const BASE: $crate::bindings::VMStateField = { + static VMSD: &$crate::bindings::VMStateDescription = $vmsd.as_ref(); + + $crate::bindings::VMStateField { + vmsd: ::core::ptr::addr_of!(*VMSD), + size: ::core::mem::size_of::<$type>(), + flags: $crate::bindings::VMStateFlags::VMS_STRUCT, + ..common::Zeroable::ZERO + } + }; } }; } @@ -594,11 +491,225 @@ macro_rules! vmstate_subsections { ($($subsection:expr),*$(,)*) => {{ static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = $crate::vmstate::VMStateSubsectionsWrapper(&[ $({ - static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection; + static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection.get(); ::core::ptr::addr_of!(_SUBSECTION) }),*, ::core::ptr::null() ]); - _SUBSECTIONS.0.as_ptr() + &_SUBSECTIONS }} } + +pub struct VMStateDescription<T>(bindings::VMStateDescription, PhantomData<fn(&T)>); + +// SAFETY: When a *const T is passed to the callbacks, the call itself +// is done in a thread-safe manner. The invocation is okay as long as +// T itself is `Sync`. +unsafe impl<T: Sync> Sync for VMStateDescription<T> {} + +#[derive(Clone)] +pub struct VMStateDescriptionBuilder<T>(bindings::VMStateDescription, PhantomData<fn(&T)>); + +#[derive(Debug)] +pub struct InvalidError; + +impl Error for InvalidError {} + +impl std::fmt::Display for InvalidError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid migration data") + } +} + +impl From<InvalidError> for Errno { + fn from(_value: InvalidError) -> Errno { + io::ErrorKind::InvalidInput.into() + } +} + +unsafe extern "C" fn vmstate_no_version_cb< + T, + F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>, +>( + opaque: *mut c_void, +) -> c_int { + // SAFETY: the function is used in T's implementation of VMState + let result = F::call((unsafe { &*(opaque.cast::<T>()) },)); + into_neg_errno(result) +} + +unsafe extern "C" fn vmstate_post_load_cb< + T, + F: for<'a> FnCall<(&'a T, u8), Result<(), impl Into<Errno>>>, +>( + opaque: *mut c_void, + version_id: c_int, +) -> c_int { + // SAFETY: the function is used in T's implementation of VMState + let owner: &T = unsafe { &*(opaque.cast::<T>()) }; + let version: u8 = version_id.try_into().unwrap(); + let result = F::call((owner, version)); + into_neg_errno(result) +} + +unsafe extern "C" fn vmstate_needed_cb<T, F: for<'a> FnCall<(&'a T,), bool>>( + opaque: *mut c_void, +) -> bool { + // SAFETY: the function is used in T's implementation of VMState + F::call((unsafe { &*(opaque.cast::<T>()) },)) +} + +unsafe extern "C" fn vmstate_dev_unplug_pending_cb<T, F: for<'a> FnCall<(&'a T,), bool>>( + opaque: *mut c_void, +) -> bool { + // SAFETY: the function is used in T's implementation of VMState + F::call((unsafe { &*(opaque.cast::<T>()) },)) +} + +impl<T> VMStateDescriptionBuilder<T> { + #[must_use] + pub const fn name(mut self, name_str: &CStr) -> Self { + self.0.name = ::std::ffi::CStr::as_ptr(name_str); + self + } + + #[must_use] + pub const fn unmigratable(mut self) -> Self { + self.0.unmigratable = true; + self + } + + #[must_use] + pub const fn early_setup(mut self) -> Self { + self.0.early_setup = true; + self + } + + #[must_use] + pub const fn version_id(mut self, version: u8) -> Self { + self.0.version_id = version as c_int; + self + } + + #[must_use] + pub const fn minimum_version_id(mut self, min_version: u8) -> Self { + self.0.minimum_version_id = min_version as c_int; + self + } + + #[must_use] + pub const fn priority(mut self, priority: MigrationPriority) -> Self { + self.0.priority = priority; + self + } + + #[must_use] + pub const fn pre_load<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>( + mut self, + _f: &F, + ) -> Self { + self.0.pre_load = if F::IS_SOME { + Some(vmstate_no_version_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn post_load<F: for<'a> FnCall<(&'a T, u8), Result<(), impl Into<Errno>>>>( + mut self, + _f: &F, + ) -> Self { + self.0.post_load = if F::IS_SOME { + Some(vmstate_post_load_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn pre_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>( + mut self, + _f: &F, + ) -> Self { + self.0.pre_save = if F::IS_SOME { + Some(vmstate_no_version_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn post_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>( + mut self, + _f: &F, + ) -> Self { + self.0.post_save = if F::IS_SOME { + Some(vmstate_no_version_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn needed<F: for<'a> FnCall<(&'a T,), bool>>(mut self, _f: &F) -> Self { + self.0.needed = if F::IS_SOME { + Some(vmstate_needed_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn unplug_pending<F: for<'a> FnCall<(&'a T,), bool>>(mut self, _f: &F) -> Self { + self.0.dev_unplug_pending = if F::IS_SOME { + Some(vmstate_dev_unplug_pending_cb::<T, F>) + } else { + None + }; + self + } + + #[must_use] + pub const fn fields(mut self, fields: *const VMStateField) -> Self { + self.0.fields = fields; + self + } + + #[must_use] + pub const fn subsections(mut self, subs: &'static VMStateSubsectionsWrapper) -> Self { + self.0.subsections = subs.0.as_ptr(); + self + } + + #[must_use] + pub const fn build(self) -> VMStateDescription<T> { + VMStateDescription::<T>(self.0, PhantomData) + } + + #[must_use] + pub const fn new() -> Self { + Self(bindings::VMStateDescription::ZERO, PhantomData) + } +} + +impl<T> Default for VMStateDescriptionBuilder<T> { + fn default() -> Self { + Self::new() + } +} + +impl<T> VMStateDescription<T> { + pub const fn get(&self) -> bindings::VMStateDescription { + self.0 + } + + pub const fn as_ref(&self) -> &bindings::VMStateDescription { + &self.0 + } +} diff --git a/rust/qemu-api/wrapper.h b/rust/migration/wrapper.h index 15a1b19847..daf316aed4 100644 --- a/rust/qemu-api/wrapper.h +++ b/rust/migration/wrapper.h @@ -48,24 +48,4 @@ typedef enum memory_order { #endif /* __CLANG_STDATOMIC_H */ #include "qemu/osdep.h" -#include "qemu/log.h" -#include "qemu/log-for-trace.h" -#include "qemu/module.h" -#include "qemu-io.h" -#include "system/system.h" -#include "hw/sysbus.h" -#include "system/memory.h" -#include "chardev/char-fe.h" -#include "hw/clock.h" -#include "hw/qdev-clock.h" -#include "hw/qdev-properties.h" -#include "hw/qdev-properties-system.h" -#include "hw/irq.h" -#include "qapi/error.h" -#include "qapi/error-internal.h" #include "migration/vmstate.h" -#include "chardev/char-serial.h" -#include "exec/memattrs.h" -#include "qemu/timer.h" -#include "system/address-spaces.h" -#include "hw/char/pl011.h" diff --git a/rust/qemu-api/.gitignore b/rust/qemu-api/.gitignore deleted file mode 100644 index df6c2163e0..0000000000 --- a/rust/qemu-api/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore generated bindings file overrides. -/src/bindings.inc.rs diff --git a/rust/qemu-api/README.md b/rust/qemu-api/README.md deleted file mode 100644 index ed1b7ab263..0000000000 --- a/rust/qemu-api/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# QEMU bindings and API wrappers - -This library exports helper Rust types, Rust macros and C FFI bindings for internal QEMU APIs. - -The C bindings can be generated with `bindgen`, using this build target: - -```console -$ make bindings.inc.rs -``` - -## Generate Rust documentation - -Common Cargo tasks can be performed from the QEMU build directory - -```console -$ make clippy -$ make rustfmt -$ make rustdoc -``` diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build deleted file mode 100644 index a090297c45..0000000000 --- a/rust/qemu-api/meson.build +++ /dev/null @@ -1,114 +0,0 @@ -_qemu_api_cfg = run_command(rustc_args, - '--config-headers', config_host_h, '--features', files('Cargo.toml'), - capture: true, check: true).stdout().strip().splitlines() - -# _qemu_api_cfg += ['--cfg', 'feature="allocator"'] -if get_option('debug_mutex') - _qemu_api_cfg += ['--cfg', 'feature="debug_cell"'] -endif - -c_enums = [ - 'DeviceCategory', - 'GpioPolarity', - 'MachineInitPhase', - 'MemoryDeviceInfoKind', - 'MigrationPolicy', - 'MigrationPriority', - 'QEMUChrEvent', - 'QEMUClockType', - 'ResetType', - 'device_endian', - 'module_init_type', -] -_qemu_api_bindgen_args = [] -foreach enum : c_enums - _qemu_api_bindgen_args += ['--rustified-enum', enum] -endforeach -c_bitfields = [ - 'ClockEvent', - 'VMStateFlags', -] -foreach enum : c_bitfields - _qemu_api_bindgen_args += ['--bitfield-enum', enum] -endforeach - -# TODO: Remove this comment when the clang/libclang mismatch issue is solved. -# -# Rust bindings generation with `bindgen` might fail in some cases where the -# detected `libclang` does not match the expected `clang` version/target. In -# this case you must pass the path to `clang` and `libclang` to your build -# command invocation using the environment variables CLANG_PATH and -# LIBCLANG_PATH -_qemu_api_bindings_inc_rs = rust.bindgen( - input: 'wrapper.h', - dependencies: common_ss.all_dependencies(), - output: 'bindings.inc.rs', - include_directories: bindings_incdir, - bindgen_version: ['>=0.60.0'], - args: bindgen_args_common + _qemu_api_bindgen_args, - ) - -_qemu_api_rs = static_library( - 'qemu_api', - structured_sources( - [ - 'src/lib.rs', - 'src/assertions.rs', - 'src/bindings.rs', - 'src/bitops.rs', - 'src/callbacks.rs', - 'src/cell.rs', - 'src/chardev.rs', - 'src/errno.rs', - 'src/error.rs', - 'src/irq.rs', - 'src/log.rs', - 'src/memory.rs', - 'src/module.rs', - 'src/prelude.rs', - 'src/qdev.rs', - 'src/qom.rs', - 'src/sysbus.rs', - 'src/timer.rs', - 'src/uninit.rs', - 'src/vmstate.rs', - 'src/zeroable.rs', - ], - {'.' : _qemu_api_bindings_inc_rs}, - ), - override_options: ['rust_std=2021', 'build.rust_std=2021'], - rust_abi: 'rust', - rust_args: _qemu_api_cfg, - dependencies: [anyhow_rs, foreign_rs, libc_rs, qemu_api_macros, qemuutil_rs, - qom, hwcore, chardev, migration], -) - -rust.test('rust-qemu-api-tests', _qemu_api_rs, - suite: ['unit', 'rust']) - -qemu_api = declare_dependency(link_with: [_qemu_api_rs], - dependencies: [qemu_api_macros, qom, hwcore, chardev, migration]) - -# Doctests are essentially integration tests, so they need the same dependencies. -# Note that running them requires the object files for C code, so place them -# in a separate suite that is run by the "build" CI jobs rather than "check". -rust.doctest('rust-qemu-api-doctests', - _qemu_api_rs, - protocol: 'rust', - dependencies: qemu_api, - suite: ['doc', 'rust']) - -test('rust-qemu-api-integration', - executable( - 'rust-qemu-api-integration', - files('tests/tests.rs', 'tests/vmstate_tests.rs'), - override_options: ['rust_std=2021', 'build.rust_std=2021'], - rust_args: ['--test'], - install: false, - dependencies: [qemu_api]), - args: [ - '--test', '--test-threads', '1', - '--format', 'pretty', - ], - protocol: 'rust', - suite: ['unit', 'rust']) diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs deleted file mode 100644 index bcb51c7986..0000000000 --- a/rust/qemu-api/src/lib.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> -// SPDX-License-Identifier: GPL-2.0-or-later - -#![cfg_attr(not(MESON), doc = include_str!("../README.md"))] -#![deny(clippy::missing_const_for_fn)] - -#[rustfmt::skip] -pub mod bindings; - -// preserve one-item-per-"use" syntax, it is clearer -// for prelude-like modules -#[rustfmt::skip] -pub mod prelude; - -pub mod assertions; -pub mod bitops; -pub mod callbacks; -pub mod cell; -pub mod chardev; -pub mod errno; -pub mod error; -pub mod irq; -pub mod log; -pub mod memory; -pub mod module; -pub mod qdev; -pub mod qom; -pub mod sysbus; -pub mod timer; -pub mod uninit; -pub mod vmstate; -pub mod zeroable; - -// Allow proc-macros to refer to `::qemu_api` inside the `qemu_api` crate (this -// crate). -extern crate self as qemu_api; - -use std::{ - alloc::{GlobalAlloc, Layout}, - ffi::c_void, -}; - -pub use error::{Error, Result}; - -#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] -extern "C" { - fn g_aligned_alloc0( - n_blocks: bindings::gsize, - n_block_bytes: bindings::gsize, - alignment: bindings::gsize, - ) -> bindings::gpointer; - fn g_aligned_free(mem: bindings::gpointer); -} - -#[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))] -extern "C" { - fn qemu_memalign(alignment: usize, size: usize) -> *mut c_void; - fn qemu_vfree(ptr: *mut c_void); -} - -extern "C" { - fn g_malloc0(n_bytes: bindings::gsize) -> bindings::gpointer; - fn g_free(mem: bindings::gpointer); -} - -/// An allocator that uses the same allocator as QEMU in C. -/// -/// It is enabled by default with the `allocator` feature. -/// -/// To set it up manually as a global allocator in your crate: -/// -/// ```ignore -/// use qemu_api::QemuAllocator; -/// -/// #[global_allocator] -/// static GLOBAL: QemuAllocator = QemuAllocator::new(); -/// ``` -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct QemuAllocator { - _unused: [u8; 0], -} - -#[cfg_attr(all(feature = "allocator", not(test)), global_allocator)] -pub static GLOBAL: QemuAllocator = QemuAllocator::new(); - -impl QemuAllocator { - // From the glibc documentation, on GNU systems, malloc guarantees 16-byte - // alignment on 64-bit systems and 8-byte alignment on 32-bit systems. See - // https://www.gnu.org/software/libc/manual/html_node/Malloc-Examples.html. - // This alignment guarantee also applies to Windows and Android. On Darwin - // and OpenBSD, the alignment is 16 bytes on both 64-bit and 32-bit systems. - #[cfg(all( - target_pointer_width = "32", - not(any(target_os = "macos", target_os = "openbsd")) - ))] - pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(8); - #[cfg(all( - target_pointer_width = "64", - not(any(target_os = "macos", target_os = "openbsd")) - ))] - pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(16); - #[cfg(all( - any(target_pointer_width = "32", target_pointer_width = "64"), - any(target_os = "macos", target_os = "openbsd") - ))] - pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(16); - #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] - pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = None; - - pub const fn new() -> Self { - Self { _unused: [] } - } -} - -impl Default for QemuAllocator { - fn default() -> Self { - Self::new() - } -} - -// Sanity check. -const _: [(); 8] = [(); ::core::mem::size_of::<*mut c_void>()]; - -unsafe impl GlobalAlloc for QemuAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - if matches!(Self::DEFAULT_ALIGNMENT_BYTES, Some(default) if default.checked_rem(layout.align()) == Some(0)) - { - // SAFETY: g_malloc0() is safe to call. - unsafe { g_malloc0(layout.size().try_into().unwrap()).cast::<u8>() } - } else { - #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] - { - // SAFETY: g_aligned_alloc0() is safe to call. - unsafe { - g_aligned_alloc0( - layout.size().try_into().unwrap(), - 1, - layout.align().try_into().unwrap(), - ) - .cast::<u8>() - } - } - #[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))] - { - // SAFETY: qemu_memalign() is safe to call. - unsafe { qemu_memalign(layout.align(), layout.size()).cast::<u8>() } - } - } - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - if matches!(Self::DEFAULT_ALIGNMENT_BYTES, Some(default) if default.checked_rem(layout.align()) == Some(0)) - { - // SAFETY: `ptr` must have been allocated by Self::alloc thus a valid - // glib-allocated pointer, so `g_free`ing is safe. - unsafe { g_free(ptr.cast::<_>()) } - } else { - #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] - { - // SAFETY: `ptr` must have been allocated by Self::alloc thus a valid aligned - // glib-allocated pointer, so `g_aligned_free`ing is safe. - unsafe { g_aligned_free(ptr.cast::<_>()) } - } - #[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))] - { - // SAFETY: `ptr` must have been allocated by Self::alloc thus a valid aligned - // glib-allocated pointer, so `qemu_vfree`ing is safe. - unsafe { qemu_vfree(ptr.cast::<_>()) } - } - } - } -} diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs deleted file mode 100644 index 8f9e23ee2c..0000000000 --- a/rust/qemu-api/src/prelude.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2024 Red Hat, Inc. -// Author(s): Paolo Bonzini <pbonzini@redhat.com> -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Commonly used traits and types for QEMU. - -pub use crate::bitops::IntegerExt; - -pub use crate::cell::BqlCell; -pub use crate::cell::BqlRefCell; - -pub use crate::errno; - -pub use crate::log_mask_ln; - -pub use crate::qdev::DeviceMethods; - -pub use crate::qom::InterfaceType; -pub use crate::qom::IsA; -pub use crate::qom::Object; -pub use crate::qom::ObjectCast; -pub use crate::qom::ObjectDeref; -pub use crate::qom::ObjectClassMethods; -pub use crate::qom::ObjectMethods; -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/zeroable.rs b/rust/qemu-api/src/zeroable.rs deleted file mode 100644 index d8239d0856..0000000000 --- a/rust/qemu-api/src/zeroable.rs +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Defines a trait for structs that can be safely initialized with zero bytes. - -/// Encapsulates the requirement that -/// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined -/// behavior. -/// -/// # Safety -/// -/// Do not add this trait to a type unless all-zeroes is a valid value for the -/// type. In particular, raw pointers can be zero, but references and -/// `NonNull<T>` cannot. -pub unsafe trait Zeroable: Default { - /// Return a value of Self whose memory representation consists of all - /// zeroes, with the possible exclusion of padding bytes. - const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() }; -} - -// 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::Property__bindgen_ty_1 {} -unsafe impl Zeroable for crate::bindings::Property {} -unsafe impl Zeroable for crate::bindings::VMStateFlags {} -unsafe impl Zeroable for crate::bindings::VMStateField {} -unsafe impl Zeroable for crate::bindings::VMStateDescription {} -unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 {} -unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 {} -unsafe impl Zeroable for crate::bindings::MemoryRegionOps {} -unsafe impl Zeroable for crate::bindings::MemTxAttrs {} -unsafe impl Zeroable for crate::bindings::CharBackend {} diff --git a/rust/qemu-api-macros/Cargo.toml b/rust/qemu-macros/Cargo.toml index 0cd40c8e16..3b6f1d337f 100644 --- a/rust/qemu-api-macros/Cargo.toml +++ b/rust/qemu-macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "qemu_api_macros" +name = "qemu_macros" version = "0.1.0" authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"] description = "Rust bindings for QEMU - Utility macros" diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-macros/meson.build index 2152bcb99b..d0b2992e20 100644 --- a/rust/qemu-api-macros/meson.build +++ b/rust/qemu-macros/meson.build @@ -1,5 +1,5 @@ -_qemu_api_macros_rs = rust.proc_macro( - 'qemu_api_macros', +_qemu_macros_rs = rust.proc_macro( + 'qemu_macros', files('src/lib.rs'), override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: [ @@ -14,9 +14,9 @@ _qemu_api_macros_rs = rust.proc_macro( ], ) -qemu_api_macros = declare_dependency( - link_with: _qemu_api_macros_rs, +qemu_macros = declare_dependency( + link_with: _qemu_macros_rs, ) -rust.test('rust-qemu-api-macros-tests', _qemu_api_macros_rs, +rust.test('rust-qemu-macros-tests', _qemu_macros_rs, suite: ['unit', 'rust']) diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-macros/src/bits.rs index a80a3b9fee..a80a3b9fee 100644 --- a/rust/qemu-api-macros/src/bits.rs +++ b/rust/qemu-macros/src/bits.rs diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-macros/src/lib.rs index 959726efe6..830b432698 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-macros/src/lib.rs @@ -3,10 +3,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later use proc_macro::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned, ToTokens}; use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, + parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, + token::Comma, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, + Variant, }; mod bits; use bits::BitsConstInternal; @@ -96,12 +97,12 @@ fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream .ident; Ok(quote! { - ::qemu_api::assert_field_type!(#name, #parent, - ::qemu_api::qom::ParentField<<#name as ::qemu_api::qom::ObjectImpl>::ParentType>); + ::common::assert_field_type!(#name, #parent, + ::qom::ParentField<<#name as ::qom::ObjectImpl>::ParentType>); - ::qemu_api::module_init! { + ::util::module_init! { MODULE_INIT_QOM => unsafe { - ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); + ::qom::type_register_static(&<#name as ::qom::ObjectImpl>::TYPE_INFO); } } }) @@ -124,20 +125,20 @@ fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream let typ = &field.ty; Ok(quote! { - unsafe impl ::qemu_api::cell::Wrapper for #name { - type Wrapped = <#typ as ::qemu_api::cell::Wrapper>::Wrapped; + unsafe impl ::common::opaque::Wrapper for #name { + type Wrapped = <#typ as ::common::opaque::Wrapper>::Wrapped; } impl #name { - pub unsafe fn from_raw<'a>(ptr: *mut <Self as ::qemu_api::cell::Wrapper>::Wrapped) -> &'a Self { + pub unsafe fn from_raw<'a>(ptr: *mut <Self as ::common::opaque::Wrapper>::Wrapped) -> &'a Self { let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::<Self>(); unsafe { ptr.as_ref() } } - pub const fn as_mut_ptr(&self) -> *mut <Self as ::qemu_api::cell::Wrapper>::Wrapped { + pub const fn as_mut_ptr(&self) -> *mut <Self as ::common::opaque::Wrapper>::Wrapped { self.0.as_mut_ptr() } - pub const fn as_ptr(&self) -> *const <Self as ::qemu_api::cell::Wrapper>::Wrapped { + pub const fn as_ptr(&self) -> *const <Self as ::common::opaque::Wrapper>::Wrapped { self.0.as_ptr() } @@ -145,13 +146,156 @@ fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream self.0.as_void_ptr() } - pub const fn raw_get(slot: *mut Self) -> *mut <Self as ::qemu_api::cell::Wrapper>::Wrapped { + pub const fn raw_get(slot: *mut Self) -> *mut <Self as ::common::opaque::Wrapper>::Wrapped { slot.cast() } } }) } +#[derive(Debug)] +enum DevicePropertyName { + CStr(syn::LitCStr), + Str(syn::LitStr), +} + +#[derive(Debug)] +struct DeviceProperty { + rename: Option<DevicePropertyName>, + defval: Option<syn::Expr>, +} + +impl Parse for DeviceProperty { + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { + let _: syn::Token![#] = input.parse()?; + let bracketed; + _ = syn::bracketed!(bracketed in input); + let attribute = bracketed.parse::<syn::Ident>()?; + debug_assert_eq!(&attribute.to_string(), "property"); + let mut retval = Self { + rename: None, + defval: None, + }; + let content; + _ = syn::parenthesized!(content in bracketed); + while !content.is_empty() { + let value: syn::Ident = content.parse()?; + if value == "rename" { + let _: syn::Token![=] = content.parse()?; + if retval.rename.is_some() { + return Err(syn::Error::new( + value.span(), + "`rename` can only be used at most once", + )); + } + if content.peek(syn::LitStr) { + retval.rename = Some(DevicePropertyName::Str(content.parse::<syn::LitStr>()?)); + } else { + retval.rename = + Some(DevicePropertyName::CStr(content.parse::<syn::LitCStr>()?)); + } + } else if value == "default" { + let _: syn::Token![=] = content.parse()?; + if retval.defval.is_some() { + return Err(syn::Error::new( + value.span(), + "`default` can only be used at most once", + )); + } + retval.defval = Some(content.parse()?); + } else { + return Err(syn::Error::new( + value.span(), + format!("unrecognized field `{value}`"), + )); + } + + if !content.is_empty() { + let _: syn::Token![,] = content.parse()?; + } + } + Ok(retval) + } +} + +#[proc_macro_derive(Device, attributes(property))] +pub fn derive_device(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_device_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { + is_c_repr(&input, "#[derive(Device)]")?; + let properties: Vec<(syn::Field, DeviceProperty)> = get_fields(&input, "#[derive(Device)]")? + .iter() + .flat_map(|f| { + f.attrs + .iter() + .filter(|a| a.path().is_ident("property")) + .map(|a| Ok((f.clone(), syn::parse2(a.to_token_stream())?))) + }) + .collect::<Result<Vec<_>, Error>>()?; + let name = &input.ident; + let mut properties_expanded = vec![]; + + for (field, prop) in properties { + let DeviceProperty { rename, defval } = prop; + let field_name = field.ident.unwrap(); + macro_rules! str_to_c_str { + ($value:expr, $span:expr) => {{ + let (value, span) = ($value, $span); + let cstr = std::ffi::CString::new(value.as_str()).map_err(|err| { + Error::new( + span, + format!( + "Property name `{value}` cannot be represented as a C string: {err}" + ), + ) + })?; + let cstr_lit = syn::LitCStr::new(&cstr, span); + Ok(quote! { #cstr_lit }) + }}; + } + + let prop_name = rename.map_or_else( + || str_to_c_str!(field_name.to_string(), field_name.span()), + |rename| -> Result<proc_macro2::TokenStream, Error> { + match rename { + DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }), + DevicePropertyName::Str(str_lit) => { + str_to_c_str!(str_lit.value(), str_lit.span()) + } + } + }, + )?; + let field_ty = field.ty.clone(); + let qdev_prop = quote! { <#field_ty as ::hwcore::QDevProp>::VALUE }; + let set_default = defval.is_some(); + let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); + properties_expanded.push(quote! { + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(#prop_name), + info: #qdev_prop , + offset: ::core::mem::offset_of!(#name, #field_name) as isize, + set_default: #set_default, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 }, + ..::common::Zeroable::ZERO + } + }); + } + + Ok(quote_spanned! {input.span() => + unsafe impl ::hwcore::DevicePropertiesImpl for #name { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + #(#properties_expanded),* + ]; + } + }) +} + #[proc_macro_derive(Wrapper)] pub fn derive_opaque(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-macros/src/tests.rs index 6028cdbc4c..9ab7eab7f3 100644 --- a/rust/qemu-api-macros/src/tests.rs +++ b/rust/qemu-macros/src/tests.rs @@ -37,6 +37,113 @@ macro_rules! derive_compile { } #[test] +fn test_derive_device() { + // Check that repr(C) is used + derive_compile_fail!( + derive_device_or_error, + quote! { + #[derive(Device)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Device)]" + ); + // Check that invalid/misspelled attributes raise an error + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(defalt = true)] + migrate_clock: bool, + } + }, + "unrecognized field `defalt`" + ); + // Check that repeated attributes are not allowed: + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(rename = "migrate-clk", rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + "`rename` can only be used at most once" + ); + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(default = true, default = true)] + migrate_clock: bool, + } + }, + "`default` can only be used at most once" + ); + // Check that the field name is preserved when `rename` isn't used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate_clock"), + info: <bool as ::hwcore::QDevProp>::VALUE, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `rename` value is used for the property name when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate-clk"), + info: <bool as ::hwcore::QDevProp>::VALUE, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); +} + +#[test] fn test_derive_object() { derive_compile_fail!( derive_object_or_error, @@ -58,14 +165,14 @@ fn test_derive_object() { } }, quote! { - ::qemu_api::assert_field_type!( + ::common::assert_field_type!( Foo, _unused, - ::qemu_api::qom::ParentField<<Foo as ::qemu_api::qom::ObjectImpl>::ParentType> + ::qom::ParentField<<Foo as ::qom::ObjectImpl>::ParentType> ); - ::qemu_api::module_init! { + ::util::module_init! { MODULE_INIT_QOM => unsafe { - ::qemu_api::bindings::type_register_static(&<Foo as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); + ::qom::type_register_static(&<Foo as ::qom::ObjectImpl>::TYPE_INFO); } } } diff --git a/rust/qom/Cargo.toml b/rust/qom/Cargo.toml new file mode 100644 index 0000000000..060ad2ec34 --- /dev/null +++ b/rust/qom/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "qom" +version = "0.1.0" +description = "Rust bindings for QEMU/QOM" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../common" } +bql = { path = "../bql" } +migration = { path = "../migration" } +qemu_macros = { path = "../qemu-macros" } +util = { path = "../util" } + +[lints] +workspace = true diff --git a/rust/qom/build.rs b/rust/qom/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/qom/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/qom/meson.build b/rust/qom/meson.build new file mode 100644 index 0000000000..40c51b71b2 --- /dev/null +++ b/rust/qom/meson.build @@ -0,0 +1,43 @@ +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_qom_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common, +) + +_qom_rs = static_library( + 'qom', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/prelude.rs', + 'src/qom.rs', + ], + {'.': _qom_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_bql_rs, _migration_rs], + dependencies: [common_rs, qemu_macros], +) + +qom_rs = declare_dependency(link_with: [_qom_rs], dependencies: [qemu_macros, qom]) + +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-qom-rs-doctests', + _qom_rs, + protocol: 'rust', + dependencies: qom_rs, + suite: ['doc', 'rust']) diff --git a/rust/qom/src/bindings.rs b/rust/qom/src/bindings.rs new file mode 100644 index 0000000000..9ffff12cde --- /dev/null +++ b/rust/qom/src/bindings.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); diff --git a/rust/qom/src/lib.rs b/rust/qom/src/lib.rs new file mode 100644 index 0000000000..24c44fc2af --- /dev/null +++ b/rust/qom/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub use qemu_macros::Object; + +pub mod bindings; + +// preserve one-item-per-"use" syntax, it is clearer +// for prelude-like modules +#[rustfmt::skip] +pub mod prelude; + +mod qom; +pub use qom::*; diff --git a/rust/qom/src/prelude.rs b/rust/qom/src/prelude.rs new file mode 100644 index 0000000000..00a6095977 --- /dev/null +++ b/rust/qom/src/prelude.rs @@ -0,0 +1,12 @@ +//! Traits and essential types intended for blanket imports. + +pub use crate::qom::InterfaceType; +pub use crate::qom::IsA; +pub use crate::qom::Object; +pub use crate::qom::ObjectCast; +pub use crate::qom::ObjectClassMethods; +pub use crate::qom::ObjectDeref; +pub use crate::qom::ObjectMethods; +pub use crate::qom::ObjectType; + +pub use crate::qom_isa; diff --git a/rust/qemu-api/src/qom.rs b/rust/qom/src/qom.rs index e20ee014cb..5808051cd7 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qom/src/qom.rs @@ -101,19 +101,18 @@ use std::{ ptr::NonNull, }; -pub use bindings::ObjectClass; - -use crate::{ - bindings::{ - self, object_class_dynamic_cast, object_dynamic_cast, object_get_class, - object_get_typename, object_new, object_ref, object_unref, TypeInfo, - }, - cell::{bql_locked, Opaque}, +use common::Opaque; +use migration::impl_vmstate_pointer; + +use crate::bindings::{ + self, object_class_dynamic_cast, object_dynamic_cast, object_get_class, object_get_typename, + object_new, object_ref, object_unref, TypeInfo, }; +pub use crate::bindings::{type_register_static, ObjectClass}; /// A safe wrapper around [`bindings::Object`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct Object(Opaque<bindings::Object>); unsafe impl Send for Object {} @@ -147,7 +146,7 @@ macro_rules! qom_isa { $( // SAFETY: it is the caller responsibility to have $parent as the // first field - unsafe impl $crate::qom::IsA<$parent> for $struct {} + unsafe impl $crate::IsA<$parent> for $struct {} impl AsRef<$parent> for $struct { fn as_ref(&self) -> &$parent { @@ -174,7 +173,7 @@ macro_rules! qom_isa { /// /// ```ignore /// #[repr(C)] -/// #[derive(qemu_api_macros::Object)] +/// #[derive(qom::Object)] /// pub struct MyDevice { /// parent: ParentField<DeviceState>, /// ... @@ -307,7 +306,7 @@ impl<T: ObjectType> ParentInit<'_, T> { /// Fields beyond `Object` could be uninitialized and it's your /// responsibility to avoid that they're used when the pointer is /// dereferenced, either directly or through a cast. - pub fn as_object_mut_ptr(&self) -> *mut bindings::Object { + pub const fn as_object_mut_ptr(&self) -> *mut bindings::Object { self.as_object_ptr().cast_mut() } @@ -318,7 +317,7 @@ impl<T: ObjectType> ParentInit<'_, T> { /// Fields beyond `Object` could be uninitialized and it's your /// responsibility to avoid that they're used when the pointer is /// dereferenced, either directly or through a cast. - pub fn as_object_ptr(&self) -> *const bindings::Object { + pub const fn as_object_ptr(&self) -> *const bindings::Object { self.0.as_ptr().cast() } } @@ -336,7 +335,7 @@ impl<'a, T: ObjectImpl> ParentInit<'a, T> { /// However, while the fields of the resulting reference are initialized, /// calls might use uninitialized fields of the subclass. It is your /// responsibility to avoid this. - pub unsafe fn upcast<U: ObjectType>(&self) -> &'a U + pub const unsafe fn upcast<U: ObjectType>(&self) -> &'a U where T::ParentType: IsA<U>, { @@ -871,7 +870,7 @@ impl<T: ObjectType> ObjectDeref for Owned<T> {} impl<T: ObjectType> Drop for Owned<T> { fn drop(&mut self) { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: creation method is unsafe, and whoever calls it has // responsibility that the pointer is valid, and remains valid // throughout the lifetime of the `Owned<T>` and its clones. @@ -895,7 +894,7 @@ impl<T: IsA<Object>> fmt::Debug for Owned<T> { pub trait ObjectClassMethods: IsA<Object> { /// Return a new reference counted instance of this class fn new() -> Owned<Self> { - assert!(bql_locked()); + assert!(bql::is_locked()); // SAFETY: the object created by object_new is allocated on // the heap and has a reference count of 1 unsafe { @@ -948,3 +947,5 @@ where impl<T> ObjectClassMethods for T where T: IsA<Object> {} impl<R: ObjectDeref> ObjectMethods for R where R::Target: IsA<Object> {} + +impl_vmstate_pointer!(Owned<T> where T: VMState + ObjectType); diff --git a/rust/qom/wrapper.h b/rust/qom/wrapper.h new file mode 100644 index 0000000000..3b71bcd3f5 --- /dev/null +++ b/rust/qom/wrapper.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "qom/object.h" diff --git a/rust/system/Cargo.toml b/rust/system/Cargo.toml new file mode 100644 index 0000000000..7fd369b9e3 --- /dev/null +++ b/rust/system/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "system" +version = "0.1.0" +description = "Rust bindings for QEMU/system" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../common" } +qom = { path = "../qom" } +util = { path = "../util" } + +[lints] +workspace = true diff --git a/rust/system/build.rs b/rust/system/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/system/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/system/meson.build b/rust/system/meson.build new file mode 100644 index 0000000000..3ec140de01 --- /dev/null +++ b/rust/system/meson.build @@ -0,0 +1,42 @@ +c_enums = [ + 'device_endian', +] +_system_bindgen_args = [] +foreach enum : c_enums + _system_bindgen_args += ['--rustified-enum', enum] +endforeach + +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_system_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _system_bindgen_args, +) + +_system_rs = static_library( + 'system', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/memory.rs', + ], + {'.': _system_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_bql_rs, _migration_rs, _qom_rs, _util_rs], + dependencies: [common_rs, qemu_macros], +) + +system_rs = declare_dependency(link_with: [_system_rs], + dependencies: [hwcore]) diff --git a/rust/system/src/bindings.rs b/rust/system/src/bindings.rs new file mode 100644 index 0000000000..43edd98807 --- /dev/null +++ b/rust/system/src/bindings.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +use common::Zeroable; + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); + +// SAFETY: these are constants and vtables; the Send and Sync requirements +// are deferred to the unsafe callbacks that they contain +unsafe impl Send for MemoryRegionOps {} +unsafe impl Sync for MemoryRegionOps {} + +// SAFETY: this is a pure data struct +unsafe impl Send for CoalescedMemoryRange {} +unsafe impl Sync for CoalescedMemoryRange {} + +unsafe impl Zeroable for MemoryRegionOps__bindgen_ty_1 {} +unsafe impl Zeroable for MemoryRegionOps__bindgen_ty_2 {} +unsafe impl Zeroable for MemoryRegionOps {} +unsafe impl Zeroable for MemTxAttrs {} diff --git a/rust/system/src/lib.rs b/rust/system/src/lib.rs new file mode 100644 index 0000000000..aafe9a866c --- /dev/null +++ b/rust/system/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub mod bindings; + +mod memory; +pub use memory::*; diff --git a/rust/qemu-api/src/memory.rs b/rust/system/src/memory.rs index e40fad6cf1..4b3316bf76 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/system/src/memory.rs @@ -9,16 +9,11 @@ use std::{ marker::PhantomData, }; -pub use bindings::{hwaddr, MemTxAttrs}; - -use crate::{ - bindings::{self, device_endian, memory_region_init_io}, - callbacks::FnCall, - cell::Opaque, - prelude::*, - uninit::MaybeUninitField, - zeroable::Zeroable, -}; +use common::{callbacks::FnCall, uninit::MaybeUninitField, zeroable::Zeroable, Opaque}; +use qom::prelude::*; + +use crate::bindings::{self, device_endian, memory_region_init_io}; +pub use crate::bindings::{hwaddr, MemTxAttrs}; pub struct MemoryRegionOps<T>( bindings::MemoryRegionOps, @@ -134,18 +129,13 @@ impl<T> Default for MemoryRegionOpsBuilder<T> { /// A safe wrapper around [`bindings::MemoryRegion`]. #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(common::Wrapper)] pub struct MemoryRegion(Opaque<bindings::MemoryRegion>); unsafe impl Send for MemoryRegion {} unsafe impl Sync for MemoryRegion {} impl MemoryRegion { - // inline to ensure that it is not included in tests, which only - // link to hwcore and qom. FIXME: inlining is actually the opposite - // of what we want, since this is the type-erased version of the - // init_io function below. Look into splitting the qemu_api crate. - #[inline(always)] unsafe fn do_init_io( slot: *mut bindings::MemoryRegion, owner: *mut bindings::Object, @@ -189,6 +179,7 @@ unsafe impl ObjectType for MemoryRegion { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_MEMORY_REGION) }; } + qom_isa!(MemoryRegion: Object); /// A special `MemTxAttrs` constant, used to indicate that no memory diff --git a/rust/system/wrapper.h b/rust/system/wrapper.h new file mode 100644 index 0000000000..48abde8505 --- /dev/null +++ b/rust/system/wrapper.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "system/system.h" +#include "system/memory.h" +#include "system/address-spaces.h" diff --git a/rust/tests/Cargo.toml b/rust/tests/Cargo.toml new file mode 100644 index 0000000000..d47dc3314d --- /dev/null +++ b/rust/tests/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "tests" +version = "0.1.0" +description = "Rust integration tests for QEMU" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../common" } +chardev = { path = "../chardev" } +hwcore = { path = "../hw/core" } +migration = { path = "../migration" } +util = { path = "../util" } +bql = { path = "../bql" } +qom = { path = "../qom" } +system = { path = "../system" } + +[lints] +workspace = true diff --git a/rust/tests/meson.build b/rust/tests/meson.build new file mode 100644 index 0000000000..00688c66fb --- /dev/null +++ b/rust/tests/meson.build @@ -0,0 +1,14 @@ +test('rust-integration', + executable( + 'rust-integration', + files('tests/vmstate_tests.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_args: ['--test'], + install: false, + dependencies: [bql_rs, common_rs, util_rs, migration_rs, qom_rs]), + args: [ + '--test', '--test-threads', '1', + '--format', 'pretty', + ], + protocol: 'rust', + suite: ['unit', 'rust']) diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/tests/tests/vmstate_tests.rs index bded836eb6..fa9bbd6a12 100644 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ b/rust/tests/tests/vmstate_tests.rs @@ -9,16 +9,16 @@ use std::{ slice, }; -use qemu_api::{ +use bql::BqlCell; +use common::Opaque; +use migration::{ bindings::{ vmstate_info_bool, vmstate_info_int32, vmstate_info_int64, vmstate_info_int8, vmstate_info_uint64, vmstate_info_uint8, vmstate_info_unused_buffer, VMStateFlags, }, - cell::{BqlCell, Opaque}, - impl_vmstate_forward, - vmstate::{VMStateDescription, VMStateField}, - vmstate_fields, vmstate_of, vmstate_struct, vmstate_unused, vmstate_validate, - zeroable::Zeroable, + impl_vmstate_forward, impl_vmstate_struct, + vmstate::{VMStateDescription, VMStateDescriptionBuilder, VMStateField}, + vmstate_fields, vmstate_of, vmstate_unused, vmstate_validate, }; const FOO_ARRAY_MAX: usize = 3; @@ -41,22 +41,24 @@ struct FooA { elem: i8, } -static VMSTATE_FOOA: VMStateDescription = VMStateDescription { - name: c"foo_a".as_ptr(), - version_id: 1, - minimum_version_id: 1, - fields: vmstate_fields! { +static VMSTATE_FOOA: VMStateDescription<FooA> = VMStateDescriptionBuilder::<FooA>::new() + .name(c"foo_a") + .version_id(1) + .minimum_version_id(1) + .fields(vmstate_fields! { vmstate_of!(FooA, elem), vmstate_unused!(size_of::<i64>()), vmstate_of!(FooA, arr[0 .. num]).with_version_id(0), vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]), - }, - ..Zeroable::ZERO -}; + }) + .build(); + +impl_vmstate_struct!(FooA, VMSTATE_FOOA); #[test] fn test_vmstate_uint16() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) }; // 1st VMStateField ("elem") in VMSTATE_FOOA (corresponding to VMSTATE_UINT16) assert_eq!( @@ -76,7 +78,8 @@ fn test_vmstate_uint16() { #[test] fn test_vmstate_unused() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) }; // 2nd VMStateField ("unused") in VMSTATE_FOOA (corresponding to VMSTATE_UNUSED) assert_eq!( @@ -96,7 +99,8 @@ fn test_vmstate_unused() { #[test] fn test_vmstate_varray_uint16_unsafe() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) }; // 3rd VMStateField ("arr") in VMSTATE_FOOA (corresponding to // VMSTATE_VARRAY_UINT16_UNSAFE) @@ -117,7 +121,8 @@ fn test_vmstate_varray_uint16_unsafe() { #[test] fn test_vmstate_varray_multiply() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) }; // 4th VMStateField ("arr_mul") in VMSTATE_FOOA (corresponding to // VMSTATE_VARRAY_MULTIPLY) @@ -171,24 +176,24 @@ fn validate_foob(_state: &FooB, _version_id: u8) -> bool { true } -static VMSTATE_FOOB: VMStateDescription = VMStateDescription { - name: c"foo_b".as_ptr(), - version_id: 2, - minimum_version_id: 1, - fields: vmstate_fields! { +static VMSTATE_FOOB: VMStateDescription<FooB> = VMStateDescriptionBuilder::<FooB>::new() + .name(c"foo_b") + .version_id(2) + .minimum_version_id(1) + .fields(vmstate_fields! { vmstate_of!(FooB, val).with_version_id(2), vmstate_of!(FooB, wrap), - vmstate_struct!(FooB, arr_a[0 .. num_a], &VMSTATE_FOOA, FooA).with_version_id(1), - vmstate_struct!(FooB, arr_a_mul[0 .. num_a_mul * 32], &VMSTATE_FOOA, FooA).with_version_id(2), + vmstate_of!(FooB, arr_a[0 .. num_a]).with_version_id(1), + vmstate_of!(FooB, arr_a_mul[0 .. num_a_mul * 32]).with_version_id(2), vmstate_of!(FooB, arr_i64), - vmstate_struct!(FooB, arr_a_wrap[0 .. num_a_wrap], &VMSTATE_FOOA, FooA, validate_foob), - }, - ..Zeroable::ZERO -}; + vmstate_of!(FooB, arr_a_wrap[0 .. num_a_wrap], validate_foob), + }) + .build(); #[test] fn test_vmstate_bool_v() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; // 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V) assert_eq!( @@ -208,7 +213,8 @@ fn test_vmstate_bool_v() { #[test] fn test_vmstate_uint64() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; // 2nd VMStateField ("wrap") in VMSTATE_FOOB (corresponding to VMSTATE_U64) assert_eq!( @@ -228,7 +234,8 @@ fn test_vmstate_uint64() { #[test] fn test_vmstate_struct_varray_uint8() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; // 3rd VMStateField ("arr_a") in VMSTATE_FOOB (corresponding to // VMSTATE_STRUCT_VARRAY_UINT8) @@ -246,13 +253,14 @@ fn test_vmstate_struct_varray_uint8() { foo_fields[2].flags.0, VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_VARRAY_UINT8.0 ); - assert_eq!(foo_fields[2].vmsd, &VMSTATE_FOOA); + assert_eq!(foo_fields[2].vmsd, VMSTATE_FOOA.as_ref()); assert!(foo_fields[2].field_exists.is_none()); } #[test] fn test_vmstate_struct_varray_uint32_multiply() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; // 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to // (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32) @@ -272,13 +280,14 @@ fn test_vmstate_struct_varray_uint32_multiply() { | VMStateFlags::VMS_VARRAY_UINT32.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0 ); - assert_eq!(foo_fields[3].vmsd, &VMSTATE_FOOA); + assert_eq!(foo_fields[3].vmsd, VMSTATE_FOOA.as_ref()); assert!(foo_fields[3].field_exists.is_none()); } #[test] fn test_vmstate_macro_array() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; // 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to // VMSTATE_ARRAY) @@ -299,7 +308,8 @@ fn test_vmstate_macro_array() { #[test] fn test_vmstate_struct_varray_uint8_wrapper() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) }; let mut foo_b: FooB = Default::default(); let foo_b_p = std::ptr::addr_of_mut!(foo_b).cast::<c_void>(); @@ -335,26 +345,26 @@ struct FooC { arr_ptr_wrap: FooCWrapper, } -static VMSTATE_FOOC: VMStateDescription = VMStateDescription { - name: c"foo_c".as_ptr(), - version_id: 3, - minimum_version_id: 1, - fields: vmstate_fields! { +unsafe impl Sync for FooC {} + +static VMSTATE_FOOC: VMStateDescription<FooC> = VMStateDescriptionBuilder::<FooC>::new() + .name(c"foo_c") + .version_id(3) + .minimum_version_id(1) + .fields(vmstate_fields! { vmstate_of!(FooC, ptr).with_version_id(2), - // FIXME: Currently vmstate_struct doesn't support the pointer to structure. - // VMSTATE_STRUCT_POINTER: vmstate_struct!(FooC, ptr_a, VMSTATE_FOOA, NonNull<FooA>) - vmstate_unused!(size_of::<NonNull<FooA>>()), + vmstate_of!(FooC, ptr_a), vmstate_of!(FooC, arr_ptr), vmstate_of!(FooC, arr_ptr_wrap), - }, - ..Zeroable::ZERO -}; + }) + .build(); const PTR_SIZE: usize = size_of::<*mut ()>(); #[test] fn test_vmstate_pointer() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) }; // 1st VMStateField ("ptr") in VMSTATE_FOOC (corresponding to VMSTATE_POINTER) assert_eq!( @@ -376,8 +386,34 @@ fn test_vmstate_pointer() { } #[test] +fn test_vmstate_struct_pointer() { + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) }; + + // 2st VMStateField ("ptr_a") in VMSTATE_FOOC (corresponding to + // VMSTATE_STRUCT_POINTER) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), + b"ptr_a\0" + ); + assert_eq!(foo_fields[1].offset, PTR_SIZE); + assert_eq!(foo_fields[1].num_offset, 0); + assert_eq!(foo_fields[1].vmsd, VMSTATE_FOOA.as_ref()); + assert_eq!(foo_fields[1].version_id, 0); + assert_eq!(foo_fields[1].size, size_of::<FooA>()); + assert_eq!(foo_fields[1].num, 0); + assert_eq!( + foo_fields[1].flags.0, + VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0 + ); + assert!(foo_fields[1].info.is_null()); + assert!(foo_fields[1].field_exists.is_none()); +} + +#[test] fn test_vmstate_macro_array_of_pointer() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) }; // 3rd VMStateField ("arr_ptr") in VMSTATE_FOOC (corresponding to // VMSTATE_ARRAY_OF_POINTER) @@ -401,7 +437,8 @@ fn test_vmstate_macro_array_of_pointer() { #[test] fn test_vmstate_macro_array_of_pointer_wrapped() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) }; // 4th VMStateField ("arr_ptr_wrap") in VMSTATE_FOOC (corresponding to // VMSTATE_ARRAY_OF_POINTER) @@ -432,8 +469,7 @@ fn test_vmstate_macro_array_of_pointer_wrapped() { // * VMSTATE_FOOD: // - VMSTATE_VALIDATE -// Add more member fields when vmstate_of/vmstate_struct support "test" -// parameter. +// Add more member fields when vmstate_of support "test" parameter. struct FooD; impl FooD { @@ -450,21 +486,21 @@ fn validate_food_2(_state: &FooD, _version_id: u8) -> bool { true } -static VMSTATE_FOOD: VMStateDescription = VMStateDescription { - name: c"foo_d".as_ptr(), - version_id: 3, - minimum_version_id: 1, - fields: vmstate_fields! { +static VMSTATE_FOOD: VMStateDescription<FooD> = VMStateDescriptionBuilder::<FooD>::new() + .name(c"foo_d") + .version_id(3) + .minimum_version_id(1) + .fields(vmstate_fields! { vmstate_validate!(FooD, c"foo_d_0", FooD::validate_food_0), vmstate_validate!(FooD, c"foo_d_1", FooD::validate_food_1), vmstate_validate!(FooD, c"foo_d_2", validate_food_2), - }, - ..Zeroable::ZERO -}; + }) + .build(); #[test] fn test_vmstate_validate() { - let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOD.fields, 4) }; + let foo_fields: &[VMStateField] = + unsafe { slice::from_raw_parts(VMSTATE_FOOD.as_ref().fields, 4) }; let mut foo_d = FooD; let foo_d_p = std::ptr::addr_of_mut!(foo_d).cast::<c_void>(); diff --git a/rust/qemu-api/Cargo.toml b/rust/util/Cargo.toml index c07a17a28b..1f6767ed9d 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/util/Cargo.toml @@ -1,12 +1,11 @@ [package] -name = "qemu_api" +name = "util" version = "0.1.0" -authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"] -description = "Rust bindings for QEMU" -readme = "README.md" +description = "Rust bindings for QEMU/util" resolver = "2" publish = false +authors.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true @@ -14,15 +13,10 @@ repository.workspace = true rust-version.workspace = true [dependencies] -qemu_api_macros = { path = "../qemu-api-macros" } anyhow = { workspace = true } foreign = { workspace = true } libc = { workspace = true } - -[features] -default = ["debug_cell"] -allocator = [] -debug_cell = [] +common = { path = "../common" } [lints] workspace = true diff --git a/rust/qemu-api/build.rs b/rust/util/build.rs index 29d0945625..5654d1d562 100644 --- a/rust/qemu-api/build.rs +++ b/rust/util/build.rs @@ -9,12 +9,14 @@ use std::os::windows::fs::symlink_file; use std::{env, fs::remove_file, io::Result, path::Path}; fn main() -> Result<()> { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); let file = if let Ok(root) = env::var("MESON_BUILD_ROOT") { - format!("{root}/rust/qemu-api/bindings.inc.rs") + let sub = get_rust_subdir(manifest_dir).unwrap(); + format!("{root}/{sub}/bindings.inc.rs") } else { // Placing bindings.inc.rs in the source directory is supported // but not documented or encouraged. - format!("{}/src/bindings.inc.rs", env!("CARGO_MANIFEST_DIR")) + format!("{manifest_dir}/src/bindings.inc.rs") }; let file = Path::new(&file); @@ -41,3 +43,7 @@ fn main() -> Result<()> { println!("cargo:rerun-if-changed=build.rs"); Ok(()) } + +fn get_rust_subdir(path: &str) -> Option<&str> { + path.find("/rust").map(|index| &path[index + 1..]) +} diff --git a/rust/util/meson.build b/rust/util/meson.build new file mode 100644 index 0000000000..87a893673d --- /dev/null +++ b/rust/util/meson.build @@ -0,0 +1,55 @@ +_util_bindgen_args = [] +c_enums = [ + 'module_init_type', + 'QEMUClockType', +] +foreach enum : c_enums + _util_bindgen_args += ['--rustified-enum', enum] +endforeach + +# +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_util_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _util_bindgen_args, +) + +_util_rs = static_library( + 'util', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/error.rs', + 'src/log.rs', + 'src/module.rs', + 'src/timer.rs', + ], + {'.': _util_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + dependencies: [anyhow_rs, libc_rs, foreign_rs, common_rs, qom, qemuutil], +) + +util_rs = declare_dependency(link_with: [_util_rs], dependencies: [qemuutil, qom]) + +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-util-rs-doctests', + _util_rs, + protocol: 'rust', + dependencies: util_rs, + suite: ['doc', 'rust'] +) diff --git a/rust/util/src/bindings.rs b/rust/util/src/bindings.rs new file mode 100644 index 0000000000..9ffff12cde --- /dev/null +++ b/rust/util/src/bindings.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); diff --git a/rust/qemu-api/src/error.rs b/rust/util/src/error.rs index e114fc4178..bfa5a8685b 100644 --- a/rust/qemu-api/src/error.rs +++ b/rust/util/src/error.rs @@ -19,7 +19,7 @@ //! //! This module is most commonly used at the boundary between C and Rust code; //! other code will usually access it through the -//! [`qemu_api::Result`](crate::Result) type alias, and will use the +//! [`utils::Result`](crate::Result) type alias, and will use the //! [`std::error::Error`] interface to let C errors participate in Rust's error //! handling functionality. //! @@ -30,7 +30,7 @@ //! type up to C code, or from a combination of the two. //! //! The third case, corresponding to [`Error::with_error`], is the only one that -//! requires mentioning [`qemu_api::Error`](crate::Error) explicitly. Similar +//! requires mentioning [`utils::Error`](crate::Error) explicitly. Similar //! to how QEMU's C code handles errno values, the string and the //! `anyhow::Error` object will be concatenated with `:` as the separator. @@ -316,10 +316,10 @@ mod tests { use std::ffi::CStr; use anyhow::anyhow; + use common::assert_match; use foreign::OwnedPointer; use super::*; - use crate::{assert_match, bindings}; #[track_caller] fn error_for_test(msg: &CStr) -> OwnedPointer<Error> { diff --git a/rust/util/src/lib.rs b/rust/util/src/lib.rs new file mode 100644 index 0000000000..16c89b9517 --- /dev/null +++ b/rust/util/src/lib.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub mod bindings; +pub mod error; +pub mod log; +pub mod module; +pub mod timer; + +pub use error::{Error, Result}; diff --git a/rust/qemu-api/src/log.rs b/rust/util/src/log.rs index a441b8c1f2..af9a3e9123 100644 --- a/rust/qemu-api/src/log.rs +++ b/rust/util/src/log.rs @@ -8,7 +8,9 @@ use std::{ ptr::NonNull, }; -use crate::{bindings, errno}; +use common::errno; + +use crate::bindings; #[repr(u32)] /// Represents specific error categories within QEMU's logging system. @@ -47,7 +49,7 @@ impl LogGuard { /// # Examples /// /// ``` - /// # use qemu_api::log::LogGuard; + /// # use util::log::LogGuard; /// # use std::io::Write; /// if let Some(mut log) = LogGuard::new() { /// writeln!(log, "test"); @@ -114,7 +116,7 @@ impl Drop for LogGuard { /// # Example /// /// ``` -/// use qemu_api::{log::Log, log_mask_ln}; +/// use util::{log::Log, log_mask_ln}; /// /// let error_address = 0xbad; /// log_mask_ln!(Log::GuestError, "Address 0x{error_address:x} out of range"); @@ -124,7 +126,7 @@ impl Drop for LogGuard { /// trailing `,`: /// /// ``` -/// use qemu_api::{log::Log, log_mask_ln}; +/// use util::{log::Log, log_mask_ln}; /// /// let error_address = 0xbad; /// log_mask_ln!( @@ -137,12 +139,12 @@ impl Drop for LogGuard { macro_rules! log_mask_ln { ($mask:expr, $fmt:tt $($args:tt)*) => {{ // Type assertion to enforce type `Log` for $mask - let _: Log = $mask; + let _: $crate::log::Log = $mask; if unsafe { - (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 + ($crate::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 } { - _ = ::qemu_api::log::LogGuard::log_fmt( + _ = $crate::log::LogGuard::log_fmt( format_args!("{}\n", format_args!($fmt $($args)*))); } }}; diff --git a/rust/qemu-api/src/module.rs b/rust/util/src/module.rs index fa5cea3598..06c45fc142 100644 --- a/rust/qemu-api/src/module.rs +++ b/rust/util/src/module.rs @@ -36,7 +36,7 @@ macro_rules! module_init { // shortcut because it's quite common that $body needs unsafe {} ($type:ident => unsafe $body:block) => { - $crate::module_init! { + ::util::module_init! { $type => { unsafe { $body } } } }; diff --git a/rust/qemu-api/src/timer.rs b/rust/util/src/timer.rs index 0a2d111d49..c6b3e4088e 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/util/src/timer.rs @@ -7,22 +7,22 @@ use std::{ pin::Pin, }; -use crate::{ - bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType}, - callbacks::FnCall, - cell::Opaque, +use common::{callbacks::FnCall, Opaque}; + +use crate::bindings::{ + self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType, }; /// A safe wrapper around [`bindings::QEMUTimer`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, common::Wrapper)] pub struct Timer(Opaque<bindings::QEMUTimer>); unsafe impl Send for Timer {} unsafe impl Sync for Timer {} #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(common::Wrapper)] pub struct TimerListGroup(Opaque<bindings::QEMUTimerListGroup>); unsafe impl Send for TimerListGroup {} @@ -39,7 +39,7 @@ impl Timer { /// /// The timer must be initialized before it is armed with /// [`modify`](Self::modify). - pub unsafe fn new() -> Self { + pub const unsafe fn new() -> Self { // SAFETY: requirements relayed to callers of Timer::new Self(unsafe { Opaque::zeroed() }) } @@ -56,7 +56,7 @@ impl Timer { ) where F: for<'a> FnCall<(&'a T,)>, { - let _: () = F::ASSERT_IS_SOME; + const { assert!(F::IS_SOME) }; /// timer expiration callback unsafe extern "C" fn rust_timer_handler<T, F: for<'a> FnCall<(&'a T,)>>( diff --git a/rust/util/wrapper.h b/rust/util/wrapper.h new file mode 100644 index 0000000000..b9ed68a01d --- /dev/null +++ b/rust/util/wrapper.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "qapi/error.h" +#include "qapi/error-internal.h" +#include "qemu/log-for-trace.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" diff --git a/system/cpu-timers.c b/system/cpu-timers.c index cb35fa62b8..9919b46230 100644 --- a/system/cpu-timers.c +++ b/system/cpu-timers.c @@ -246,14 +246,14 @@ void qemu_timer_notify_cb(void *opaque, QEMUClockType type) if (qemu_in_vcpu_thread()) { /* - * A CPU is currently running; kick it back out to the + * A CPU is currently running; send it out of the * tcg_cpu_exec() loop so it will recalculate its * icount deadline immediately. */ - qemu_cpu_kick(current_cpu); + cpu_exit(current_cpu); } else if (first_cpu) { /* - * qemu_cpu_kick is not enough to kick a halted CPU out of + * cpu_exit() is not enough to kick a halted CPU out of * qemu_tcg_wait_io_event. async_run_on_cpu, instead, * causes cpu_thread_is_idle to return false. This way, * handle_icount_deadline can run. diff --git a/system/cpus.c b/system/cpus.c index 437848b5eb..aa7bfcf56e 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -257,8 +257,7 @@ int64_t cpus_get_elapsed_ticks(void) void cpu_set_interrupt(CPUState *cpu, int mask) { /* Pairs with cpu_test_interrupt(). */ - qatomic_store_release(&cpu->interrupt_request, - cpu->interrupt_request | mask); + qatomic_or(&cpu->interrupt_request, mask); } void generic_handle_interrupt(CPUState *cpu, int mask) @@ -451,7 +450,7 @@ static void qemu_cpu_stop(CPUState *cpu, bool exit) qemu_cond_broadcast(&qemu_pause_cond); } -void qemu_wait_io_event_common(CPUState *cpu) +void qemu_process_cpu_events_common(CPUState *cpu) { qatomic_set_mb(&cpu->thread_kicked, false); if (cpu->stop) { @@ -460,10 +459,11 @@ void qemu_wait_io_event_common(CPUState *cpu) process_queued_cpu_work(cpu); } -void qemu_wait_io_event(CPUState *cpu) +void qemu_process_cpu_events(CPUState *cpu) { bool slept = false; + qatomic_set(&cpu->exit_request, false); while (cpu_thread_is_idle(cpu)) { if (!slept) { slept = true; @@ -475,7 +475,7 @@ void qemu_wait_io_event(CPUState *cpu) qemu_plugin_vcpu_resume_cb(cpu); } - qemu_wait_io_event_common(cpu); + qemu_process_cpu_events_common(cpu); } void cpus_kick_thread(CPUState *cpu) @@ -605,7 +605,7 @@ void cpu_pause(CPUState *cpu) qemu_cpu_stop(cpu, true); } else { cpu->stop = true; - qemu_cpu_kick(cpu); + cpu_exit(cpu); } } @@ -645,6 +645,7 @@ void pause_all_vcpus(void) while (!all_vcpus_paused()) { qemu_cond_wait(&qemu_pause_cond, &bql); + /* FIXME: is this needed? */ CPU_FOREACH(cpu) { qemu_cpu_kick(cpu); } @@ -673,7 +674,7 @@ void cpu_remove_sync(CPUState *cpu) { cpu->stop = true; cpu->unplug = true; - qemu_cpu_kick(cpu); + cpu_exit(cpu); bql_unlock(); qemu_thread_join(cpu->thread); bql_lock(); diff --git a/system/physmem.c b/system/physmem.c index ddd58e9eb8..ae8ecd50ea 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -793,9 +793,6 @@ void cpu_address_space_init(CPUState *cpu, int asidx, cpu->as = as; } - /* KVM cannot currently support multiple address spaces. */ - assert(asidx == 0 || !kvm_enabled()); - if (!cpu->cpu_ases) { cpu->cpu_ases = g_new0(CPUAddressSpace, cpu->num_ases); cpu->cpu_ases_count = cpu->num_ases; @@ -818,8 +815,6 @@ void cpu_address_space_destroy(CPUState *cpu, int asidx) assert(cpu->cpu_ases); assert(asidx >= 0 && asidx < cpu->num_ases); - /* KVM cannot currently support multiple address spaces. */ - assert(asidx == 0 || !kvm_enabled()); cpuas = &cpu->cpu_ases[asidx]; if (tcg_enabled()) { diff --git a/target/arm/cpu-irq.c b/target/arm/cpu-irq.c new file mode 100644 index 0000000000..fe514cc93a --- /dev/null +++ b/target/arm/cpu-irq.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * QEMU ARM CPU - interrupt_request handling + * + * Copyright (c) 2003-2025 QEMU contributors + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "accel/tcg/cpu-ops.h" +#include "internals.h" + +#ifdef CONFIG_TCG +static inline bool arm_excp_unmasked(CPUState *cs, unsigned int excp_idx, + unsigned int target_el, + unsigned int cur_el, bool secure, + uint64_t hcr_el2) +{ + CPUARMState *env = cpu_env(cs); + bool pstate_unmasked; + bool unmasked = false; + bool allIntMask = false; + + /* + * Don't take exceptions if they target a lower EL. + * This check should catch any exceptions that would not be taken + * but left pending. + */ + if (cur_el > target_el) { + return false; + } + + if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && + env->cp15.sctlr_el[target_el] & SCTLR_NMI && cur_el == target_el) { + allIntMask = env->pstate & PSTATE_ALLINT || + ((env->cp15.sctlr_el[target_el] & SCTLR_SPINTMASK) && + (env->pstate & PSTATE_SP)); + } + + switch (excp_idx) { + case EXCP_NMI: + pstate_unmasked = !allIntMask; + break; + + case EXCP_VINMI: + if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { + /* VINMIs are only taken when hypervized. */ + return false; + } + return !allIntMask; + case EXCP_VFNMI: + if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { + /* VFNMIs are only taken when hypervized. */ + return false; + } + return !allIntMask; + case EXCP_FIQ: + pstate_unmasked = (!(env->daif & PSTATE_F)) && (!allIntMask); + break; + + case EXCP_IRQ: + pstate_unmasked = (!(env->daif & PSTATE_I)) && (!allIntMask); + break; + + case EXCP_VFIQ: + if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { + /* VFIQs are only taken when hypervized. */ + return false; + } + return !(env->daif & PSTATE_F) && (!allIntMask); + case EXCP_VIRQ: + if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { + /* VIRQs are only taken when hypervized. */ + return false; + } + return !(env->daif & PSTATE_I) && (!allIntMask); + case EXCP_VSERR: + if (!(hcr_el2 & HCR_AMO) || (hcr_el2 & HCR_TGE)) { + /* VIRQs are only taken when hypervized. */ + return false; + } + return !(env->daif & PSTATE_A); + default: + g_assert_not_reached(); + } + + /* + * Use the target EL, current execution state and SCR/HCR settings to + * determine whether the corresponding CPSR bit is used to mask the + * interrupt. + */ + if ((target_el > cur_el) && (target_el != 1)) { + /* Exceptions targeting a higher EL may not be maskable */ + if (arm_feature(env, ARM_FEATURE_AARCH64)) { + switch (target_el) { + case 2: + /* + * According to ARM DDI 0487H.a, an interrupt can be masked + * when HCR_E2H and HCR_TGE are both set regardless of the + * current Security state. Note that we need to revisit this + * part again once we need to support NMI. + */ + if ((hcr_el2 & (HCR_E2H | HCR_TGE)) != (HCR_E2H | HCR_TGE)) { + unmasked = true; + } + break; + case 3: + /* Interrupt cannot be masked when the target EL is 3 */ + unmasked = true; + break; + default: + g_assert_not_reached(); + } + } else { + /* + * The old 32-bit-only environment has a more complicated + * masking setup. HCR and SCR bits not only affect interrupt + * routing but also change the behaviour of masking. + */ + bool hcr, scr; + + switch (excp_idx) { + case EXCP_FIQ: + /* + * If FIQs are routed to EL3 or EL2 then there are cases where + * we override the CPSR.F in determining if the exception is + * masked or not. If neither of these are set then we fall back + * to the CPSR.F setting otherwise we further assess the state + * below. + */ + hcr = hcr_el2 & HCR_FMO; + scr = (env->cp15.scr_el3 & SCR_FIQ); + + /* + * When EL3 is 32-bit, the SCR.FW bit controls whether the + * CPSR.F bit masks FIQ interrupts when taken in non-secure + * state. If SCR.FW is set then FIQs can be masked by CPSR.F + * when non-secure but only when FIQs are only routed to EL3. + */ + scr = scr && !((env->cp15.scr_el3 & SCR_FW) && !hcr); + break; + case EXCP_IRQ: + /* + * When EL3 execution state is 32-bit, if HCR.IMO is set then + * we may override the CPSR.I masking when in non-secure state. + * The SCR.IRQ setting has already been taken into consideration + * when setting the target EL, so it does not have a further + * affect here. + */ + hcr = hcr_el2 & HCR_IMO; + scr = false; + break; + default: + g_assert_not_reached(); + } + + if ((scr || hcr) && !secure) { + unmasked = true; + } + } + } + + /* + * The PSTATE bits only mask the interrupt if we have not overridden the + * ability above. + */ + return unmasked || pstate_unmasked; +} + +bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) +{ + CPUARMState *env = cpu_env(cs); + uint32_t cur_el = arm_current_el(env); + bool secure = arm_is_secure(env); + uint64_t hcr_el2 = arm_hcr_el2_eff(env); + uint32_t target_el; + uint32_t excp_idx; + + /* The prioritization of interrupts is IMPLEMENTATION DEFINED. */ + + if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && + (arm_sctlr(env, cur_el) & SCTLR_NMI)) { + if (interrupt_request & CPU_INTERRUPT_NMI) { + excp_idx = EXCP_NMI; + target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_VINMI) { + excp_idx = EXCP_VINMI; + target_el = 1; + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_VFNMI) { + excp_idx = EXCP_VFNMI; + target_el = 1; + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + } else { + /* + * NMI disabled: interrupts with superpriority are handled + * as if they didn't have it + */ + if (interrupt_request & CPU_INTERRUPT_NMI) { + interrupt_request |= CPU_INTERRUPT_HARD; + } + if (interrupt_request & CPU_INTERRUPT_VINMI) { + interrupt_request |= CPU_INTERRUPT_VIRQ; + } + if (interrupt_request & CPU_INTERRUPT_VFNMI) { + interrupt_request |= CPU_INTERRUPT_VFIQ; + } + } + + if (interrupt_request & CPU_INTERRUPT_FIQ) { + excp_idx = EXCP_FIQ; + target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_HARD) { + excp_idx = EXCP_IRQ; + target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_VIRQ) { + excp_idx = EXCP_VIRQ; + target_el = 1; + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_VFIQ) { + excp_idx = EXCP_VFIQ; + target_el = 1; + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + goto found; + } + } + if (interrupt_request & CPU_INTERRUPT_VSERR) { + excp_idx = EXCP_VSERR; + target_el = 1; + if (arm_excp_unmasked(cs, excp_idx, target_el, + cur_el, secure, hcr_el2)) { + /* Taking a virtual abort clears HCR_EL2.VSE */ + env->cp15.hcr_el2 &= ~HCR_VSE; + cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); + goto found; + } + } + return false; + + found: + cs->exception_index = excp_idx; + env->exception.target_el = target_el; + cs->cc->tcg_ops->do_interrupt(cs); + return true; +} +#endif /* CONFIG_TCG */ + +void arm_cpu_update_virq(ARMCPU *cpu) +{ + /* + * Update the interrupt level for VIRQ, which is the logical OR of + * the HCR_EL2.VI bit and the input line level from the GIC. + */ + CPUARMState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && + !(arm_hcrx_el2_eff(env) & HCRX_VINMI)) || + (env->irq_line_state & CPU_INTERRUPT_VIRQ); + + if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VIRQ)) { + if (new_state) { + cpu_interrupt(cs, CPU_INTERRUPT_VIRQ); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_VIRQ); + } + } +} + +void arm_cpu_update_vfiq(ARMCPU *cpu) +{ + /* + * Update the interrupt level for VFIQ, which is the logical OR of + * the HCR_EL2.VF bit and the input line level from the GIC. + */ + CPUARMState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + bool new_state = ((arm_hcr_el2_eff(env) & HCR_VF) && + !(arm_hcrx_el2_eff(env) & HCRX_VFNMI)) || + (env->irq_line_state & CPU_INTERRUPT_VFIQ); + + if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFIQ)) { + if (new_state) { + cpu_interrupt(cs, CPU_INTERRUPT_VFIQ); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_VFIQ); + } + } +} + +void arm_cpu_update_vinmi(ARMCPU *cpu) +{ + /* + * Update the interrupt level for VINMI, which is the logical OR of + * the HCRX_EL2.VINMI bit and the input line level from the GIC. + */ + CPUARMState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && + (arm_hcrx_el2_eff(env) & HCRX_VINMI)) || + (env->irq_line_state & CPU_INTERRUPT_VINMI); + + if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VINMI)) { + if (new_state) { + cpu_interrupt(cs, CPU_INTERRUPT_VINMI); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_VINMI); + } + } +} + +void arm_cpu_update_vfnmi(ARMCPU *cpu) +{ + /* + * Update the interrupt level for VFNMI, which is the HCRX_EL2.VFNMI bit. + */ + CPUARMState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + bool new_state = (arm_hcr_el2_eff(env) & HCR_VF) && + (arm_hcrx_el2_eff(env) & HCRX_VFNMI); + + if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFNMI)) { + if (new_state) { + cpu_interrupt(cs, CPU_INTERRUPT_VFNMI); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_VFNMI); + } + } +} + +void arm_cpu_update_vserr(ARMCPU *cpu) +{ + /* + * Update the interrupt level for VSERR, which is the HCR_EL2.VSE bit. + */ + CPUARMState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + bool new_state = env->cp15.hcr_el2 & HCR_VSE; + + if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VSERR)) { + if (new_state) { + cpu_interrupt(cs, CPU_INTERRUPT_VSERR); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); + } + } +} + diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 02e2a31a86..c65af7e761 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -677,376 +677,6 @@ void arm_emulate_firmware_reset(CPUState *cpustate, int target_el) } -#if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) - -static inline bool arm_excp_unmasked(CPUState *cs, unsigned int excp_idx, - unsigned int target_el, - unsigned int cur_el, bool secure, - uint64_t hcr_el2) -{ - CPUARMState *env = cpu_env(cs); - bool pstate_unmasked; - bool unmasked = false; - bool allIntMask = false; - - /* - * Don't take exceptions if they target a lower EL. - * This check should catch any exceptions that would not be taken - * but left pending. - */ - if (cur_el > target_el) { - return false; - } - - if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && - env->cp15.sctlr_el[target_el] & SCTLR_NMI && cur_el == target_el) { - allIntMask = env->pstate & PSTATE_ALLINT || - ((env->cp15.sctlr_el[target_el] & SCTLR_SPINTMASK) && - (env->pstate & PSTATE_SP)); - } - - switch (excp_idx) { - case EXCP_NMI: - pstate_unmasked = !allIntMask; - break; - - case EXCP_VINMI: - if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { - /* VINMIs are only taken when hypervized. */ - return false; - } - return !allIntMask; - case EXCP_VFNMI: - if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { - /* VFNMIs are only taken when hypervized. */ - return false; - } - return !allIntMask; - case EXCP_FIQ: - pstate_unmasked = (!(env->daif & PSTATE_F)) && (!allIntMask); - break; - - case EXCP_IRQ: - pstate_unmasked = (!(env->daif & PSTATE_I)) && (!allIntMask); - break; - - case EXCP_VFIQ: - if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { - /* VFIQs are only taken when hypervized. */ - return false; - } - return !(env->daif & PSTATE_F) && (!allIntMask); - case EXCP_VIRQ: - if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { - /* VIRQs are only taken when hypervized. */ - return false; - } - return !(env->daif & PSTATE_I) && (!allIntMask); - case EXCP_VSERR: - if (!(hcr_el2 & HCR_AMO) || (hcr_el2 & HCR_TGE)) { - /* VIRQs are only taken when hypervized. */ - return false; - } - return !(env->daif & PSTATE_A); - default: - g_assert_not_reached(); - } - - /* - * Use the target EL, current execution state and SCR/HCR settings to - * determine whether the corresponding CPSR bit is used to mask the - * interrupt. - */ - if ((target_el > cur_el) && (target_el != 1)) { - /* Exceptions targeting a higher EL may not be maskable */ - if (arm_feature(env, ARM_FEATURE_AARCH64)) { - switch (target_el) { - case 2: - /* - * According to ARM DDI 0487H.a, an interrupt can be masked - * when HCR_E2H and HCR_TGE are both set regardless of the - * current Security state. Note that we need to revisit this - * part again once we need to support NMI. - */ - if ((hcr_el2 & (HCR_E2H | HCR_TGE)) != (HCR_E2H | HCR_TGE)) { - unmasked = true; - } - break; - case 3: - /* Interrupt cannot be masked when the target EL is 3 */ - unmasked = true; - break; - default: - g_assert_not_reached(); - } - } else { - /* - * The old 32-bit-only environment has a more complicated - * masking setup. HCR and SCR bits not only affect interrupt - * routing but also change the behaviour of masking. - */ - bool hcr, scr; - - switch (excp_idx) { - case EXCP_FIQ: - /* - * If FIQs are routed to EL3 or EL2 then there are cases where - * we override the CPSR.F in determining if the exception is - * masked or not. If neither of these are set then we fall back - * to the CPSR.F setting otherwise we further assess the state - * below. - */ - hcr = hcr_el2 & HCR_FMO; - scr = (env->cp15.scr_el3 & SCR_FIQ); - - /* - * When EL3 is 32-bit, the SCR.FW bit controls whether the - * CPSR.F bit masks FIQ interrupts when taken in non-secure - * state. If SCR.FW is set then FIQs can be masked by CPSR.F - * when non-secure but only when FIQs are only routed to EL3. - */ - scr = scr && !((env->cp15.scr_el3 & SCR_FW) && !hcr); - break; - case EXCP_IRQ: - /* - * When EL3 execution state is 32-bit, if HCR.IMO is set then - * we may override the CPSR.I masking when in non-secure state. - * The SCR.IRQ setting has already been taken into consideration - * when setting the target EL, so it does not have a further - * affect here. - */ - hcr = hcr_el2 & HCR_IMO; - scr = false; - break; - default: - g_assert_not_reached(); - } - - if ((scr || hcr) && !secure) { - unmasked = true; - } - } - } - - /* - * The PSTATE bits only mask the interrupt if we have not overridden the - * ability above. - */ - return unmasked || pstate_unmasked; -} - -static bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) -{ - CPUARMState *env = cpu_env(cs); - uint32_t cur_el = arm_current_el(env); - bool secure = arm_is_secure(env); - uint64_t hcr_el2 = arm_hcr_el2_eff(env); - uint32_t target_el; - uint32_t excp_idx; - - /* The prioritization of interrupts is IMPLEMENTATION DEFINED. */ - - if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && - (arm_sctlr(env, cur_el) & SCTLR_NMI)) { - if (interrupt_request & CPU_INTERRUPT_NMI) { - excp_idx = EXCP_NMI; - target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_VINMI) { - excp_idx = EXCP_VINMI; - target_el = 1; - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_VFNMI) { - excp_idx = EXCP_VFNMI; - target_el = 1; - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - } else { - /* - * NMI disabled: interrupts with superpriority are handled - * as if they didn't have it - */ - if (interrupt_request & CPU_INTERRUPT_NMI) { - interrupt_request |= CPU_INTERRUPT_HARD; - } - if (interrupt_request & CPU_INTERRUPT_VINMI) { - interrupt_request |= CPU_INTERRUPT_VIRQ; - } - if (interrupt_request & CPU_INTERRUPT_VFNMI) { - interrupt_request |= CPU_INTERRUPT_VFIQ; - } - } - - if (interrupt_request & CPU_INTERRUPT_FIQ) { - excp_idx = EXCP_FIQ; - target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_HARD) { - excp_idx = EXCP_IRQ; - target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_VIRQ) { - excp_idx = EXCP_VIRQ; - target_el = 1; - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_VFIQ) { - excp_idx = EXCP_VFIQ; - target_el = 1; - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - goto found; - } - } - if (interrupt_request & CPU_INTERRUPT_VSERR) { - excp_idx = EXCP_VSERR; - target_el = 1; - if (arm_excp_unmasked(cs, excp_idx, target_el, - cur_el, secure, hcr_el2)) { - /* Taking a virtual abort clears HCR_EL2.VSE */ - env->cp15.hcr_el2 &= ~HCR_VSE; - cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); - goto found; - } - } - return false; - - found: - cs->exception_index = excp_idx; - env->exception.target_el = target_el; - cs->cc->tcg_ops->do_interrupt(cs); - return true; -} - -#endif /* CONFIG_TCG && !CONFIG_USER_ONLY */ - -void arm_cpu_update_virq(ARMCPU *cpu) -{ - /* - * Update the interrupt level for VIRQ, which is the logical OR of - * the HCR_EL2.VI bit and the input line level from the GIC. - */ - CPUARMState *env = &cpu->env; - CPUState *cs = CPU(cpu); - - bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && - !(arm_hcrx_el2_eff(env) & HCRX_VINMI)) || - (env->irq_line_state & CPU_INTERRUPT_VIRQ); - - if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VIRQ)) { - if (new_state) { - cpu_interrupt(cs, CPU_INTERRUPT_VIRQ); - } else { - cpu_reset_interrupt(cs, CPU_INTERRUPT_VIRQ); - } - } -} - -void arm_cpu_update_vfiq(ARMCPU *cpu) -{ - /* - * Update the interrupt level for VFIQ, which is the logical OR of - * the HCR_EL2.VF bit and the input line level from the GIC. - */ - CPUARMState *env = &cpu->env; - CPUState *cs = CPU(cpu); - - bool new_state = ((arm_hcr_el2_eff(env) & HCR_VF) && - !(arm_hcrx_el2_eff(env) & HCRX_VFNMI)) || - (env->irq_line_state & CPU_INTERRUPT_VFIQ); - - if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFIQ)) { - if (new_state) { - cpu_interrupt(cs, CPU_INTERRUPT_VFIQ); - } else { - cpu_reset_interrupt(cs, CPU_INTERRUPT_VFIQ); - } - } -} - -void arm_cpu_update_vinmi(ARMCPU *cpu) -{ - /* - * Update the interrupt level for VINMI, which is the logical OR of - * the HCRX_EL2.VINMI bit and the input line level from the GIC. - */ - CPUARMState *env = &cpu->env; - CPUState *cs = CPU(cpu); - - bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && - (arm_hcrx_el2_eff(env) & HCRX_VINMI)) || - (env->irq_line_state & CPU_INTERRUPT_VINMI); - - if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VINMI)) { - if (new_state) { - cpu_interrupt(cs, CPU_INTERRUPT_VINMI); - } else { - cpu_reset_interrupt(cs, CPU_INTERRUPT_VINMI); - } - } -} - -void arm_cpu_update_vfnmi(ARMCPU *cpu) -{ - /* - * Update the interrupt level for VFNMI, which is the HCRX_EL2.VFNMI bit. - */ - CPUARMState *env = &cpu->env; - CPUState *cs = CPU(cpu); - - bool new_state = (arm_hcr_el2_eff(env) & HCR_VF) && - (arm_hcrx_el2_eff(env) & HCRX_VFNMI); - - if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFNMI)) { - if (new_state) { - cpu_interrupt(cs, CPU_INTERRUPT_VFNMI); - } else { - cpu_reset_interrupt(cs, CPU_INTERRUPT_VFNMI); - } - } -} - -void arm_cpu_update_vserr(ARMCPU *cpu) -{ - /* - * Update the interrupt level for VSERR, which is the HCR_EL2.VSE bit. - */ - CPUARMState *env = &cpu->env; - CPUState *cs = CPU(cpu); - - bool new_state = env->cp15.hcr_el2 & HCR_VSE; - - if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VSERR)) { - if (new_state) { - cpu_interrupt(cs, CPU_INTERRUPT_VSERR); - } else { - cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); - } - } -} - #ifndef CONFIG_USER_ONLY static void arm_cpu_set_irq(void *opaque, int irq, int level) { diff --git a/target/arm/el2-stubs.c b/target/arm/el2-stubs.c new file mode 100644 index 0000000000..972023c337 --- /dev/null +++ b/target/arm/el2-stubs.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* QEMU ARM CPU - user-mode emulation stubs for EL2 interrupts + * + * These should not really be needed, but CP registers for EL2 + * are not elided by user-mode emulation and they call these + * functions. Leave them as stubs until it's cleaned up. + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "internals.h" + +void arm_cpu_update_virq(ARMCPU *cpu) +{ + g_assert_not_reached(); +} + +void arm_cpu_update_vfiq(ARMCPU *cpu) +{ + g_assert_not_reached(); +} + +void arm_cpu_update_vinmi(ARMCPU *cpu) +{ + g_assert_not_reached(); +} + +void arm_cpu_update_vfnmi(ARMCPU *cpu) +{ + g_assert_not_reached(); +} + +void arm_cpu_update_vserr(ARMCPU *cpu) +{ + g_assert_not_reached(); +} diff --git a/target/arm/helper.c b/target/arm/helper.c index fa8dfac299..c44294711f 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2868,8 +2868,12 @@ static void omap_threadid_write(CPUARMState *env, const ARMCPRegInfo *ri, static void omap_wfi_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { +#ifdef CONFIG_USER_ONLY + g_assert_not_reached(); +#else /* Wait-for-interrupt (deprecated) */ cpu_interrupt(env_cpu(env), CPU_INTERRUPT_HALT); +#endif } static void omap_cachemaint_write(CPUARMState *env, const ARMCPRegInfo *ri, diff --git a/target/arm/internals.h b/target/arm/internals.h index 532fabcafc..0f7df97b99 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1293,6 +1293,11 @@ static inline const char *aarch32_mode_name(uint32_t psr) } /** + * arm_cpu_exec_interrupt(): Implementation of the cpu_exec_inrerrupt hook. + */ +bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request); + +/** * arm_cpu_update_virq: Update CPU_INTERRUPT_VIRQ bit in cs->interrupt_request * * Update the CPU_INTERRUPT_VIRQ bit in cs->interrupt_request, following diff --git a/target/arm/meson.build b/target/arm/meson.build index 07d9271aa4..914f1498fc 100644 --- a/target/arm/meson.build +++ b/target/arm/meson.build @@ -26,6 +26,7 @@ arm_user_ss.add(files( 'debug_helper.c', 'helper.c', 'vfp_fpscr.c', + 'el2-stubs.c', )) arm_common_system_ss.add(files('cpu.c')) @@ -38,6 +39,7 @@ arm_common_system_ss.add(files( 'arm-powerctl.c', 'cortex-regs.c', 'cpregs-pmu.c', + 'cpu-irq.c', 'debug_helper.c', 'helper.c', 'machine.c', diff --git a/target/arm/tcg/mte_helper.c b/target/arm/tcg/mte_helper.c index 0efc18a181..302e899287 100644 --- a/target/arm/tcg/mte_helper.c +++ b/target/arm/tcg/mte_helper.c @@ -591,7 +591,7 @@ static void mte_async_check_fail(CPUARMState *env, uint64_t dirty_ptr, * which is rather sooner than "normal". But the alternative * is waiting until the next syscall. */ - qemu_cpu_kick(env_cpu(env)); + cpu_exit(env_cpu(env)); #endif } diff --git a/target/avr/helper.c b/target/avr/helper.c index b9cd6d5ef2..4b29ab3526 100644 --- a/target/avr/helper.c +++ b/target/avr/helper.c @@ -47,7 +47,7 @@ bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request) cs->exception_index = EXCP_RESET; avr_cpu_do_interrupt(cs); - cs->interrupt_request &= ~CPU_INTERRUPT_RESET; + cpu_reset_interrupt(cs, CPU_INTERRUPT_RESET); return true; } } @@ -59,7 +59,7 @@ bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request) env->intsrc &= env->intsrc - 1; /* clear the interrupt */ if (!env->intsrc) { - cs->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); } return true; } diff --git a/target/i386/cpu.h b/target/i386/cpu.h index f977fc49a7..e0be7a7406 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2574,6 +2574,11 @@ static inline bool x86_has_cpuid_0x1f(X86CPU *cpu) void x86_cpu_set_a20(X86CPU *cpu, int a20_state); void cpu_sync_avx_hflag(CPUX86State *env); +typedef enum X86ASIdx { + X86ASIdx_MEM = 0, + X86ASIdx_SMM = 1, +} X86ASIdx; + #ifndef CONFIG_USER_ONLY static inline int x86_asidx_from_attrs(CPUState *cs, MemTxAttrs attrs) { diff --git a/target/i386/helper.c b/target/i386/helper.c index e0aaed3c4c..651041ccfa 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -110,6 +110,7 @@ int cpu_x86_support_mca_broadcast(CPUX86State *env) /* x86 mmu */ /* XXX: add PGE support */ +#ifndef CONFIG_USER_ONLY void x86_cpu_set_a20(X86CPU *cpu, int a20_state) { CPUX86State *env = &cpu->env; @@ -129,6 +130,7 @@ void x86_cpu_set_a20(X86CPU *cpu, int a20_state) env->a20_mask = ~(1 << 20) | (a20_state << 20); } } +#endif void cpu_x86_update_cr0(CPUX86State *env, uint32_t new_cr0) { diff --git a/target/i386/hvf/x86hvf.c b/target/i386/hvf/x86hvf.c index 9e05e0e576..a502437c30 100644 --- a/target/i386/hvf/x86hvf.c +++ b/target/i386/hvf/x86hvf.c @@ -397,7 +397,7 @@ bool hvf_inject_interrupts(CPUState *cs) if (cpu_test_interrupt(cs, CPU_INTERRUPT_NMI)) { if (!(env->hflags2 & HF2_NMI_MASK) && !(info & VMCS_INTR_VALID)) { - cs->interrupt_request &= ~CPU_INTERRUPT_NMI; + cpu_reset_interrupt(cs, CPU_INTERRUPT_NMI); info = VMCS_INTR_VALID | VMCS_INTR_T_NMI | EXCP02_NMI; wvmcs(cs->accel->fd, VMCS_ENTRY_INTR_INFO, info); } else { @@ -409,7 +409,7 @@ bool hvf_inject_interrupts(CPUState *cs) cpu_test_interrupt(cs, CPU_INTERRUPT_HARD) && (env->eflags & IF_MASK) && !(info & VMCS_INTR_VALID)) { int line = cpu_get_pic_interrupt(env); - cs->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); if (line >= 0) { wvmcs(cs->accel->fd, VMCS_ENTRY_INTR_INFO, line | VMCS_INTR_VALID | VMCS_INTR_T_HWINTR); @@ -437,7 +437,7 @@ int hvf_process_events(CPUState *cs) } if (cpu_test_interrupt(cs, CPU_INTERRUPT_POLL)) { - cs->interrupt_request &= ~CPU_INTERRUPT_POLL; + cpu_reset_interrupt(cs, CPU_INTERRUPT_POLL); apic_poll_irq(cpu->apic_state); } if ((cpu_test_interrupt(cs, CPU_INTERRUPT_HARD) && @@ -450,7 +450,7 @@ int hvf_process_events(CPUState *cs) do_cpu_sipi(cpu); } if (cpu_test_interrupt(cs, CPU_INTERRUPT_TPR)) { - cs->interrupt_request &= ~CPU_INTERRUPT_TPR; + cpu_reset_interrupt(cs, CPU_INTERRUPT_TPR); cpu_synchronize_state(cs); apic_handle_tpr_access_report(cpu->apic_state, env->eip, env->tpr_access_type); diff --git a/target/i386/kvm/hyperv.c b/target/i386/kvm/hyperv.c index 9865120cc4..f7a81bd270 100644 --- a/target/i386/kvm/hyperv.c +++ b/target/i386/kvm/hyperv.c @@ -81,7 +81,6 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit) * necessary because memory hierarchy is being changed */ async_safe_run_on_cpu(CPU(cpu), async_synic_update, RUN_ON_CPU_NULL); - cpu_exit(CPU(cpu)); return EXCP_INTERRUPT; case KVM_EXIT_HYPERV_HCALL: { diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 89a7953659..9c25b55839 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -13,6 +13,7 @@ #include "qapi/error.h" #include "system/system.h" #include "hw/boards.h" +#include "hw/i386/x86.h" #include "kvm_i386.h" #include "accel/accel-cpu-target.h" @@ -91,6 +92,15 @@ static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) kvm_set_guest_phys_bits(cs); } + /* + * When SMM is enabled, there is 2 address spaces. Otherwise only 1. + * + * Only initialize address space 0 here, the second one for SMM is + * initialized at register_smram_listener() after machine init done. + */ + cs->num_ases = x86_machine_is_smm_enabled(X86_MACHINE(current_machine)) ? 2 : 1; + cpu_address_space_init(cs, X86ASIdx_MEM, "cpu-memory", cs->memory); + return true; } diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 306430a052..6a3a1c1ed8 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -2704,6 +2704,7 @@ static MemoryRegion smram_as_mem; static void register_smram_listener(Notifier *n, void *unused) { + CPUState *cpu; MemoryRegion *smram = (MemoryRegion *) object_resolve_path("/machine/smram", NULL); @@ -2727,7 +2728,11 @@ static void register_smram_listener(Notifier *n, void *unused) address_space_init(&smram_address_space, &smram_as_root, "KVM-SMRAM"); kvm_memory_listener_register(kvm_state, &smram_listener, - &smram_address_space, 1, "kvm-smram"); + &smram_address_space, X86ASIdx_SMM, "kvm-smram"); + + CPU_FOREACH(cpu) { + cpu_address_space_init(cpu, X86ASIdx_SMM, "cpu-smm", &smram_as_root); + } } static void *kvm_msr_energy_thread(void *data) @@ -3310,8 +3315,7 @@ int kvm_arch_init(MachineState *ms, KVMState *s) return ret; } - if (kvm_check_extension(s, KVM_CAP_X86_SMM) && - object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE) && + if (object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE) && x86_machine_is_smm_enabled(X86_MACHINE(ms))) { smram_machine_done.notify = register_smram_listener; qemu_add_machine_init_done_notifier(&smram_machine_done); @@ -5066,7 +5070,7 @@ static int kvm_put_vcpu_events(X86CPU *cpu, int level) */ events.smi.pending = cs->interrupt_request & CPU_INTERRUPT_SMI; events.smi.latched_init = cs->interrupt_request & CPU_INTERRUPT_INIT; - cs->interrupt_request &= ~(CPU_INTERRUPT_INIT | CPU_INTERRUPT_SMI); + cpu_reset_interrupt(cs, CPU_INTERRUPT_INIT | CPU_INTERRUPT_SMI); } else { /* Keep these in cs->interrupt_request. */ events.smi.pending = 0; @@ -5456,7 +5460,7 @@ void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run) if (cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI | CPU_INTERRUPT_SMI)) { if (cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI)) { bql_lock(); - cpu->interrupt_request &= ~CPU_INTERRUPT_NMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_NMI); bql_unlock(); DPRINTF("injected NMI\n"); ret = kvm_vcpu_ioctl(cpu, KVM_NMI); @@ -5467,7 +5471,7 @@ void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run) } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_SMI)) { bql_lock(); - cpu->interrupt_request &= ~CPU_INTERRUPT_SMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_SMI); bql_unlock(); DPRINTF("injected SMI\n"); ret = kvm_vcpu_ioctl(cpu, KVM_SMI); @@ -5486,10 +5490,10 @@ void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run) if (cpu_test_interrupt(cpu, CPU_INTERRUPT_INIT | CPU_INTERRUPT_TPR)) { if (cpu_test_interrupt(cpu, CPU_INTERRUPT_INIT) && !(env->hflags & HF_SMM_MASK)) { - qatomic_set(&cpu->exit_request, 1); + qatomic_set(&cpu->exit_request, true); } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_TPR)) { - qatomic_set(&cpu->exit_request, 1); + qatomic_set(&cpu->exit_request, true); } } @@ -5502,7 +5506,7 @@ void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run) bql_lock(); - cpu->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); irq = cpu_get_pic_interrupt(env); if (irq >= 0) { struct kvm_interrupt intr; @@ -5597,14 +5601,14 @@ int kvm_arch_process_async_events(CPUState *cs) /* We must not raise CPU_INTERRUPT_MCE if it's not supported. */ assert(env->mcg_cap); - cs->interrupt_request &= ~CPU_INTERRUPT_MCE; + cpu_reset_interrupt(cs, CPU_INTERRUPT_MCE); kvm_cpu_synchronize_state(cs); if (env->exception_nr == EXCP08_DBLE) { /* this means triple fault */ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); - cs->exit_request = 1; + qatomic_set(&cs->exit_request, true); return 0; } kvm_queue_exception(env, EXCP12_MCHK, 0, 0); @@ -5627,7 +5631,7 @@ int kvm_arch_process_async_events(CPUState *cs) } if (cpu_test_interrupt(cs, CPU_INTERRUPT_POLL)) { - cs->interrupt_request &= ~CPU_INTERRUPT_POLL; + cpu_reset_interrupt(cs, CPU_INTERRUPT_POLL); apic_poll_irq(cpu->apic_state); } if ((cpu_test_interrupt(cs, CPU_INTERRUPT_HARD) && @@ -5640,7 +5644,7 @@ int kvm_arch_process_async_events(CPUState *cs) do_cpu_sipi(cpu); } if (cpu_test_interrupt(cs, CPU_INTERRUPT_TPR)) { - cs->interrupt_request &= ~CPU_INTERRUPT_TPR; + cpu_reset_interrupt(cs, CPU_INTERRUPT_TPR); kvm_cpu_synchronize_state(cs); apic_handle_tpr_access_report(cpu->apic_state, env->eip, env->tpr_access_type); diff --git a/target/i386/nvmm/nvmm-accel-ops.c b/target/i386/nvmm/nvmm-accel-ops.c index 3799260bbd..dd5d5428b1 100644 --- a/target/i386/nvmm/nvmm-accel-ops.c +++ b/target/i386/nvmm/nvmm-accel-ops.c @@ -42,16 +42,14 @@ static void *qemu_nvmm_cpu_thread_fn(void *arg) qemu_guest_random_seed_thread_part2(cpu->random_seed); do { + qemu_process_cpu_events(cpu); + if (cpu_can_run(cpu)) { r = nvmm_vcpu_exec(cpu); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(cpu); } } - while (cpu_thread_is_idle(cpu)) { - qemu_cond_wait_bql(cpu->halt_cond); - } - qemu_wait_io_event_common(cpu); } while (!cpu->unplug || cpu_can_run(cpu)); nvmm_destroy_vcpu(cpu); @@ -77,7 +75,7 @@ static void nvmm_start_vcpu_thread(CPUState *cpu) */ static void nvmm_kick_vcpu_thread(CPUState *cpu) { - cpu->exit_request = 1; + qatomic_set(&cpu->exit_request, true); cpus_kick_thread(cpu); } diff --git a/target/i386/nvmm/nvmm-all.c b/target/i386/nvmm/nvmm-all.c index c1ac74c4f0..ed42425167 100644 --- a/target/i386/nvmm/nvmm-all.c +++ b/target/i386/nvmm/nvmm-all.c @@ -414,12 +414,12 @@ nvmm_vcpu_pre_run(CPUState *cpu) * or commit pending TPR access. */ if (cpu_test_interrupt(cpu, CPU_INTERRUPT_INIT | CPU_INTERRUPT_TPR)) { - cpu->exit_request = 1; + qatomic_set(&cpu->exit_request, true); } if (!has_event && cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI)) { if (nvmm_can_take_nmi(cpu)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_NMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_NMI); event->type = NVMM_VCPU_EVENT_INTR; event->vector = 2; has_event = true; @@ -428,7 +428,7 @@ nvmm_vcpu_pre_run(CPUState *cpu) if (!has_event && cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) { if (nvmm_can_take_int(cpu)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); event->type = NVMM_VCPU_EVENT_INTR; event->vector = cpu_get_pic_interrupt(env); has_event = true; @@ -437,7 +437,7 @@ nvmm_vcpu_pre_run(CPUState *cpu) /* Don't want SMIs. */ if (cpu_test_interrupt(cpu, CPU_INTERRUPT_SMI)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_SMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_SMI); } if (sync_tpr) { @@ -697,7 +697,7 @@ nvmm_vcpu_loop(CPUState *cpu) /* set int/nmi windows back to the reset state */ } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_POLL)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_POLL; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_POLL); apic_poll_irq(x86_cpu->apic_state); } if ((cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD) && @@ -710,7 +710,7 @@ nvmm_vcpu_loop(CPUState *cpu) do_cpu_sipi(x86_cpu); } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_TPR)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_TPR; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_TPR); nvmm_cpu_synchronize_state(cpu); apic_handle_tpr_access_report(x86_cpu->apic_state, env->eip, env->tpr_access_type); @@ -743,7 +743,8 @@ nvmm_vcpu_loop(CPUState *cpu) nvmm_vcpu_pre_run(cpu); - if (qatomic_read(&cpu->exit_request)) { + /* Corresponding store-release is in cpu_exit. */ + if (qatomic_load_acquire(&cpu->exit_request)) { #if NVMM_USER_VERSION >= 2 nvmm_vcpu_stop(vcpu); #else @@ -751,8 +752,6 @@ nvmm_vcpu_loop(CPUState *cpu) #endif } - /* Read exit_request before the kernel reads the immediate exit flag */ - smp_rmb(); ret = nvmm_vcpu_run(mach, vcpu); if (ret == -1) { error_report("NVMM: Failed to exec a virtual processor," @@ -818,8 +817,6 @@ nvmm_vcpu_loop(CPUState *cpu) cpu_exec_end(cpu); bql_lock(); - qatomic_set(&cpu->exit_request, false); - return ret < 0; } diff --git a/target/i386/tcg/system/seg_helper.c b/target/i386/tcg/system/seg_helper.c index 794a23ddfc..38072e51d7 100644 --- a/target/i386/tcg/system/seg_helper.c +++ b/target/i386/tcg/system/seg_helper.c @@ -178,7 +178,7 @@ bool x86_cpu_exec_interrupt(CPUState *cs, int interrupt_request) */ switch (interrupt_request) { case CPU_INTERRUPT_POLL: - cs->interrupt_request &= ~CPU_INTERRUPT_POLL; + cpu_reset_interrupt(cs, CPU_INTERRUPT_POLL); apic_poll_irq(cpu->apic_state); break; case CPU_INTERRUPT_SIPI: @@ -186,23 +186,22 @@ bool x86_cpu_exec_interrupt(CPUState *cs, int interrupt_request) break; case CPU_INTERRUPT_SMI: cpu_svm_check_intercept_param(env, SVM_EXIT_SMI, 0, 0); - cs->interrupt_request &= ~CPU_INTERRUPT_SMI; + cpu_reset_interrupt(cs, CPU_INTERRUPT_SMI); do_smm_enter(cpu); break; case CPU_INTERRUPT_NMI: cpu_svm_check_intercept_param(env, SVM_EXIT_NMI, 0, 0); - cs->interrupt_request &= ~CPU_INTERRUPT_NMI; + cpu_reset_interrupt(cs, CPU_INTERRUPT_NMI); env->hflags2 |= HF2_NMI_MASK; do_interrupt_x86_hardirq(env, EXCP02_NMI, 1); break; case CPU_INTERRUPT_MCE: - cs->interrupt_request &= ~CPU_INTERRUPT_MCE; + cpu_reset_interrupt(cs, CPU_INTERRUPT_MCE); do_interrupt_x86_hardirq(env, EXCP12_MCHK, 0); break; case CPU_INTERRUPT_HARD: cpu_svm_check_intercept_param(env, SVM_EXIT_INTR, 0, 0); - cs->interrupt_request &= ~(CPU_INTERRUPT_HARD | - CPU_INTERRUPT_VIRQ); + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD | CPU_INTERRUPT_VIRQ); intno = cpu_get_pic_interrupt(env); qemu_log_mask(CPU_LOG_INT, "Servicing hardware INT=0x%02x\n", intno); @@ -215,7 +214,7 @@ bool x86_cpu_exec_interrupt(CPUState *cs, int interrupt_request) qemu_log_mask(CPU_LOG_INT, "Servicing virtual hardware INT=0x%02x\n", intno); do_interrupt_x86_hardirq(env, intno, 1); - cs->interrupt_request &= ~CPU_INTERRUPT_VIRQ; + cpu_reset_interrupt(cs, CPU_INTERRUPT_VIRQ); env->int_ctl &= ~V_IRQ_MASK; break; } diff --git a/target/i386/tcg/system/svm_helper.c b/target/i386/tcg/system/svm_helper.c index 3569196bdd..505788b0e2 100644 --- a/target/i386/tcg/system/svm_helper.c +++ b/target/i386/tcg/system/svm_helper.c @@ -824,7 +824,7 @@ void do_vmexit(CPUX86State *env) env->intercept_exceptions = 0; /* Clears the V_IRQ and V_INTR_MASKING bits inside the processor. */ - cs->interrupt_request &= ~CPU_INTERRUPT_VIRQ; + cpu_reset_interrupt(cs, CPU_INTERRUPT_VIRQ); env->int_ctl = 0; /* Clears the TSC_OFFSET inside the processor. */ diff --git a/target/i386/tcg/system/tcg-cpu.c b/target/i386/tcg/system/tcg-cpu.c index 0538a4fd51..7255862c24 100644 --- a/target/i386/tcg/system/tcg-cpu.c +++ b/target/i386/tcg/system/tcg-cpu.c @@ -74,8 +74,8 @@ bool tcg_cpu_realizefn(CPUState *cs, Error **errp) memory_region_set_enabled(cpu->cpu_as_mem, true); cs->num_ases = 2; - cpu_address_space_init(cs, 0, "cpu-memory", cs->memory); - cpu_address_space_init(cs, 1, "cpu-smm", cpu->cpu_as_root); + cpu_address_space_init(cs, X86ASIdx_MEM, "cpu-memory", cs->memory); + cpu_address_space_init(cs, X86ASIdx_SMM, "cpu-smm", cpu->cpu_as_root); /* ... SMRAM with higher priority, linked from /machine/smram. */ cpu->machine_done.notify = tcg_cpu_machine_done; diff --git a/target/i386/whpx/whpx-accel-ops.c b/target/i386/whpx/whpx-accel-ops.c index da58805b1a..f75886128d 100644 --- a/target/i386/whpx/whpx-accel-ops.c +++ b/target/i386/whpx/whpx-accel-ops.c @@ -42,16 +42,14 @@ static void *whpx_cpu_thread_fn(void *arg) qemu_guest_random_seed_thread_part2(cpu->random_seed); do { + qemu_process_cpu_events(cpu); + if (cpu_can_run(cpu)) { r = whpx_vcpu_exec(cpu); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(cpu); } } - while (cpu_thread_is_idle(cpu)) { - qemu_cond_wait_bql(cpu->halt_cond); - } - qemu_wait_io_event_common(cpu); } while (!cpu->unplug || cpu_can_run(cpu)); whpx_destroy_vcpu(cpu); diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index 878cdd1668..2a85168ed5 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -1471,14 +1471,14 @@ static void whpx_vcpu_pre_run(CPUState *cpu) if (!vcpu->interruption_pending && cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI | CPU_INTERRUPT_SMI)) { if (cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_NMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_NMI); vcpu->interruptable = false; new_int.InterruptionType = WHvX64PendingNmi; new_int.InterruptionPending = 1; new_int.InterruptionVector = 2; } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_SMI)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_SMI; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_SMI); } } @@ -1489,10 +1489,10 @@ static void whpx_vcpu_pre_run(CPUState *cpu) if (cpu_test_interrupt(cpu, CPU_INTERRUPT_INIT | CPU_INTERRUPT_TPR)) { if (cpu_test_interrupt(cpu, CPU_INTERRUPT_INIT) && !(env->hflags & HF_SMM_MASK)) { - cpu->exit_request = 1; + qatomic_set(&cpu->exit_request, true); } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_TPR)) { - cpu->exit_request = 1; + qatomic_set(&cpu->exit_request, true); } } @@ -1502,7 +1502,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu) vcpu->interruptable && (env->eflags & IF_MASK)) { assert(!new_int.InterruptionPending); if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); irq = cpu_get_pic_interrupt(env); if (irq >= 0) { new_int.InterruptionType = WHvX64PendingInterrupt; @@ -1520,7 +1520,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu) } } else if (vcpu->ready_for_pic_interrupt && cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); irq = cpu_get_pic_interrupt(env); if (irq >= 0) { reg_names[reg_count] = WHvRegisterPendingEvent; @@ -1539,7 +1539,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu) if (tpr != vcpu->tpr) { vcpu->tpr = tpr; reg_values[reg_count].Reg64 = tpr; - cpu->exit_request = 1; + qatomic_set(&cpu->exit_request, true); reg_names[reg_count] = WHvX64RegisterCr8; reg_count += 1; } @@ -1607,7 +1607,7 @@ static void whpx_vcpu_process_async_events(CPUState *cpu) } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_POLL)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_POLL; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_POLL); apic_poll_irq(x86_cpu->apic_state); } @@ -1623,7 +1623,7 @@ static void whpx_vcpu_process_async_events(CPUState *cpu) } if (cpu_test_interrupt(cpu, CPU_INTERRUPT_TPR)) { - cpu->interrupt_request &= ~CPU_INTERRUPT_TPR; + cpu_reset_interrupt(cpu, CPU_INTERRUPT_TPR); whpx_cpu_synchronize_state(cpu); apic_handle_tpr_access_report(x86_cpu->apic_state, env->eip, env->tpr_access_type); @@ -1714,7 +1714,8 @@ static int whpx_vcpu_run(CPUState *cpu) if (exclusive_step_mode == WHPX_STEP_NONE) { whpx_vcpu_pre_run(cpu); - if (qatomic_read(&cpu->exit_request)) { + /* Corresponding store-release is in cpu_exit. */ + if (qatomic_load_acquire(&cpu->exit_request)) { whpx_vcpu_kick(cpu); } } @@ -2049,8 +2050,6 @@ static int whpx_vcpu_run(CPUState *cpu) whpx_last_vcpu_stopping(cpu); } - qatomic_set(&cpu->exit_request, false); - return ret < 0; } diff --git a/target/openrisc/sys_helper.c b/target/openrisc/sys_helper.c index d96b41a01c..b091a9c668 100644 --- a/target/openrisc/sys_helper.c +++ b/target/openrisc/sys_helper.c @@ -196,7 +196,7 @@ void HELPER(mtspr)(CPUOpenRISCState *env, target_ulong spr, target_ulong rb) env->ttmr = (rb & ~TTMR_IP) | ip; } else { /* Clear IP bit. */ env->ttmr = rb & ~TTMR_IP; - cs->interrupt_request &= ~CPU_INTERRUPT_TIMER; + cpu_reset_interrupt(cs, CPU_INTERRUPT_TIMER); } cpu_openrisc_timer_update(cpu); bql_unlock(); diff --git a/target/ppc/helper_regs.c b/target/ppc/helper_regs.c index 7e5726871e..5f21739749 100644 --- a/target/ppc/helper_regs.c +++ b/target/ppc/helper_regs.c @@ -274,6 +274,7 @@ TCGTBCPUState ppc_get_tb_cpu_state(CPUState *cs) return (TCGTBCPUState){ .pc = env->nip, .flags = hflags_current }; } +#ifndef CONFIG_USER_ONLY void cpu_interrupt_exittb(CPUState *cs) { /* @@ -285,6 +286,7 @@ void cpu_interrupt_exittb(CPUState *cs) cpu_interrupt(cs, CPU_INTERRUPT_EXITTB); } } +#endif int hreg_store_msr(CPUPPCState *env, target_ulong value, int alter_hv) { diff --git a/target/rx/helper.c b/target/rx/helper.c index ce003af421..41c9606fd1 100644 --- a/target/rx/helper.c +++ b/target/rx/helper.c @@ -63,7 +63,7 @@ void rx_cpu_do_interrupt(CPUState *cs) env->bpsw = save_psw; env->pc = env->fintv; env->psw_ipl = 15; - cs->interrupt_request &= ~CPU_INTERRUPT_FIR; + cpu_reset_interrupt(cs, CPU_INTERRUPT_FIR); qemu_set_irq(env->ack, env->ack_irq); qemu_log_mask(CPU_LOG_INT, "fast interrupt raised\n"); } else if (do_irq & CPU_INTERRUPT_HARD) { @@ -73,7 +73,7 @@ void rx_cpu_do_interrupt(CPUState *cs) cpu_stl_data(env, env->isp, env->pc); env->pc = cpu_ldl_data(env, env->intb + env->ack_irq * 4); env->psw_ipl = env->ack_ipl; - cs->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); qemu_set_irq(env->ack, env->ack_irq); qemu_log_mask(CPU_LOG_INT, "interrupt 0x%02x raised\n", env->ack_irq); diff --git a/target/s390x/tcg/excp_helper.c b/target/s390x/tcg/excp_helper.c index e4c75d0ce0..4c7faeee82 100644 --- a/target/s390x/tcg/excp_helper.c +++ b/target/s390x/tcg/excp_helper.c @@ -559,7 +559,7 @@ try_deliver: /* we might still have pending interrupts, but not deliverable */ if (!env->pending_int && !qemu_s390_flic_has_any(flic)) { - cs->interrupt_request &= ~CPU_INTERRUPT_HARD; + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); } /* WAIT PSW during interrupt injection or STOP interrupt */ diff --git a/target/sparc/int32_helper.c b/target/sparc/int32_helper.c index 39db4ffa70..fdcaa0a578 100644 --- a/target/sparc/int32_helper.c +++ b/target/sparc/int32_helper.c @@ -65,6 +65,7 @@ static const char *excp_name_str(int32_t exception_index) return excp_names[exception_index]; } +#if !defined(CONFIG_USER_ONLY) void cpu_check_irqs(CPUSPARCState *env) { CPUState *cs; @@ -96,6 +97,7 @@ void cpu_check_irqs(CPUSPARCState *env) cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); } } +#endif void sparc_cpu_do_interrupt(CPUState *cs) { diff --git a/target/sparc/int64_helper.c b/target/sparc/int64_helper.c index 49e4e51c6d..23adda4cad 100644 --- a/target/sparc/int64_helper.c +++ b/target/sparc/int64_helper.c @@ -62,6 +62,7 @@ static const char * const excp_names[0x80] = { }; #endif +#if !defined(CONFIG_USER_ONLY) void cpu_check_irqs(CPUSPARCState *env) { CPUState *cs; @@ -127,6 +128,7 @@ void cpu_check_irqs(CPUSPARCState *env) cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); } } +#endif void sparc_cpu_do_interrupt(CPUState *cs) { |