From 1e70d83de4d502759d6e68fbac301c1243088385 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 8 Sep 2025 12:49:49 +0200 Subject: rust: make build.rs generic over various ./rust/projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guess the name of the subdir from the manifest directory, instead of hard-coding it. In the following commits, other crates can then link to this file, instead of maintaining their own copy. Signed-off-by: Marc-André Lureau Link: https://lore.kernel.org/r/20250827104147.717203-5-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/build.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'rust/qemu-api/build.rs') diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs index 29d0945625..5654d1d562 100644 --- a/rust/qemu-api/build.rs +++ b/rust/qemu-api/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..]) +} -- cgit 1.4.1 From 59869b4d58854190f09a79c5392d60fdc0b55d45 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 8 Sep 2025 12:49:50 +0200 Subject: rust: split "util" crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marc-André Lureau Link: https://lore.kernel.org/r/20250827104147.717203-7-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- MAINTAINERS | 1 + rust/Cargo.lock | 17 +- rust/Cargo.toml | 5 +- rust/hw/char/pl011/Cargo.toml | 1 + rust/hw/char/pl011/meson.build | 1 + rust/hw/char/pl011/src/device.rs | 7 +- rust/hw/timer/hpet/Cargo.toml | 1 + rust/hw/timer/hpet/meson.build | 1 + rust/hw/timer/hpet/src/device.rs | 6 +- rust/meson.build | 1 + rust/qemu-api-macros/src/lib.rs | 2 +- rust/qemu-api-macros/src/tests.rs | 2 +- rust/qemu-api/Cargo.toml | 4 +- rust/qemu-api/build.rs | 50 +---- rust/qemu-api/meson.build | 14 +- rust/qemu-api/src/bindings.rs | 1 + rust/qemu-api/src/error.rs | 417 -------------------------------------- rust/qemu-api/src/lib.rs | 6 - rust/qemu-api/src/log.rs | 151 -------------- rust/qemu-api/src/module.rs | 43 ---- rust/qemu-api/src/prelude.rs | 2 - rust/qemu-api/src/qdev.rs | 4 +- rust/qemu-api/src/sysbus.rs | 2 +- rust/qemu-api/src/timer.rs | 125 ------------ rust/qemu-api/src/vmstate.rs | 2 +- rust/qemu-api/tests/tests.rs | 2 +- rust/qemu-api/wrapper.h | 6 - rust/util/Cargo.toml | 23 +++ rust/util/build.rs | 49 +++++ rust/util/meson.build | 55 +++++ rust/util/src/bindings.rs | 25 +++ rust/util/src/error.rs | 416 +++++++++++++++++++++++++++++++++++++ rust/util/src/lib.rs | 9 + rust/util/src/log.rs | 151 ++++++++++++++ rust/util/src/module.rs | 43 ++++ rust/util/src/timer.rs | 125 ++++++++++++ rust/util/wrapper.h | 32 +++ 37 files changed, 969 insertions(+), 833 deletions(-) mode change 100644 => 120000 rust/qemu-api/build.rs delete mode 100644 rust/qemu-api/src/error.rs delete mode 100644 rust/qemu-api/src/log.rs delete mode 100644 rust/qemu-api/src/module.rs delete mode 100644 rust/qemu-api/src/timer.rs create mode 100644 rust/util/Cargo.toml create mode 100644 rust/util/build.rs create mode 100644 rust/util/meson.build create mode 100644 rust/util/src/bindings.rs create mode 100644 rust/util/src/error.rs create mode 100644 rust/util/src/lib.rs create mode 100644 rust/util/src/log.rs create mode 100644 rust/util/src/module.rs create mode 100644 rust/util/src/timer.rs create mode 100644 rust/util/wrapper.h (limited to 'rust/qemu-api/build.rs') diff --git a/MAINTAINERS b/MAINTAINERS index cee5a34206..3d7b47873f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3519,6 +3519,7 @@ F: rust/common/ F: rust/qemu-api F: rust/qemu-api-macros F: rust/rustfmt.toml +F: rust/util/ F: scripts/get-wraps-from-cargo-registry.py Rust-related patches CC here diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 71e8c7ed62..757c03cbde 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -73,6 +73,7 @@ dependencies = [ "common", "qemu_api", "qemu_api_macros", + "util", ] [[package]] @@ -100,6 +101,7 @@ dependencies = [ "common", "qemu_api", "qemu_api_macros", + "util", ] [[package]] @@ -138,11 +140,9 @@ dependencies = [ name = "qemu_api" version = "0.1.0" dependencies = [ - "anyhow", "common", - "foreign", - "libc", "qemu_api_macros", + "util", ] [[package]] @@ -180,6 +180,17 @@ 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", + "qemu_api_macros", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c0426d4243..cfdd535e3b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,6 +7,7 @@ members = [ "qemu-api", "hw/char/pl011", "hw/timer/hpet", + "util", ] [workspace.package] @@ -24,9 +25,7 @@ foreign = "~0.3.1" libc = "0.2.162" [workspace.lints.rust] -unexpected_cfgs = { level = "deny", check-cfg = [ - 'cfg(MESON)', -] } +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/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml index 6d15f107df..0cf9943fe8 100644 --- a/rust/hw/char/pl011/Cargo.toml +++ b/rust/hw/char/pl011/Cargo.toml @@ -17,6 +17,7 @@ bilge = { version = "0.2.0" } bilge-impl = { version = "0.2.0" } bits = { path = "../../../bits" } common = { path = "../../../common" } +util = { path = "../../../util" } qemu_api = { path = "../../../qemu-api" } qemu_api_macros = { path = "../../../qemu-api-macros" } diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build index c4a9f531f7..8a931a4d03 100644 --- a/rust/hw/char/pl011/meson.build +++ b/rust/hw/char/pl011/meson.build @@ -9,6 +9,7 @@ _libpl011_rs = static_library( bits_rs, common_rs, qemu_api_rs, + util_rs, qemu_api_macros, ], ) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index b4aa6c45f8..ab38d57fc4 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -9,8 +9,6 @@ use qemu_api::{ chardev::{CharBackend, Chardev, Event}, impl_vmstate_forward, impl_vmstate_struct, irq::{IRQState, InterruptSource}, - log::Log, - log_mask_ln, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, ResetType, ResettablePhasesImpl}, @@ -19,6 +17,7 @@ use qemu_api::{ vmstate::{self, VMStateDescription, VMStateDescriptionBuilder}, vmstate_fields, vmstate_of, vmstate_subsections, vmstate_unused, }; +use util::{log::Log, log_mask_ln}; use crate::registers::{self, Interrupt, RegisterOffset}; @@ -172,7 +171,7 @@ impl ObjectImpl for PL011State { impl DeviceImpl for PL011State { const VMSTATE: Option> = Some(VMSTATE_PL011); - const REALIZE: Option qemu_api::Result<()>> = Some(Self::realize); + const REALIZE: Option util::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for PL011State { @@ -623,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(()) diff --git a/rust/hw/timer/hpet/Cargo.toml b/rust/hw/timer/hpet/Cargo.toml index ba7354f07e..dd9a5ed3d4 100644 --- a/rust/hw/timer/hpet/Cargo.toml +++ b/rust/hw/timer/hpet/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true [dependencies] common = { path = "../../../common" } +util = { path = "../../../util" } qemu_api = { path = "../../../qemu-api" } qemu_api_macros = { path = "../../../qemu-api-macros" } diff --git a/rust/hw/timer/hpet/meson.build b/rust/hw/timer/hpet/meson.build index c91d330439..ca09660bf4 100644 --- a/rust/hw/timer/hpet/meson.build +++ b/rust/hw/timer/hpet/meson.build @@ -6,6 +6,7 @@ _libhpet_rs = static_library( dependencies: [ common_rs, qemu_api_rs, + util_rs, qemu_api_macros, ], ) diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index 72375d3155..2be180fded 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -27,10 +27,10 @@ use qemu_api::{ qom::{ObjectImpl, ObjectType, ParentField, ParentInit}, qom_isa, sysbus::{SysBusDevice, SysBusDeviceImpl}, - timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND}, vmstate::{self, VMStateDescription, VMStateDescriptionBuilder}, vmstate_fields, vmstate_of, vmstate_subsections, vmstate_validate, }; +use util::timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND}; use crate::fw_cfg::HPETFwConfig; @@ -728,7 +728,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}" @@ -1013,7 +1013,7 @@ unsafe impl qemu_api::qdev::DevicePropertiesImpl for HPETState { impl DeviceImpl for HPETState { const VMSTATE: Option> = Some(VMSTATE_HPET); - const REALIZE: Option qemu_api::Result<()>> = Some(Self::realize); + const REALIZE: Option util::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for HPETState { diff --git a/rust/meson.build b/rust/meson.build index 402f8d6600..a9d715e6e9 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -25,6 +25,7 @@ genrs = [] subdir('common') subdir('qemu-api-macros') subdir('bits') +subdir('util') subdir('qemu-api') subdir('hw') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 49003a4780..67650a9a26 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -100,7 +100,7 @@ fn derive_object_or_error(input: DeriveInput) -> Result::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); } diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-api-macros/src/tests.rs index ef2806368d..8e71ac6e67 100644 --- a/rust/qemu-api-macros/src/tests.rs +++ b/rust/qemu-api-macros/src/tests.rs @@ -170,7 +170,7 @@ fn test_derive_object() { _unused, ::qemu_api::qom::ParentField<::ParentType> ); - ::qemu_api::module_init! { + ::util::module_init! { MODULE_INIT_QOM => unsafe { ::qemu_api::bindings::type_register_static(&::TYPE_INFO); } diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml index 2e0e204491..fbfb894421 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/qemu-api/Cargo.toml @@ -15,10 +15,8 @@ rust-version.workspace = true [dependencies] common = { path = "../common" } +util = { path = "../util" } qemu_api_macros = { path = "../qemu-api-macros" } -anyhow = { workspace = true } -foreign = { workspace = true } -libc = { workspace = true } [features] default = ["debug_cell"] diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs deleted file mode 100644 index 5654d1d562..0000000000 --- a/rust/qemu-api/build.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -#[cfg(unix)] -use std::os::unix::fs::symlink as symlink_file; -#[cfg(windows)] -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") { - 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!("{manifest_dir}/src/bindings.inc.rs") - }; - - let file = Path::new(&file); - if !Path::new(&file).exists() { - panic!(concat!( - "\n", - " No generated C bindings found! Maybe you wanted one of\n", - " `make clippy`, `make rustfmt`, `make rustdoc`?\n", - "\n", - " For other uses of `cargo`, start a subshell with\n", - " `pyvenv/bin/meson devenv`, or point MESON_BUILD_ROOT to\n", - " the top of the build tree." - )); - } - - let out_dir = env::var("OUT_DIR").unwrap(); - let dest_path = format!("{out_dir}/bindings.inc.rs"); - let dest_path = Path::new(&dest_path); - if dest_path.symlink_metadata().is_ok() { - remove_file(dest_path)?; - } - symlink_file(file, dest_path)?; - - 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/qemu-api/build.rs b/rust/qemu-api/build.rs new file mode 120000 index 0000000000..71a3167885 --- /dev/null +++ b/rust/qemu-api/build.rs @@ -0,0 +1 @@ +../util/build.rs \ No newline at end of file diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 64af3caef5..7734f656a2 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -14,10 +14,8 @@ c_enums = [ 'MigrationPolicy', 'MigrationPriority', 'QEMUChrEvent', - 'QEMUClockType', 'ResetType', 'device_endian', - 'module_init_type', ] _qemu_api_bindgen_args = [] foreach enum : c_enums @@ -31,6 +29,7 @@ foreach enum : c_bitfields _qemu_api_bindgen_args += ['--bitfield-enum', enum] endforeach +_qemu_api_bindgen_args += ['--blocklist-type', 'Error'] # TODO: Remove this comment when the clang/libclang mismatch issue is solved. # # Rust bindings generation with `bindgen` might fail in some cases where the @@ -55,16 +54,12 @@ _qemu_api_rs = static_library( 'src/bindings.rs', 'src/cell.rs', 'src/chardev.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/vmstate.rs', ], {'.' : _qemu_api_bindings_inc_rs}, @@ -72,13 +67,10 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, - dependencies: [anyhow_rs, common_rs, foreign_rs, libc_rs, qemu_api_macros, qemuutil_rs, + dependencies: [anyhow_rs, common_rs, foreign_rs, libc_rs, qemu_api_macros, util_rs, qom, hwcore, chardev, migration], ) -rust.test('rust-qemu-api-tests', _qemu_api_rs, - suite: ['unit', 'rust']) - qemu_api_rs = declare_dependency(link_with: [_qemu_api_rs], dependencies: [qemu_api_macros, qom, hwcore, chardev, migration]) @@ -98,7 +90,7 @@ test('rust-qemu-api-integration', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: ['--test'], install: false, - dependencies: [common_rs, qemu_api_rs]), + dependencies: [common_rs, util_rs, qemu_api_rs]), args: [ '--test', '--test-threads', '1', '--format', 'pretty', diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 3acdd903b5..aedf42b652 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -21,6 +21,7 @@ //! `bindgen`-generated declarations. use common::Zeroable; +use util::bindings::Error; #[cfg(MESON)] include!("bindings.inc.rs"); diff --git a/rust/qemu-api/src/error.rs b/rust/qemu-api/src/error.rs deleted file mode 100644 index 8bac3cbec8..0000000000 --- a/rust/qemu-api/src/error.rs +++ /dev/null @@ -1,417 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Error propagation for QEMU Rust code -//! -//! This module contains [`Error`], the bridge between Rust errors and -//! [`Result`](std::result::Result)s and QEMU's C [`Error`](bindings::Error) -//! struct. -//! -//! For FFI code, [`Error`] provides functions to simplify conversion between -//! the Rust ([`Result<>`](std::result::Result)) and C (`Error**`) conventions: -//! -//! * [`ok_or_propagate`](crate::Error::ok_or_propagate), -//! [`bool_or_propagate`](crate::Error::bool_or_propagate), -//! [`ptr_or_propagate`](crate::Error::ptr_or_propagate) can be used to build -//! a C return value while also propagating an error condition -//! -//! * [`err_or_else`](crate::Error::err_or_else) and -//! [`err_or_unit`](crate::Error::err_or_unit) can be used to build a `Result` -//! -//! 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 -//! [`std::error::Error`] interface to let C errors participate in Rust's error -//! handling functionality. -//! -//! Rust code can also create use this module to create an error object that -//! will be passed up to C code, though in most cases this will be done -//! transparently through the `?` operator. Errors can be constructed from a -//! simple error string, from an [`anyhow::Error`] to pass any other Rust error -//! 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 -//! to how QEMU's C code handles errno values, the string and the -//! `anyhow::Error` object will be concatenated with `:` as the separator. - -use std::{ - borrow::Cow, - ffi::{c_char, c_int, c_void, CStr}, - fmt::{self, Display}, - panic, ptr, -}; - -use foreign::{prelude::*, OwnedPointer}; - -use crate::bindings; - -pub type Result = std::result::Result; - -#[derive(Debug)] -pub struct Error { - msg: Option>, - /// Appends the print string of the error to the msg if not None - cause: Option, - file: &'static str, - line: u32, -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.cause.as_ref().map(AsRef::as_ref) - } - - #[allow(deprecated)] - fn description(&self) -> &str { - self.msg - .as_deref() - .or_else(|| self.cause.as_deref().map(std::error::Error::description)) - .expect("no message nor cause?") - } -} - -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut prefix = ""; - if let Some(ref msg) = self.msg { - write!(f, "{msg}")?; - prefix = ": "; - } - if let Some(ref cause) = self.cause { - write!(f, "{prefix}{cause}")?; - } else if prefix.is_empty() { - panic!("no message nor cause?"); - } - Ok(()) - } -} - -impl From for Error { - #[track_caller] - fn from(msg: String) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(Cow::Owned(msg)), - cause: None, - file: location.file(), - line: location.line(), - } - } -} - -impl From<&'static str> for Error { - #[track_caller] - fn from(msg: &'static str) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(Cow::Borrowed(msg)), - cause: None, - file: location.file(), - line: location.line(), - } - } -} - -impl From for Error { - #[track_caller] - fn from(error: anyhow::Error) -> Self { - let location = panic::Location::caller(); - Error { - msg: None, - cause: Some(error), - file: location.file(), - line: location.line(), - } - } -} - -impl Error { - /// Create a new error, prepending `msg` to the - /// description of `cause` - #[track_caller] - pub fn with_error(msg: impl Into>, cause: impl Into) -> Self { - let location = panic::Location::caller(); - Error { - msg: Some(msg.into()), - cause: Some(cause.into()), - file: location.file(), - line: location.line(), - } - } - - /// Consume a result, returning `false` if it is an error and - /// `true` if it is successful. The error is propagated into - /// `errp` like the C API `error_propagate` would do. - /// - /// # Safety - /// - /// `errp` must be a valid argument to `error_propagate`; - /// typically it is received from C code and need not be - /// checked further at the Rust↔C boundary. - pub unsafe fn bool_or_propagate(result: Result<()>, errp: *mut *mut bindings::Error) -> bool { - // SAFETY: caller guarantees errp is valid - unsafe { Self::ok_or_propagate(result, errp) }.is_some() - } - - /// Consume a result, returning a `NULL` pointer if it is an error and - /// a C representation of the contents if it is successful. This is - /// similar to the C API `error_propagate`, but it panics if `*errp` - /// is not `NULL`. - /// - /// # Safety - /// - /// `errp` must be a valid argument to `error_propagate`; - /// typically it is received from C code and need not be - /// checked further at the Rust↔C boundary. - /// - /// See [`propagate`](Error::propagate) for more information. - #[must_use] - pub unsafe fn ptr_or_propagate( - result: Result, - errp: *mut *mut bindings::Error, - ) -> *mut T::Foreign { - // SAFETY: caller guarantees errp is valid - unsafe { Self::ok_or_propagate(result, errp) }.clone_to_foreign_ptr() - } - - /// Consume a result in the same way as `self.ok()`, but also propagate - /// a possible error into `errp`. This is similar to the C API - /// `error_propagate`, but it panics if `*errp` is not `NULL`. - /// - /// # Safety - /// - /// `errp` must be a valid argument to `error_propagate`; - /// typically it is received from C code and need not be - /// checked further at the Rust↔C boundary. - /// - /// See [`propagate`](Error::propagate) for more information. - pub unsafe fn ok_or_propagate( - result: Result, - errp: *mut *mut bindings::Error, - ) -> Option { - result.map_err(|err| unsafe { err.propagate(errp) }).ok() - } - - /// Equivalent of the C function `error_propagate`. Fill `*errp` - /// with the information container in `self` if `errp` is not NULL; - /// then consume it. - /// - /// This is similar to the C API `error_propagate`, but it panics if - /// `*errp` is not `NULL`. - /// - /// # Safety - /// - /// `errp` must be a valid argument to `error_propagate`; it can be - /// `NULL` or it can point to any of: - /// * `error_abort` - /// * `error_fatal` - /// * a local variable of (C) type `Error *` - /// - /// Typically `errp` is received from C code and need not be - /// checked further at the Rust↔C boundary. - pub unsafe fn propagate(self, errp: *mut *mut bindings::Error) { - if errp.is_null() { - return; - } - - // SAFETY: caller guarantees that errp and *errp are valid - unsafe { - assert_eq!(*errp, ptr::null_mut()); - bindings::error_propagate(errp, self.clone_to_foreign_ptr()); - } - } - - /// Convert a C `Error*` into a Rust `Result`, using - /// `Ok(())` if `c_error` is NULL. Free the `Error*`. - /// - /// # Safety - /// - /// `c_error` must be `NULL` or valid; typically it was initialized - /// with `ptr::null_mut()` and passed by reference to a C function. - pub unsafe fn err_or_unit(c_error: *mut bindings::Error) -> Result<()> { - // SAFETY: caller guarantees c_error is valid - unsafe { Self::err_or_else(c_error, || ()) } - } - - /// Convert a C `Error*` into a Rust `Result`, calling `f()` to - /// obtain an `Ok` value if `c_error` is NULL. Free the `Error*`. - /// - /// # Safety - /// - /// `c_error` must be `NULL` or point to a valid C [`struct - /// Error`](bindings::Error); typically it was initialized with - /// `ptr::null_mut()` and passed by reference to a C function. - pub unsafe fn err_or_else T>( - c_error: *mut bindings::Error, - f: F, - ) -> Result { - // SAFETY: caller guarantees c_error is valid - let err = unsafe { Option::::from_foreign(c_error) }; - match err { - None => Ok(f()), - Some(err) => Err(err), - } - } -} - -impl FreeForeign for Error { - type Foreign = bindings::Error; - - unsafe fn free_foreign(p: *mut bindings::Error) { - // SAFETY: caller guarantees p is valid - unsafe { - bindings::error_free(p); - } - } -} - -impl CloneToForeign for Error { - fn clone_to_foreign(&self) -> OwnedPointer { - // SAFETY: all arguments are controlled by this function - unsafe { - let err: *mut c_void = libc::malloc(std::mem::size_of::()); - let err: &mut bindings::Error = &mut *err.cast(); - *err = bindings::Error { - msg: format!("{self}").clone_to_foreign_ptr(), - err_class: bindings::ERROR_CLASS_GENERIC_ERROR, - src_len: self.file.len() as c_int, - src: self.file.as_ptr().cast::(), - line: self.line as c_int, - func: ptr::null_mut(), - hint: ptr::null_mut(), - }; - OwnedPointer::new(err) - } - } -} - -impl FromForeign for Error { - unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self { - // SAFETY: caller guarantees c_error is valid - unsafe { - let error = &*c_error; - let file = if error.src_len < 0 { - // NUL-terminated - CStr::from_ptr(error.src).to_str() - } else { - // Can become str::from_utf8 with Rust 1.87.0 - std::str::from_utf8(std::slice::from_raw_parts( - &*error.src.cast::(), - error.src_len as usize, - )) - }; - - Error { - msg: FromForeign::cloned_from_foreign(error.msg), - cause: None, - file: file.unwrap(), - line: error.line as u32, - } - } - } -} - -#[cfg(test)] -mod tests { - use std::ffi::CStr; - - use anyhow::anyhow; - use common::assert_match; - use foreign::OwnedPointer; - - use super::*; - use crate::bindings; - - #[track_caller] - fn error_for_test(msg: &CStr) -> OwnedPointer { - // SAFETY: all arguments are controlled by this function - let location = panic::Location::caller(); - unsafe { - let err: *mut c_void = libc::malloc(std::mem::size_of::()); - let err: &mut bindings::Error = &mut *err.cast(); - *err = bindings::Error { - msg: msg.clone_to_foreign_ptr(), - err_class: bindings::ERROR_CLASS_GENERIC_ERROR, - src_len: location.file().len() as c_int, - src: location.file().as_ptr().cast::(), - line: location.line() as c_int, - func: ptr::null_mut(), - hint: ptr::null_mut(), - }; - OwnedPointer::new(err) - } - } - - unsafe fn error_get_pretty<'a>(local_err: *mut bindings::Error) -> &'a CStr { - unsafe { CStr::from_ptr(bindings::error_get_pretty(local_err)) } - } - - #[test] - #[allow(deprecated)] - fn test_description() { - use std::error::Error; - - assert_eq!(super::Error::from("msg").description(), "msg"); - assert_eq!(super::Error::from("msg".to_owned()).description(), "msg"); - } - - #[test] - fn test_display() { - assert_eq!(&*format!("{}", Error::from("msg")), "msg"); - assert_eq!(&*format!("{}", Error::from("msg".to_owned())), "msg"); - assert_eq!(&*format!("{}", Error::from(anyhow!("msg"))), "msg"); - - assert_eq!( - &*format!("{}", Error::with_error("msg", anyhow!("cause"))), - "msg: cause" - ); - } - - #[test] - fn test_bool_or_propagate() { - unsafe { - let mut local_err: *mut bindings::Error = ptr::null_mut(); - - assert!(Error::bool_or_propagate(Ok(()), &mut local_err)); - assert_eq!(local_err, ptr::null_mut()); - - let my_err = Error::from("msg"); - assert!(!Error::bool_or_propagate(Err(my_err), &mut local_err)); - assert_ne!(local_err, ptr::null_mut()); - assert_eq!(error_get_pretty(local_err), c"msg"); - bindings::error_free(local_err); - } - } - - #[test] - fn test_ptr_or_propagate() { - unsafe { - let mut local_err: *mut bindings::Error = ptr::null_mut(); - - let ret = Error::ptr_or_propagate(Ok("abc".to_owned()), &mut local_err); - assert_eq!(String::from_foreign(ret), "abc"); - assert_eq!(local_err, ptr::null_mut()); - - let my_err = Error::from("msg"); - assert_eq!( - Error::ptr_or_propagate(Err::(my_err), &mut local_err), - ptr::null_mut() - ); - assert_ne!(local_err, ptr::null_mut()); - assert_eq!(error_get_pretty(local_err), c"msg"); - bindings::error_free(local_err); - } - } - - #[test] - fn test_err_or_unit() { - unsafe { - let result = Error::err_or_unit(ptr::null_mut()); - assert_match!(result, Ok(())); - - let err = error_for_test(c"msg"); - let err = Error::err_or_unit(err.into_inner()).unwrap_err(); - assert_eq!(&*format!("{err}"), "msg"); - } - } -} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 6232e39e71..54b252fb2c 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -15,19 +15,13 @@ pub mod prelude; pub mod cell; pub mod chardev; -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 vmstate; // Allow proc-macros to refer to `::qemu_api` inside the `qemu_api` crate (this // crate). extern crate self as qemu_api; - -pub use error::{Error, Result}; diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs deleted file mode 100644 index d07f6272dc..0000000000 --- a/rust/qemu-api/src/log.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2025 Bernhard Beschow -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Bindings for QEMU's logging infrastructure - -use std::{ - io::{self, Write}, - ptr::NonNull, -}; - -use common::errno; - -use crate::bindings; - -#[repr(u32)] -/// Represents specific error categories within QEMU's logging system. -/// -/// The `Log` enum provides a Rust abstraction for logging errors, corresponding -/// to a subset of the error categories defined in the C implementation. -pub enum Log { - /// Log invalid access caused by the guest. - /// Corresponds to `LOG_GUEST_ERROR` in the C implementation. - GuestError = bindings::LOG_GUEST_ERROR, - - /// Log guest access of unimplemented functionality. - /// Corresponds to `LOG_UNIMP` in the C implementation. - Unimp = bindings::LOG_UNIMP, -} - -/// A RAII guard for QEMU's logging infrastructure. Creating the guard -/// locks the log file, and dropping it (letting it go out of scope) unlocks -/// the file. -/// -/// As long as the guard lives, it can be written to using [`std::io::Write`]. -/// -/// The locking is recursive, therefore owning a guard does not prevent -/// using [`log_mask_ln!()`](crate::log_mask_ln). -pub struct LogGuard(NonNull); - -impl LogGuard { - /// Return a RAII guard that writes to QEMU's logging infrastructure. - /// The log file is locked while the guard exists, ensuring that there - /// is no tearing of the messages. - /// - /// Return `None` if the log file is closed and could not be opened. - /// Do *not* use `unwrap()` on the result; failure can be handled simply - /// by not logging anything. - /// - /// # Examples - /// - /// ``` - /// # use qemu_api::log::LogGuard; - /// # use std::io::Write; - /// if let Some(mut log) = LogGuard::new() { - /// writeln!(log, "test"); - /// } - /// ``` - pub fn new() -> Option { - let f = unsafe { bindings::qemu_log_trylock() }.cast(); - NonNull::new(f).map(Self) - } - - /// Writes a formatted string into the log, returning any error encountered. - /// - /// This method is primarily used by the - /// [`log_mask_ln!()`](crate::log_mask_ln) macro, and it is rare for it - /// to be called explicitly. It is public because it is the only way to - /// examine the error, which `log_mask_ln!()` ignores - /// - /// Unlike `log_mask_ln!()`, it does *not* append a newline at the end. - pub fn log_fmt(args: std::fmt::Arguments) -> io::Result<()> { - if let Some(mut log) = Self::new() { - log.write_fmt(args)?; - } - Ok(()) - } -} - -impl Write for LogGuard { - fn write(&mut self, bytes: &[u8]) -> io::Result { - let ret = unsafe { - bindings::rust_fwrite(bytes.as_ptr().cast(), 1, bytes.len(), self.0.as_ptr()) - }; - errno::into_io_result(ret) - } - - fn flush(&mut self) -> io::Result<()> { - // Do nothing, dropping the guard takes care of flushing - Ok(()) - } -} - -impl Drop for LogGuard { - fn drop(&mut self) { - unsafe { - bindings::qemu_log_unlock(self.0.as_ptr()); - } - } -} - -/// A macro to log messages conditionally based on a provided mask. -/// -/// The `log_mask_ln` macro checks whether the given mask matches the current -/// log level and, if so, formats and logs the message. It is the Rust -/// counterpart of the `qemu_log_mask()` macro in the C implementation. -/// -/// Errors from writing to the log are ignored. -/// -/// # Parameters -/// -/// - `$mask`: A log level mask. This should be a variant of the `Log` enum. -/// - `$fmt`: A format string following the syntax and rules of the `format!` -/// macro. It specifies the structure of the log message. -/// - `$args`: Optional arguments to be interpolated into the format string. -/// -/// # Example -/// -/// ``` -/// use qemu_api::{log::Log, log_mask_ln}; -/// -/// let error_address = 0xbad; -/// log_mask_ln!(Log::GuestError, "Address 0x{error_address:x} out of range"); -/// ``` -/// -/// It is also possible to use printf-style formatting, as well as having a -/// trailing `,`: -/// -/// ``` -/// use qemu_api::{log::Log, log_mask_ln}; -/// -/// let error_address = 0xbad; -/// log_mask_ln!( -/// Log::GuestError, -/// "Address 0x{:x} out of range", -/// error_address, -/// ); -/// ``` -#[macro_export] -macro_rules! log_mask_ln { - ($mask:expr, $fmt:tt $($args:tt)*) => {{ - // Type assertion to enforce type `Log` for $mask - let _: Log = $mask; - - if unsafe { - (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 - } { - _ = ::qemu_api::log::LogGuard::log_fmt( - format_args!("{}\n", format_args!($fmt $($args)*))); - } - }}; -} diff --git a/rust/qemu-api/src/module.rs b/rust/qemu-api/src/module.rs deleted file mode 100644 index fa5cea3598..0000000000 --- a/rust/qemu-api/src/module.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Macro to register blocks of code that run as QEMU starts up. - -#[macro_export] -macro_rules! module_init { - ($type:ident => $body:block) => { - const _: () = { - #[used] - #[cfg_attr( - not(any(target_vendor = "apple", target_os = "windows")), - link_section = ".init_array" - )] - #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")] - #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] - pub static LOAD_MODULE: extern "C" fn() = { - extern "C" fn init_fn() { - $body - } - - extern "C" fn ctor_fn() { - unsafe { - $crate::bindings::register_module_init( - Some(init_fn), - $crate::bindings::module_init_type::$type, - ); - } - } - - ctor_fn - }; - }; - }; - - // shortcut because it's quite common that $body needs unsafe {} - ($type:ident => unsafe $body:block) => { - $crate::module_init! { - $type => { unsafe { $body } } - } - }; -} diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index dcfe71497f..3d771481e4 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -7,8 +7,6 @@ pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; -pub use crate::log_mask_ln; - pub use crate::qdev::DeviceMethods; pub use crate::qom::InterfaceType; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 8be125fae4..d87479ce13 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -11,12 +11,12 @@ use std::{ pub use bindings::{ClockEvent, DeviceClass, Property, ResetType}; use common::{callbacks::FnCall, Opaque}; +use util::{Error, Result}; use crate::{ bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, ResettableClass}, cell::bql_locked, chardev::Chardev, - error::{Error, Result}, impl_vmstate_c_struct, irq::InterruptSource, prelude::*, @@ -183,7 +183,7 @@ pub trait DeviceImpl: /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_realize_fn( dev: *mut bindings::DeviceState, - errp: *mut *mut bindings::Error, + errp: *mut *mut util::bindings::Error, ) { let state = NonNull::new(dev).unwrap().cast::(); let result = T::REALIZE.unwrap()(unsafe { state.as_ref() }); diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 4a5b4cbbf6..2dbfc31dbd 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -114,7 +114,7 @@ where 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/src/timer.rs b/rust/qemu-api/src/timer.rs deleted file mode 100644 index 383e1a6e77..0000000000 --- a/rust/qemu-api/src/timer.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu -// SPDX-License-Identifier: GPL-2.0-or-later - -use std::{ - ffi::{c_int, c_void}, - pin::Pin, -}; - -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)] -pub struct Timer(Opaque); - -unsafe impl Send for Timer {} -unsafe impl Sync for Timer {} - -#[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] -pub struct TimerListGroup(Opaque); - -unsafe impl Send for TimerListGroup {} -unsafe impl Sync for TimerListGroup {} - -impl Timer { - pub const MS: u32 = bindings::SCALE_MS; - pub const US: u32 = bindings::SCALE_US; - pub const NS: u32 = bindings::SCALE_NS; - - /// Create a `Timer` struct without initializing it. - /// - /// # Safety - /// - /// The timer must be initialized before it is armed with - /// [`modify`](Self::modify). - pub const unsafe fn new() -> Self { - // SAFETY: requirements relayed to callers of Timer::new - Self(unsafe { Opaque::zeroed() }) - } - - /// Create a new timer with the given attributes. - pub fn init_full<'timer, 'opaque: 'timer, T, F>( - self: Pin<&'timer mut Self>, - timer_list_group: Option<&TimerListGroup>, - clk_type: ClockType, - scale: u32, - attributes: u32, - _cb: F, - opaque: &'opaque T, - ) where - F: for<'a> FnCall<(&'a T,)>, - { - const { assert!(F::IS_SOME) }; - - /// timer expiration callback - unsafe extern "C" fn rust_timer_handler FnCall<(&'a T,)>>( - opaque: *mut c_void, - ) { - // SAFETY: the opaque was passed as a reference to `T`. - F::call((unsafe { &*(opaque.cast::()) },)) - } - - let timer_cb: unsafe extern "C" fn(*mut c_void) = rust_timer_handler::; - - // SAFETY: the opaque outlives the timer - unsafe { - timer_init_full( - self.as_mut_ptr(), - if let Some(g) = timer_list_group { - g as *const TimerListGroup as *mut _ - } else { - ::core::ptr::null_mut() - }, - clk_type.id, - scale as c_int, - attributes as c_int, - Some(timer_cb), - (opaque as *const T).cast::().cast_mut(), - ) - } - } - - pub fn modify(&self, expire_time: u64) { - // SAFETY: the only way to obtain a Timer safely is via methods that - // take a Pin<&mut Self>, therefore the timer is pinned - unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) } - } - - pub fn delete(&self) { - // SAFETY: the only way to obtain a Timer safely is via methods that - // take a Pin<&mut Self>, therefore the timer is pinned - unsafe { timer_del(self.as_mut_ptr()) } - } -} - -// FIXME: use something like PinnedDrop from the pinned_init crate -impl Drop for Timer { - fn drop(&mut self) { - self.delete() - } -} - -pub struct ClockType { - id: QEMUClockType, -} - -impl ClockType { - pub fn get_ns(&self) -> u64 { - // SAFETY: cannot be created outside this module, therefore id - // is valid - (unsafe { qemu_clock_get_ns(self.id) }) as u64 - } -} - -pub const CLOCK_VIRTUAL: ClockType = ClockType { - id: QEMUClockType::QEMU_CLOCK_VIRTUAL, -}; - -pub const NANOSECONDS_PER_SECOND: u64 = 1000000000; diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 06aac3a73f..37e47cc4c6 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -315,7 +315,7 @@ 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 { diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 1349568741..70ef4a80d5 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -5,7 +5,6 @@ use std::{ffi::CStr, ptr::addr_of}; use qemu_api::{ - bindings::{module_call_init, module_init_type}, cell::{self, BqlCell}, prelude::*, qdev::{DeviceImpl, DeviceState, ResettablePhasesImpl}, @@ -13,6 +12,7 @@ use qemu_api::{ sysbus::SysBusDevice, vmstate::{VMStateDescription, VMStateDescriptionBuilder}, }; +use util::bindings::{module_call_init, module_init_type}; mod vmstate_tests; diff --git a/rust/qemu-api/wrapper.h b/rust/qemu-api/wrapper.h index 15a1b19847..cc7112406b 100644 --- a/rust/qemu-api/wrapper.h +++ b/rust/qemu-api/wrapper.h @@ -48,9 +48,6 @@ 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" @@ -61,11 +58,8 @@ typedef enum memory_order { #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/util/Cargo.toml b/rust/util/Cargo.toml new file mode 100644 index 0000000000..637df61060 --- /dev/null +++ b/rust/util/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "util" +version = "0.1.0" +description = "Rust bindings for QEMU/util" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +anyhow = { workspace = true } +foreign = { workspace = true } +libc = { workspace = true } +common = { path = "../common" } +qemu_api_macros = { path = "../qemu-api-macros" } + +[lints] +workspace = true diff --git a/rust/util/build.rs b/rust/util/build.rs new file mode 100644 index 0000000000..5654d1d562 --- /dev/null +++ b/rust/util/build.rs @@ -0,0 +1,49 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +#[cfg(unix)] +use std::os::unix::fs::symlink as symlink_file; +#[cfg(windows)] +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") { + 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!("{manifest_dir}/src/bindings.inc.rs") + }; + + let file = Path::new(&file); + if !Path::new(&file).exists() { + panic!(concat!( + "\n", + " No generated C bindings found! Maybe you wanted one of\n", + " `make clippy`, `make rustfmt`, `make rustdoc`?\n", + "\n", + " For other uses of `cargo`, start a subshell with\n", + " `pyvenv/bin/meson devenv`, or point MESON_BUILD_ROOT to\n", + " the top of the build tree." + )); + } + + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = format!("{out_dir}/bindings.inc.rs"); + let dest_path = Path::new(&dest_path); + if dest_path.symlink_metadata().is_ok() { + remove_file(dest_path)?; + } + symlink_file(file, dest_path)?; + + 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..56e929349b --- /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, qemu_api_macros, 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/util/src/error.rs b/rust/util/src/error.rs new file mode 100644 index 0000000000..bfa5a8685b --- /dev/null +++ b/rust/util/src/error.rs @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Error propagation for QEMU Rust code +//! +//! This module contains [`Error`], the bridge between Rust errors and +//! [`Result`](std::result::Result)s and QEMU's C [`Error`](bindings::Error) +//! struct. +//! +//! For FFI code, [`Error`] provides functions to simplify conversion between +//! the Rust ([`Result<>`](std::result::Result)) and C (`Error**`) conventions: +//! +//! * [`ok_or_propagate`](crate::Error::ok_or_propagate), +//! [`bool_or_propagate`](crate::Error::bool_or_propagate), +//! [`ptr_or_propagate`](crate::Error::ptr_or_propagate) can be used to build +//! a C return value while also propagating an error condition +//! +//! * [`err_or_else`](crate::Error::err_or_else) and +//! [`err_or_unit`](crate::Error::err_or_unit) can be used to build a `Result` +//! +//! This module is most commonly used at the boundary between C and Rust code; +//! other code will usually access it through 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. +//! +//! Rust code can also create use this module to create an error object that +//! will be passed up to C code, though in most cases this will be done +//! transparently through the `?` operator. Errors can be constructed from a +//! simple error string, from an [`anyhow::Error`] to pass any other Rust error +//! 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 [`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. + +use std::{ + borrow::Cow, + ffi::{c_char, c_int, c_void, CStr}, + fmt::{self, Display}, + panic, ptr, +}; + +use foreign::{prelude::*, OwnedPointer}; + +use crate::bindings; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct Error { + msg: Option>, + /// Appends the print string of the error to the msg if not None + cause: Option, + file: &'static str, + line: u32, +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.cause.as_ref().map(AsRef::as_ref) + } + + #[allow(deprecated)] + fn description(&self) -> &str { + self.msg + .as_deref() + .or_else(|| self.cause.as_deref().map(std::error::Error::description)) + .expect("no message nor cause?") + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut prefix = ""; + if let Some(ref msg) = self.msg { + write!(f, "{msg}")?; + prefix = ": "; + } + if let Some(ref cause) = self.cause { + write!(f, "{prefix}{cause}")?; + } else if prefix.is_empty() { + panic!("no message nor cause?"); + } + Ok(()) + } +} + +impl From for Error { + #[track_caller] + fn from(msg: String) -> Self { + let location = panic::Location::caller(); + Error { + msg: Some(Cow::Owned(msg)), + cause: None, + file: location.file(), + line: location.line(), + } + } +} + +impl From<&'static str> for Error { + #[track_caller] + fn from(msg: &'static str) -> Self { + let location = panic::Location::caller(); + Error { + msg: Some(Cow::Borrowed(msg)), + cause: None, + file: location.file(), + line: location.line(), + } + } +} + +impl From for Error { + #[track_caller] + fn from(error: anyhow::Error) -> Self { + let location = panic::Location::caller(); + Error { + msg: None, + cause: Some(error), + file: location.file(), + line: location.line(), + } + } +} + +impl Error { + /// Create a new error, prepending `msg` to the + /// description of `cause` + #[track_caller] + pub fn with_error(msg: impl Into>, cause: impl Into) -> Self { + let location = panic::Location::caller(); + Error { + msg: Some(msg.into()), + cause: Some(cause.into()), + file: location.file(), + line: location.line(), + } + } + + /// Consume a result, returning `false` if it is an error and + /// `true` if it is successful. The error is propagated into + /// `errp` like the C API `error_propagate` would do. + /// + /// # Safety + /// + /// `errp` must be a valid argument to `error_propagate`; + /// typically it is received from C code and need not be + /// checked further at the Rust↔C boundary. + pub unsafe fn bool_or_propagate(result: Result<()>, errp: *mut *mut bindings::Error) -> bool { + // SAFETY: caller guarantees errp is valid + unsafe { Self::ok_or_propagate(result, errp) }.is_some() + } + + /// Consume a result, returning a `NULL` pointer if it is an error and + /// a C representation of the contents if it is successful. This is + /// similar to the C API `error_propagate`, but it panics if `*errp` + /// is not `NULL`. + /// + /// # Safety + /// + /// `errp` must be a valid argument to `error_propagate`; + /// typically it is received from C code and need not be + /// checked further at the Rust↔C boundary. + /// + /// See [`propagate`](Error::propagate) for more information. + #[must_use] + pub unsafe fn ptr_or_propagate( + result: Result, + errp: *mut *mut bindings::Error, + ) -> *mut T::Foreign { + // SAFETY: caller guarantees errp is valid + unsafe { Self::ok_or_propagate(result, errp) }.clone_to_foreign_ptr() + } + + /// Consume a result in the same way as `self.ok()`, but also propagate + /// a possible error into `errp`. This is similar to the C API + /// `error_propagate`, but it panics if `*errp` is not `NULL`. + /// + /// # Safety + /// + /// `errp` must be a valid argument to `error_propagate`; + /// typically it is received from C code and need not be + /// checked further at the Rust↔C boundary. + /// + /// See [`propagate`](Error::propagate) for more information. + pub unsafe fn ok_or_propagate( + result: Result, + errp: *mut *mut bindings::Error, + ) -> Option { + result.map_err(|err| unsafe { err.propagate(errp) }).ok() + } + + /// Equivalent of the C function `error_propagate`. Fill `*errp` + /// with the information container in `self` if `errp` is not NULL; + /// then consume it. + /// + /// This is similar to the C API `error_propagate`, but it panics if + /// `*errp` is not `NULL`. + /// + /// # Safety + /// + /// `errp` must be a valid argument to `error_propagate`; it can be + /// `NULL` or it can point to any of: + /// * `error_abort` + /// * `error_fatal` + /// * a local variable of (C) type `Error *` + /// + /// Typically `errp` is received from C code and need not be + /// checked further at the Rust↔C boundary. + pub unsafe fn propagate(self, errp: *mut *mut bindings::Error) { + if errp.is_null() { + return; + } + + // SAFETY: caller guarantees that errp and *errp are valid + unsafe { + assert_eq!(*errp, ptr::null_mut()); + bindings::error_propagate(errp, self.clone_to_foreign_ptr()); + } + } + + /// Convert a C `Error*` into a Rust `Result`, using + /// `Ok(())` if `c_error` is NULL. Free the `Error*`. + /// + /// # Safety + /// + /// `c_error` must be `NULL` or valid; typically it was initialized + /// with `ptr::null_mut()` and passed by reference to a C function. + pub unsafe fn err_or_unit(c_error: *mut bindings::Error) -> Result<()> { + // SAFETY: caller guarantees c_error is valid + unsafe { Self::err_or_else(c_error, || ()) } + } + + /// Convert a C `Error*` into a Rust `Result`, calling `f()` to + /// obtain an `Ok` value if `c_error` is NULL. Free the `Error*`. + /// + /// # Safety + /// + /// `c_error` must be `NULL` or point to a valid C [`struct + /// Error`](bindings::Error); typically it was initialized with + /// `ptr::null_mut()` and passed by reference to a C function. + pub unsafe fn err_or_else T>( + c_error: *mut bindings::Error, + f: F, + ) -> Result { + // SAFETY: caller guarantees c_error is valid + let err = unsafe { Option::::from_foreign(c_error) }; + match err { + None => Ok(f()), + Some(err) => Err(err), + } + } +} + +impl FreeForeign for Error { + type Foreign = bindings::Error; + + unsafe fn free_foreign(p: *mut bindings::Error) { + // SAFETY: caller guarantees p is valid + unsafe { + bindings::error_free(p); + } + } +} + +impl CloneToForeign for Error { + fn clone_to_foreign(&self) -> OwnedPointer { + // SAFETY: all arguments are controlled by this function + unsafe { + let err: *mut c_void = libc::malloc(std::mem::size_of::()); + let err: &mut bindings::Error = &mut *err.cast(); + *err = bindings::Error { + msg: format!("{self}").clone_to_foreign_ptr(), + err_class: bindings::ERROR_CLASS_GENERIC_ERROR, + src_len: self.file.len() as c_int, + src: self.file.as_ptr().cast::(), + line: self.line as c_int, + func: ptr::null_mut(), + hint: ptr::null_mut(), + }; + OwnedPointer::new(err) + } + } +} + +impl FromForeign for Error { + unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self { + // SAFETY: caller guarantees c_error is valid + unsafe { + let error = &*c_error; + let file = if error.src_len < 0 { + // NUL-terminated + CStr::from_ptr(error.src).to_str() + } else { + // Can become str::from_utf8 with Rust 1.87.0 + std::str::from_utf8(std::slice::from_raw_parts( + &*error.src.cast::(), + error.src_len as usize, + )) + }; + + Error { + msg: FromForeign::cloned_from_foreign(error.msg), + cause: None, + file: file.unwrap(), + line: error.line as u32, + } + } + } +} + +#[cfg(test)] +mod tests { + use std::ffi::CStr; + + use anyhow::anyhow; + use common::assert_match; + use foreign::OwnedPointer; + + use super::*; + + #[track_caller] + fn error_for_test(msg: &CStr) -> OwnedPointer { + // SAFETY: all arguments are controlled by this function + let location = panic::Location::caller(); + unsafe { + let err: *mut c_void = libc::malloc(std::mem::size_of::()); + let err: &mut bindings::Error = &mut *err.cast(); + *err = bindings::Error { + msg: msg.clone_to_foreign_ptr(), + err_class: bindings::ERROR_CLASS_GENERIC_ERROR, + src_len: location.file().len() as c_int, + src: location.file().as_ptr().cast::(), + line: location.line() as c_int, + func: ptr::null_mut(), + hint: ptr::null_mut(), + }; + OwnedPointer::new(err) + } + } + + unsafe fn error_get_pretty<'a>(local_err: *mut bindings::Error) -> &'a CStr { + unsafe { CStr::from_ptr(bindings::error_get_pretty(local_err)) } + } + + #[test] + #[allow(deprecated)] + fn test_description() { + use std::error::Error; + + assert_eq!(super::Error::from("msg").description(), "msg"); + assert_eq!(super::Error::from("msg".to_owned()).description(), "msg"); + } + + #[test] + fn test_display() { + assert_eq!(&*format!("{}", Error::from("msg")), "msg"); + assert_eq!(&*format!("{}", Error::from("msg".to_owned())), "msg"); + assert_eq!(&*format!("{}", Error::from(anyhow!("msg"))), "msg"); + + assert_eq!( + &*format!("{}", Error::with_error("msg", anyhow!("cause"))), + "msg: cause" + ); + } + + #[test] + fn test_bool_or_propagate() { + unsafe { + let mut local_err: *mut bindings::Error = ptr::null_mut(); + + assert!(Error::bool_or_propagate(Ok(()), &mut local_err)); + assert_eq!(local_err, ptr::null_mut()); + + let my_err = Error::from("msg"); + assert!(!Error::bool_or_propagate(Err(my_err), &mut local_err)); + assert_ne!(local_err, ptr::null_mut()); + assert_eq!(error_get_pretty(local_err), c"msg"); + bindings::error_free(local_err); + } + } + + #[test] + fn test_ptr_or_propagate() { + unsafe { + let mut local_err: *mut bindings::Error = ptr::null_mut(); + + let ret = Error::ptr_or_propagate(Ok("abc".to_owned()), &mut local_err); + assert_eq!(String::from_foreign(ret), "abc"); + assert_eq!(local_err, ptr::null_mut()); + + let my_err = Error::from("msg"); + assert_eq!( + Error::ptr_or_propagate(Err::(my_err), &mut local_err), + ptr::null_mut() + ); + assert_ne!(local_err, ptr::null_mut()); + assert_eq!(error_get_pretty(local_err), c"msg"); + bindings::error_free(local_err); + } + } + + #[test] + fn test_err_or_unit() { + unsafe { + let result = Error::err_or_unit(ptr::null_mut()); + assert_match!(result, Ok(())); + + let err = error_for_test(c"msg"); + let err = Error::err_or_unit(err.into_inner()).unwrap_err(); + assert_eq!(&*format!("{err}"), "msg"); + } + } +} 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/util/src/log.rs b/rust/util/src/log.rs new file mode 100644 index 0000000000..af9a3e9123 --- /dev/null +++ b/rust/util/src/log.rs @@ -0,0 +1,151 @@ +// Copyright 2025 Bernhard Beschow +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings for QEMU's logging infrastructure + +use std::{ + io::{self, Write}, + ptr::NonNull, +}; + +use common::errno; + +use crate::bindings; + +#[repr(u32)] +/// Represents specific error categories within QEMU's logging system. +/// +/// The `Log` enum provides a Rust abstraction for logging errors, corresponding +/// to a subset of the error categories defined in the C implementation. +pub enum Log { + /// Log invalid access caused by the guest. + /// Corresponds to `LOG_GUEST_ERROR` in the C implementation. + GuestError = bindings::LOG_GUEST_ERROR, + + /// Log guest access of unimplemented functionality. + /// Corresponds to `LOG_UNIMP` in the C implementation. + Unimp = bindings::LOG_UNIMP, +} + +/// A RAII guard for QEMU's logging infrastructure. Creating the guard +/// locks the log file, and dropping it (letting it go out of scope) unlocks +/// the file. +/// +/// As long as the guard lives, it can be written to using [`std::io::Write`]. +/// +/// The locking is recursive, therefore owning a guard does not prevent +/// using [`log_mask_ln!()`](crate::log_mask_ln). +pub struct LogGuard(NonNull); + +impl LogGuard { + /// Return a RAII guard that writes to QEMU's logging infrastructure. + /// The log file is locked while the guard exists, ensuring that there + /// is no tearing of the messages. + /// + /// Return `None` if the log file is closed and could not be opened. + /// Do *not* use `unwrap()` on the result; failure can be handled simply + /// by not logging anything. + /// + /// # Examples + /// + /// ``` + /// # use util::log::LogGuard; + /// # use std::io::Write; + /// if let Some(mut log) = LogGuard::new() { + /// writeln!(log, "test"); + /// } + /// ``` + pub fn new() -> Option { + let f = unsafe { bindings::qemu_log_trylock() }.cast(); + NonNull::new(f).map(Self) + } + + /// Writes a formatted string into the log, returning any error encountered. + /// + /// This method is primarily used by the + /// [`log_mask_ln!()`](crate::log_mask_ln) macro, and it is rare for it + /// to be called explicitly. It is public because it is the only way to + /// examine the error, which `log_mask_ln!()` ignores + /// + /// Unlike `log_mask_ln!()`, it does *not* append a newline at the end. + pub fn log_fmt(args: std::fmt::Arguments) -> io::Result<()> { + if let Some(mut log) = Self::new() { + log.write_fmt(args)?; + } + Ok(()) + } +} + +impl Write for LogGuard { + fn write(&mut self, bytes: &[u8]) -> io::Result { + let ret = unsafe { + bindings::rust_fwrite(bytes.as_ptr().cast(), 1, bytes.len(), self.0.as_ptr()) + }; + errno::into_io_result(ret) + } + + fn flush(&mut self) -> io::Result<()> { + // Do nothing, dropping the guard takes care of flushing + Ok(()) + } +} + +impl Drop for LogGuard { + fn drop(&mut self) { + unsafe { + bindings::qemu_log_unlock(self.0.as_ptr()); + } + } +} + +/// A macro to log messages conditionally based on a provided mask. +/// +/// The `log_mask_ln` macro checks whether the given mask matches the current +/// log level and, if so, formats and logs the message. It is the Rust +/// counterpart of the `qemu_log_mask()` macro in the C implementation. +/// +/// Errors from writing to the log are ignored. +/// +/// # Parameters +/// +/// - `$mask`: A log level mask. This should be a variant of the `Log` enum. +/// - `$fmt`: A format string following the syntax and rules of the `format!` +/// macro. It specifies the structure of the log message. +/// - `$args`: Optional arguments to be interpolated into the format string. +/// +/// # Example +/// +/// ``` +/// use util::{log::Log, log_mask_ln}; +/// +/// let error_address = 0xbad; +/// log_mask_ln!(Log::GuestError, "Address 0x{error_address:x} out of range"); +/// ``` +/// +/// It is also possible to use printf-style formatting, as well as having a +/// trailing `,`: +/// +/// ``` +/// use util::{log::Log, log_mask_ln}; +/// +/// let error_address = 0xbad; +/// log_mask_ln!( +/// Log::GuestError, +/// "Address 0x{:x} out of range", +/// error_address, +/// ); +/// ``` +#[macro_export] +macro_rules! log_mask_ln { + ($mask:expr, $fmt:tt $($args:tt)*) => {{ + // Type assertion to enforce type `Log` for $mask + let _: $crate::log::Log = $mask; + + if unsafe { + ($crate::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 + } { + _ = $crate::log::LogGuard::log_fmt( + format_args!("{}\n", format_args!($fmt $($args)*))); + } + }}; +} diff --git a/rust/util/src/module.rs b/rust/util/src/module.rs new file mode 100644 index 0000000000..06c45fc142 --- /dev/null +++ b/rust/util/src/module.rs @@ -0,0 +1,43 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Macro to register blocks of code that run as QEMU starts up. + +#[macro_export] +macro_rules! module_init { + ($type:ident => $body:block) => { + const _: () = { + #[used] + #[cfg_attr( + not(any(target_vendor = "apple", target_os = "windows")), + link_section = ".init_array" + )] + #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")] + #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] + pub static LOAD_MODULE: extern "C" fn() = { + extern "C" fn init_fn() { + $body + } + + extern "C" fn ctor_fn() { + unsafe { + $crate::bindings::register_module_init( + Some(init_fn), + $crate::bindings::module_init_type::$type, + ); + } + } + + ctor_fn + }; + }; + }; + + // shortcut because it's quite common that $body needs unsafe {} + ($type:ident => unsafe $body:block) => { + ::util::module_init! { + $type => { unsafe { $body } } + } + }; +} diff --git a/rust/util/src/timer.rs b/rust/util/src/timer.rs new file mode 100644 index 0000000000..383e1a6e77 --- /dev/null +++ b/rust/util/src/timer.rs @@ -0,0 +1,125 @@ +// Copyright (C) 2024 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::{ + ffi::{c_int, c_void}, + pin::Pin, +}; + +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)] +pub struct Timer(Opaque); + +unsafe impl Send for Timer {} +unsafe impl Sync for Timer {} + +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct TimerListGroup(Opaque); + +unsafe impl Send for TimerListGroup {} +unsafe impl Sync for TimerListGroup {} + +impl Timer { + pub const MS: u32 = bindings::SCALE_MS; + pub const US: u32 = bindings::SCALE_US; + pub const NS: u32 = bindings::SCALE_NS; + + /// Create a `Timer` struct without initializing it. + /// + /// # Safety + /// + /// The timer must be initialized before it is armed with + /// [`modify`](Self::modify). + pub const unsafe fn new() -> Self { + // SAFETY: requirements relayed to callers of Timer::new + Self(unsafe { Opaque::zeroed() }) + } + + /// Create a new timer with the given attributes. + pub fn init_full<'timer, 'opaque: 'timer, T, F>( + self: Pin<&'timer mut Self>, + timer_list_group: Option<&TimerListGroup>, + clk_type: ClockType, + scale: u32, + attributes: u32, + _cb: F, + opaque: &'opaque T, + ) where + F: for<'a> FnCall<(&'a T,)>, + { + const { assert!(F::IS_SOME) }; + + /// timer expiration callback + unsafe extern "C" fn rust_timer_handler FnCall<(&'a T,)>>( + opaque: *mut c_void, + ) { + // SAFETY: the opaque was passed as a reference to `T`. + F::call((unsafe { &*(opaque.cast::()) },)) + } + + let timer_cb: unsafe extern "C" fn(*mut c_void) = rust_timer_handler::; + + // SAFETY: the opaque outlives the timer + unsafe { + timer_init_full( + self.as_mut_ptr(), + if let Some(g) = timer_list_group { + g as *const TimerListGroup as *mut _ + } else { + ::core::ptr::null_mut() + }, + clk_type.id, + scale as c_int, + attributes as c_int, + Some(timer_cb), + (opaque as *const T).cast::().cast_mut(), + ) + } + } + + pub fn modify(&self, expire_time: u64) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned + unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) } + } + + pub fn delete(&self) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned + unsafe { timer_del(self.as_mut_ptr()) } + } +} + +// FIXME: use something like PinnedDrop from the pinned_init crate +impl Drop for Timer { + fn drop(&mut self) { + self.delete() + } +} + +pub struct ClockType { + id: QEMUClockType, +} + +impl ClockType { + pub fn get_ns(&self) -> u64 { + // SAFETY: cannot be created outside this module, therefore id + // is valid + (unsafe { qemu_clock_get_ns(self.id) }) as u64 + } +} + +pub const CLOCK_VIRTUAL: ClockType = ClockType { + id: QEMUClockType::QEMU_CLOCK_VIRTUAL, +}; + +pub const NANOSECONDS_PER_SECOND: u64 = 1000000000; 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" -- cgit 1.4.1 From d58fcd05ffc682fbad02cd8d0bee840cb7997e3e Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 8 Sep 2025 12:50:00 +0200 Subject: rust: repurpose qemu_api -> tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crate purpose is only to provide integration tests at this point, that can't easily be moved to a specific crate. It's also often a good practice to have a single integration test crate (see for ex https://github.com/rust-lang/cargo/issues/4867) Drop README.md, use docs/devel/rust.rst instead. Signed-off-by: Marc-André Lureau Link: https://lore.kernel.org/r/20250827104147.717203-20-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- MAINTAINERS | 2 +- rust/Cargo.lock | 30 +- rust/Cargo.toml | 2 +- rust/meson.build | 4 +- rust/qemu-api/.gitignore | 2 - rust/qemu-api/Cargo.toml | 28 -- rust/qemu-api/README.md | 19 -- rust/qemu-api/build.rs | 1 - rust/qemu-api/meson.build | 75 ----- rust/qemu-api/src/bindings.rs | 33 --- rust/qemu-api/src/lib.rs | 18 -- rust/qemu-api/src/prelude.rs | 5 - rust/qemu-api/tests/vmstate_tests.rs | 541 ----------------------------------- rust/qemu-api/wrapper.h | 53 ---- rust/tests/Cargo.toml | 27 ++ rust/tests/meson.build | 14 + rust/tests/tests/vmstate_tests.rs | 541 +++++++++++++++++++++++++++++++++++ 17 files changed, 600 insertions(+), 795 deletions(-) delete mode 100644 rust/qemu-api/.gitignore delete mode 100644 rust/qemu-api/Cargo.toml delete mode 100644 rust/qemu-api/README.md delete mode 120000 rust/qemu-api/build.rs delete mode 100644 rust/qemu-api/meson.build delete mode 100644 rust/qemu-api/src/bindings.rs delete mode 100644 rust/qemu-api/src/lib.rs delete mode 100644 rust/qemu-api/src/prelude.rs delete mode 100644 rust/qemu-api/tests/vmstate_tests.rs delete mode 100644 rust/qemu-api/wrapper.h create mode 100644 rust/tests/Cargo.toml create mode 100644 rust/tests/meson.build create mode 100644 rust/tests/tests/vmstate_tests.rs (limited to 'rust/qemu-api/build.rs') diff --git a/MAINTAINERS b/MAINTAINERS index 23bda7d332..05e0597d53 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3520,11 +3520,11 @@ F: rust/chardev/ F: rust/common/ F: rust/hw/core/ F: rust/migration/ -F: rust/qemu-api 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 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2018d13fbf..ac79c6a34a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -187,21 +187,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "qemu_api" -version = "0.1.0" -dependencies = [ - "bql", - "chardev", - "common", - "hwcore", - "migration", - "qemu_macros", - "qom", - "system", - "util", -] - [[package]] name = "qemu_macros" version = "0.1.0" @@ -252,6 +237,21 @@ dependencies = [ "util", ] +[[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "bql", + "chardev", + "common", + "hwcore", + "migration", + "qemu_macros", + "qom", + "system", + "util", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b2a5c230fa..d8183c614d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,13 +6,13 @@ members = [ "common", "migration", "qemu-macros", - "qemu-api", "qom", "system", "hw/core", "hw/char/pl011", "hw/timer/hpet", "util", + "tests", ] [workspace.package] diff --git a/rust/meson.build b/rust/meson.build index 9f6a0b161d..bd9b9cb83e 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -18,8 +18,6 @@ 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('common') @@ -32,7 +30,7 @@ subdir('qom') subdir('system') subdir('chardev') subdir('hw/core') -subdir('qemu-api') +subdir('tests') subdir('hw') 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/Cargo.toml b/rust/qemu-api/Cargo.toml deleted file mode 100644 index 9abb88aa1f..0000000000 --- a/rust/qemu-api/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "qemu_api" -version = "0.1.0" -authors = ["Manos Pitsidianakis "] -description = "Rust bindings for QEMU" -readme = "README.md" -resolver = "2" -publish = false - -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" } -qemu_macros = { path = "../qemu-macros" } -qom = { path = "../qom" } -system = { path = "../system" } - -[lints] -workspace = true 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/build.rs b/rust/qemu-api/build.rs deleted file mode 120000 index 71a3167885..0000000000 --- a/rust/qemu-api/build.rs +++ /dev/null @@ -1 +0,0 @@ -../util/build.rs \ No newline at end of file diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build deleted file mode 100644 index fe81f16d99..0000000000 --- a/rust/qemu-api/meson.build +++ /dev/null @@ -1,75 +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() - -c_enums = [ - 'MemoryDeviceInfoKind', -] -_qemu_api_bindgen_args = [] -foreach enum : c_enums - _qemu_api_bindgen_args += ['--rustified-enum', enum] -endforeach - -blocked_type = [ - 'Chardev', - 'Error', - 'MemTxAttrs', - 'MemoryRegion', - 'ObjectClass', - 'VMStateDescription', - 'device_endian', -] -foreach type: blocked_type - _qemu_api_bindgen_args += ['--blocklist-type', type] -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/bindings.rs', - 'src/prelude.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, bql_rs, chardev_rs, common_rs, foreign_rs, hwcore_rs, libc_rs, migration_rs, qemu_macros, - qom_rs, system_rs, util_rs, hwcore], -) - -qemu_api_rs = declare_dependency(link_with: [_qemu_api_rs], - dependencies: [qemu_macros, qom, hwcore, chardev, migration]) - -test('rust-qemu-api-integration', - executable( - 'rust-qemu-api-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, qemu_api_rs]), - args: [ - '--test', '--test-threads', '1', - '--format', 'pretty', - ], - protocol: 'rust', - suite: ['unit', 'rust']) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs deleted file mode 100644 index 9c863e9b5b..0000000000 --- a/rust/qemu-api/src/bindings.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -use chardev::bindings::Chardev; -use migration::bindings::VMStateDescription; -use qom::bindings::ObjectClass; -use system::bindings::{device_endian, MemTxAttrs, MemoryRegion}; -use util::bindings::Error; - -#[cfg(MESON)] -include!("bindings.inc.rs"); - -#[cfg(not(MESON))] -include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs deleted file mode 100644 index 21b886035f..0000000000 --- a/rust/qemu-api/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// 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; - -// Allow proc-macros to refer to `::qemu_api` inside the `qemu_api` crate (this -// crate). -extern crate self as qemu_api; diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs deleted file mode 100644 index 8db56f9f81..0000000000 --- a/rust/qemu-api/src/prelude.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2024 Red Hat, Inc. -// Author(s): Paolo Bonzini -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Commonly used traits and types for QEMU. diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs deleted file mode 100644 index fa9bbd6a12..0000000000 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ /dev/null @@ -1,541 +0,0 @@ -// Copyright (C) 2025 Intel Corporation. -// Author(s): Zhao Liu -// SPDX-License-Identifier: GPL-2.0-or-later - -use std::{ - ffi::{c_void, CStr}, - mem::size_of, - ptr::NonNull, - slice, -}; - -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, - }, - impl_vmstate_forward, impl_vmstate_struct, - vmstate::{VMStateDescription, VMStateDescriptionBuilder, VMStateField}, - vmstate_fields, vmstate_of, vmstate_unused, vmstate_validate, -}; - -const FOO_ARRAY_MAX: usize = 3; - -// =========================== Test VMSTATE_FOOA =========================== -// Test the use cases of the vmstate macro, corresponding to the following C -// macro variants: -// * VMSTATE_FOOA: -// - VMSTATE_U16 -// - VMSTATE_UNUSED -// - VMSTATE_VARRAY_UINT16_UNSAFE -// - VMSTATE_VARRAY_MULTIPLY -#[repr(C)] -#[derive(Default)] -struct FooA { - arr: [u8; FOO_ARRAY_MAX], - num: u16, - arr_mul: [i8; FOO_ARRAY_MAX], - num_mul: u32, - elem: i8, -} - -static VMSTATE_FOOA: VMStateDescription = VMStateDescriptionBuilder::::new() - .name(c"foo_a") - .version_id(1) - .minimum_version_id(1) - .fields(vmstate_fields! { - vmstate_of!(FooA, elem), - vmstate_unused!(size_of::()), - vmstate_of!(FooA, arr[0 .. num]).with_version_id(0), - vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]), - }) - .build(); - -impl_vmstate_struct!(FooA, VMSTATE_FOOA); - -#[test] -fn test_vmstate_uint16() { - 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!( - unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), - b"elem\0" - ); - assert_eq!(foo_fields[0].offset, 16); - assert_eq!(foo_fields[0].num_offset, 0); - assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int8 }); - assert_eq!(foo_fields[0].version_id, 0); - assert_eq!(foo_fields[0].size, 1); - assert_eq!(foo_fields[0].num, 0); - assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE); - assert!(foo_fields[0].vmsd.is_null()); - assert!(foo_fields[0].field_exists.is_none()); -} - -#[test] -fn test_vmstate_unused() { - 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!( - unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), - b"unused\0" - ); - assert_eq!(foo_fields[1].offset, 0); - assert_eq!(foo_fields[1].num_offset, 0); - assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_unused_buffer }); - assert_eq!(foo_fields[1].version_id, 0); - assert_eq!(foo_fields[1].size, 8); - assert_eq!(foo_fields[1].num, 0); - assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_BUFFER); - assert!(foo_fields[1].vmsd.is_null()); - assert!(foo_fields[1].field_exists.is_none()); -} - -#[test] -fn test_vmstate_varray_uint16_unsafe() { - 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) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), - b"arr\0" - ); - assert_eq!(foo_fields[2].offset, 0); - assert_eq!(foo_fields[2].num_offset, 4); - assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 }); - assert_eq!(foo_fields[2].version_id, 0); - assert_eq!(foo_fields[2].size, 1); - assert_eq!(foo_fields[2].num, 0); - assert_eq!(foo_fields[2].flags, VMStateFlags::VMS_VARRAY_UINT16); - assert!(foo_fields[2].vmsd.is_null()); - assert!(foo_fields[2].field_exists.is_none()); -} - -#[test] -fn test_vmstate_varray_multiply() { - 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) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), - b"arr_mul\0" - ); - assert_eq!(foo_fields[3].offset, 6); - assert_eq!(foo_fields[3].num_offset, 12); - assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_int8 }); - assert_eq!(foo_fields[3].version_id, 0); - assert_eq!(foo_fields[3].size, 1); - assert_eq!(foo_fields[3].num, 16); - assert_eq!( - foo_fields[3].flags.0, - VMStateFlags::VMS_VARRAY_UINT32.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0 - ); - assert!(foo_fields[3].vmsd.is_null()); - assert!(foo_fields[3].field_exists.is_none()); - - // The last VMStateField in VMSTATE_FOOA. - assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END); -} - -// =========================== Test VMSTATE_FOOB =========================== -// Test the use cases of the vmstate macro, corresponding to the following C -// macro variants: -// * VMSTATE_FOOB: -// - VMSTATE_BOOL_V -// - VMSTATE_U64 -// - VMSTATE_STRUCT_VARRAY_UINT8 -// - (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32 -// - VMSTATE_ARRAY -// - VMSTATE_STRUCT_VARRAY_UINT8 with BqlCell wrapper & test_fn -#[repr(C)] -#[derive(Default)] -struct FooB { - arr_a: [FooA; FOO_ARRAY_MAX], - num_a: u8, - arr_a_mul: [FooA; FOO_ARRAY_MAX], - num_a_mul: u32, - wrap: BqlCell, - val: bool, - // FIXME: Use Timer array. Now we can't since it's hard to link savevm.c to test. - arr_i64: [i64; FOO_ARRAY_MAX], - arr_a_wrap: [FooA; FOO_ARRAY_MAX], - num_a_wrap: BqlCell, -} - -fn validate_foob(_state: &FooB, _version_id: u8) -> bool { - true -} - -static VMSTATE_FOOB: VMStateDescription = VMStateDescriptionBuilder::::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_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_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.as_ref().fields, 7) }; - - // 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), - b"val\0" - ); - assert_eq!(foo_fields[0].offset, 136); - assert_eq!(foo_fields[0].num_offset, 0); - assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_bool }); - assert_eq!(foo_fields[0].version_id, 2); - assert_eq!(foo_fields[0].size, 1); - assert_eq!(foo_fields[0].num, 0); - assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE); - assert!(foo_fields[0].vmsd.is_null()); - assert!(foo_fields[0].field_exists.is_none()); -} - -#[test] -fn test_vmstate_uint64() { - 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!( - unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), - b"wrap\0" - ); - assert_eq!(foo_fields[1].offset, 128); - assert_eq!(foo_fields[1].num_offset, 0); - assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_uint64 }); - assert_eq!(foo_fields[1].version_id, 0); - assert_eq!(foo_fields[1].size, 8); - assert_eq!(foo_fields[1].num, 0); - assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_SINGLE); - assert!(foo_fields[1].vmsd.is_null()); - assert!(foo_fields[1].field_exists.is_none()); -} - -#[test] -fn test_vmstate_struct_varray_uint8() { - 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) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), - b"arr_a\0" - ); - assert_eq!(foo_fields[2].offset, 0); - assert_eq!(foo_fields[2].num_offset, 60); - assert!(foo_fields[2].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field. - assert_eq!(foo_fields[2].version_id, 1); - assert_eq!(foo_fields[2].size, 20); - assert_eq!(foo_fields[2].num, 0); - assert_eq!( - foo_fields[2].flags.0, - VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_VARRAY_UINT8.0 - ); - 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.as_ref().fields, 7) }; - - // 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to - // (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), - b"arr_a_mul\0" - ); - assert_eq!(foo_fields[3].offset, 64); - assert_eq!(foo_fields[3].num_offset, 124); - assert!(foo_fields[3].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field. - assert_eq!(foo_fields[3].version_id, 2); - assert_eq!(foo_fields[3].size, 20); - assert_eq!(foo_fields[3].num, 32); - assert_eq!( - foo_fields[3].flags.0, - VMStateFlags::VMS_STRUCT.0 - | VMStateFlags::VMS_VARRAY_UINT32.0 - | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0 - ); - 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.as_ref().fields, 7) }; - - // 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to - // VMSTATE_ARRAY) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[4].name) }.to_bytes_with_nul(), - b"arr_i64\0" - ); - assert_eq!(foo_fields[4].offset, 144); - assert_eq!(foo_fields[4].num_offset, 0); - assert_eq!(foo_fields[4].info, unsafe { &vmstate_info_int64 }); - assert_eq!(foo_fields[4].version_id, 0); - assert_eq!(foo_fields[4].size, 8); - assert_eq!(foo_fields[4].num, FOO_ARRAY_MAX as i32); - assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_ARRAY); - assert!(foo_fields[4].vmsd.is_null()); - assert!(foo_fields[4].field_exists.is_none()); -} - -#[test] -fn test_vmstate_struct_varray_uint8_wrapper() { - 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::(); - - // 6th VMStateField ("arr_a_wrap") in VMSTATE_FOOB (corresponding to - // VMSTATE_STRUCT_VARRAY_UINT8). Other fields are checked in - // test_vmstate_struct_varray_uint8. - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[5].name) }.to_bytes_with_nul(), - b"arr_a_wrap\0" - ); - assert_eq!(foo_fields[5].num_offset, 228); - assert!(unsafe { foo_fields[5].field_exists.unwrap()(foo_b_p, 0) }); - - // The last VMStateField in VMSTATE_FOOB. - assert_eq!(foo_fields[6].flags, VMStateFlags::VMS_END); -} - -// =========================== Test VMSTATE_FOOC =========================== -// Test the use cases of the vmstate macro, corresponding to the following C -// macro variants: -// * VMSTATE_FOOC: -// - VMSTATE_POINTER -// - VMSTATE_ARRAY_OF_POINTER -struct FooCWrapper([Opaque<*mut u8>; FOO_ARRAY_MAX]); // Though Opaque<> array is almost impossible. - -impl_vmstate_forward!(FooCWrapper); - -#[repr(C)] -struct FooC { - ptr: *const i32, - ptr_a: NonNull, - arr_ptr: [Box; FOO_ARRAY_MAX], - arr_ptr_wrap: FooCWrapper, -} - -unsafe impl Sync for FooC {} - -static VMSTATE_FOOC: VMStateDescription = VMStateDescriptionBuilder::::new() - .name(c"foo_c") - .version_id(3) - .minimum_version_id(1) - .fields(vmstate_fields! { - vmstate_of!(FooC, ptr).with_version_id(2), - vmstate_of!(FooC, ptr_a), - vmstate_of!(FooC, arr_ptr), - vmstate_of!(FooC, arr_ptr_wrap), - }) - .build(); - -const PTR_SIZE: usize = size_of::<*mut ()>(); - -#[test] -fn test_vmstate_pointer() { - 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!( - unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), - b"ptr\0" - ); - assert_eq!(foo_fields[0].offset, 0); - assert_eq!(foo_fields[0].num_offset, 0); - assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int32 }); - assert_eq!(foo_fields[0].version_id, 2); - assert_eq!(foo_fields[0].size, 4); - assert_eq!(foo_fields[0].num, 0); - assert_eq!( - foo_fields[0].flags.0, - VMStateFlags::VMS_SINGLE.0 | VMStateFlags::VMS_POINTER.0 - ); - assert!(foo_fields[0].vmsd.is_null()); - assert!(foo_fields[0].field_exists.is_none()); -} - -#[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::()); - 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.as_ref().fields, 6) }; - - // 3rd VMStateField ("arr_ptr") in VMSTATE_FOOC (corresponding to - // VMSTATE_ARRAY_OF_POINTER) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), - b"arr_ptr\0" - ); - assert_eq!(foo_fields[2].offset, 2 * PTR_SIZE); - assert_eq!(foo_fields[2].num_offset, 0); - assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 }); - assert_eq!(foo_fields[2].version_id, 0); - assert_eq!(foo_fields[2].size, PTR_SIZE); - assert_eq!(foo_fields[2].num, FOO_ARRAY_MAX as i32); - assert_eq!( - foo_fields[2].flags.0, - VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0 - ); - assert!(foo_fields[2].vmsd.is_null()); - assert!(foo_fields[2].field_exists.is_none()); -} - -#[test] -fn test_vmstate_macro_array_of_pointer_wrapped() { - 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) - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), - b"arr_ptr_wrap\0" - ); - assert_eq!(foo_fields[3].offset, (FOO_ARRAY_MAX + 2) * PTR_SIZE); - assert_eq!(foo_fields[3].num_offset, 0); - assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_uint8 }); - assert_eq!(foo_fields[3].version_id, 0); - assert_eq!(foo_fields[3].size, PTR_SIZE); - assert_eq!(foo_fields[3].num, FOO_ARRAY_MAX as i32); - assert_eq!( - foo_fields[3].flags.0, - VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0 - ); - assert!(foo_fields[3].vmsd.is_null()); - assert!(foo_fields[3].field_exists.is_none()); - - // The last VMStateField in VMSTATE_FOOC. - assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END); -} - -// =========================== Test VMSTATE_FOOD =========================== -// Test the use cases of the vmstate macro, corresponding to the following C -// macro variants: -// * VMSTATE_FOOD: -// - VMSTATE_VALIDATE - -// Add more member fields when vmstate_of support "test" parameter. -struct FooD; - -impl FooD { - fn validate_food_0(&self, _version_id: u8) -> bool { - true - } - - fn validate_food_1(_state: &FooD, _version_id: u8) -> bool { - false - } -} - -fn validate_food_2(_state: &FooD, _version_id: u8) -> bool { - true -} - -static VMSTATE_FOOD: VMStateDescription = VMStateDescriptionBuilder::::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), - }) - .build(); - -#[test] -fn test_vmstate_validate() { - 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::(); - - // 1st VMStateField in VMSTATE_FOOD - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), - b"foo_d_0\0" - ); - assert_eq!(foo_fields[0].offset, 0); - assert_eq!(foo_fields[0].num_offset, 0); - assert!(foo_fields[0].info.is_null()); - assert_eq!(foo_fields[0].version_id, 0); - assert_eq!(foo_fields[0].size, 0); - assert_eq!(foo_fields[0].num, 0); - assert_eq!( - foo_fields[0].flags.0, - VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_MUST_EXIST.0 - ); - assert!(foo_fields[0].vmsd.is_null()); - assert!(unsafe { foo_fields[0].field_exists.unwrap()(foo_d_p, 0) }); - - // 2nd VMStateField in VMSTATE_FOOD - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), - b"foo_d_1\0" - ); - assert!(!unsafe { foo_fields[1].field_exists.unwrap()(foo_d_p, 1) }); - - // 3rd VMStateField in VMSTATE_FOOD - assert_eq!( - unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), - b"foo_d_2\0" - ); - assert!(unsafe { foo_fields[2].field_exists.unwrap()(foo_d_p, 2) }); - - // The last VMStateField in VMSTATE_FOOD. - assert_eq!(foo_fields[3].flags, VMStateFlags::VMS_END); -} diff --git a/rust/qemu-api/wrapper.h b/rust/qemu-api/wrapper.h deleted file mode 100644 index 7c9c20b14f..0000000000 --- a/rust/qemu-api/wrapper.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * QEMU System Emulator - * - * Copyright (c) 2024 Linaro Ltd. - * - * Authors: Manos Pitsidianakis - * - * 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 "qemu-io.h" -#include "exec/memattrs.h" -#include "hw/char/pl011.h" diff --git a/rust/tests/Cargo.toml b/rust/tests/Cargo.toml new file mode 100644 index 0000000000..8d106d896d --- /dev/null +++ b/rust/tests/Cargo.toml @@ -0,0 +1,27 @@ +[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" } +qemu_macros = { path = "../qemu-macros" } +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/tests/tests/vmstate_tests.rs b/rust/tests/tests/vmstate_tests.rs new file mode 100644 index 0000000000..fa9bbd6a12 --- /dev/null +++ b/rust/tests/tests/vmstate_tests.rs @@ -0,0 +1,541 @@ +// Copyright (C) 2025 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::{ + ffi::{c_void, CStr}, + mem::size_of, + ptr::NonNull, + slice, +}; + +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, + }, + impl_vmstate_forward, impl_vmstate_struct, + vmstate::{VMStateDescription, VMStateDescriptionBuilder, VMStateField}, + vmstate_fields, vmstate_of, vmstate_unused, vmstate_validate, +}; + +const FOO_ARRAY_MAX: usize = 3; + +// =========================== Test VMSTATE_FOOA =========================== +// Test the use cases of the vmstate macro, corresponding to the following C +// macro variants: +// * VMSTATE_FOOA: +// - VMSTATE_U16 +// - VMSTATE_UNUSED +// - VMSTATE_VARRAY_UINT16_UNSAFE +// - VMSTATE_VARRAY_MULTIPLY +#[repr(C)] +#[derive(Default)] +struct FooA { + arr: [u8; FOO_ARRAY_MAX], + num: u16, + arr_mul: [i8; FOO_ARRAY_MAX], + num_mul: u32, + elem: i8, +} + +static VMSTATE_FOOA: VMStateDescription = VMStateDescriptionBuilder::::new() + .name(c"foo_a") + .version_id(1) + .minimum_version_id(1) + .fields(vmstate_fields! { + vmstate_of!(FooA, elem), + vmstate_unused!(size_of::()), + vmstate_of!(FooA, arr[0 .. num]).with_version_id(0), + vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]), + }) + .build(); + +impl_vmstate_struct!(FooA, VMSTATE_FOOA); + +#[test] +fn test_vmstate_uint16() { + 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!( + unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), + b"elem\0" + ); + assert_eq!(foo_fields[0].offset, 16); + assert_eq!(foo_fields[0].num_offset, 0); + assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int8 }); + assert_eq!(foo_fields[0].version_id, 0); + assert_eq!(foo_fields[0].size, 1); + assert_eq!(foo_fields[0].num, 0); + assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE); + assert!(foo_fields[0].vmsd.is_null()); + assert!(foo_fields[0].field_exists.is_none()); +} + +#[test] +fn test_vmstate_unused() { + 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!( + unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), + b"unused\0" + ); + assert_eq!(foo_fields[1].offset, 0); + assert_eq!(foo_fields[1].num_offset, 0); + assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_unused_buffer }); + assert_eq!(foo_fields[1].version_id, 0); + assert_eq!(foo_fields[1].size, 8); + assert_eq!(foo_fields[1].num, 0); + assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_BUFFER); + assert!(foo_fields[1].vmsd.is_null()); + assert!(foo_fields[1].field_exists.is_none()); +} + +#[test] +fn test_vmstate_varray_uint16_unsafe() { + 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) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), + b"arr\0" + ); + assert_eq!(foo_fields[2].offset, 0); + assert_eq!(foo_fields[2].num_offset, 4); + assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 }); + assert_eq!(foo_fields[2].version_id, 0); + assert_eq!(foo_fields[2].size, 1); + assert_eq!(foo_fields[2].num, 0); + assert_eq!(foo_fields[2].flags, VMStateFlags::VMS_VARRAY_UINT16); + assert!(foo_fields[2].vmsd.is_null()); + assert!(foo_fields[2].field_exists.is_none()); +} + +#[test] +fn test_vmstate_varray_multiply() { + 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) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), + b"arr_mul\0" + ); + assert_eq!(foo_fields[3].offset, 6); + assert_eq!(foo_fields[3].num_offset, 12); + assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_int8 }); + assert_eq!(foo_fields[3].version_id, 0); + assert_eq!(foo_fields[3].size, 1); + assert_eq!(foo_fields[3].num, 16); + assert_eq!( + foo_fields[3].flags.0, + VMStateFlags::VMS_VARRAY_UINT32.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0 + ); + assert!(foo_fields[3].vmsd.is_null()); + assert!(foo_fields[3].field_exists.is_none()); + + // The last VMStateField in VMSTATE_FOOA. + assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END); +} + +// =========================== Test VMSTATE_FOOB =========================== +// Test the use cases of the vmstate macro, corresponding to the following C +// macro variants: +// * VMSTATE_FOOB: +// - VMSTATE_BOOL_V +// - VMSTATE_U64 +// - VMSTATE_STRUCT_VARRAY_UINT8 +// - (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32 +// - VMSTATE_ARRAY +// - VMSTATE_STRUCT_VARRAY_UINT8 with BqlCell wrapper & test_fn +#[repr(C)] +#[derive(Default)] +struct FooB { + arr_a: [FooA; FOO_ARRAY_MAX], + num_a: u8, + arr_a_mul: [FooA; FOO_ARRAY_MAX], + num_a_mul: u32, + wrap: BqlCell, + val: bool, + // FIXME: Use Timer array. Now we can't since it's hard to link savevm.c to test. + arr_i64: [i64; FOO_ARRAY_MAX], + arr_a_wrap: [FooA; FOO_ARRAY_MAX], + num_a_wrap: BqlCell, +} + +fn validate_foob(_state: &FooB, _version_id: u8) -> bool { + true +} + +static VMSTATE_FOOB: VMStateDescription = VMStateDescriptionBuilder::::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_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_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.as_ref().fields, 7) }; + + // 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), + b"val\0" + ); + assert_eq!(foo_fields[0].offset, 136); + assert_eq!(foo_fields[0].num_offset, 0); + assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_bool }); + assert_eq!(foo_fields[0].version_id, 2); + assert_eq!(foo_fields[0].size, 1); + assert_eq!(foo_fields[0].num, 0); + assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE); + assert!(foo_fields[0].vmsd.is_null()); + assert!(foo_fields[0].field_exists.is_none()); +} + +#[test] +fn test_vmstate_uint64() { + 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!( + unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), + b"wrap\0" + ); + assert_eq!(foo_fields[1].offset, 128); + assert_eq!(foo_fields[1].num_offset, 0); + assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_uint64 }); + assert_eq!(foo_fields[1].version_id, 0); + assert_eq!(foo_fields[1].size, 8); + assert_eq!(foo_fields[1].num, 0); + assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_SINGLE); + assert!(foo_fields[1].vmsd.is_null()); + assert!(foo_fields[1].field_exists.is_none()); +} + +#[test] +fn test_vmstate_struct_varray_uint8() { + 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) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), + b"arr_a\0" + ); + assert_eq!(foo_fields[2].offset, 0); + assert_eq!(foo_fields[2].num_offset, 60); + assert!(foo_fields[2].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field. + assert_eq!(foo_fields[2].version_id, 1); + assert_eq!(foo_fields[2].size, 20); + assert_eq!(foo_fields[2].num, 0); + assert_eq!( + foo_fields[2].flags.0, + VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_VARRAY_UINT8.0 + ); + 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.as_ref().fields, 7) }; + + // 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to + // (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), + b"arr_a_mul\0" + ); + assert_eq!(foo_fields[3].offset, 64); + assert_eq!(foo_fields[3].num_offset, 124); + assert!(foo_fields[3].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field. + assert_eq!(foo_fields[3].version_id, 2); + assert_eq!(foo_fields[3].size, 20); + assert_eq!(foo_fields[3].num, 32); + assert_eq!( + foo_fields[3].flags.0, + VMStateFlags::VMS_STRUCT.0 + | VMStateFlags::VMS_VARRAY_UINT32.0 + | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0 + ); + 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.as_ref().fields, 7) }; + + // 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to + // VMSTATE_ARRAY) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[4].name) }.to_bytes_with_nul(), + b"arr_i64\0" + ); + assert_eq!(foo_fields[4].offset, 144); + assert_eq!(foo_fields[4].num_offset, 0); + assert_eq!(foo_fields[4].info, unsafe { &vmstate_info_int64 }); + assert_eq!(foo_fields[4].version_id, 0); + assert_eq!(foo_fields[4].size, 8); + assert_eq!(foo_fields[4].num, FOO_ARRAY_MAX as i32); + assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_ARRAY); + assert!(foo_fields[4].vmsd.is_null()); + assert!(foo_fields[4].field_exists.is_none()); +} + +#[test] +fn test_vmstate_struct_varray_uint8_wrapper() { + 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::(); + + // 6th VMStateField ("arr_a_wrap") in VMSTATE_FOOB (corresponding to + // VMSTATE_STRUCT_VARRAY_UINT8). Other fields are checked in + // test_vmstate_struct_varray_uint8. + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[5].name) }.to_bytes_with_nul(), + b"arr_a_wrap\0" + ); + assert_eq!(foo_fields[5].num_offset, 228); + assert!(unsafe { foo_fields[5].field_exists.unwrap()(foo_b_p, 0) }); + + // The last VMStateField in VMSTATE_FOOB. + assert_eq!(foo_fields[6].flags, VMStateFlags::VMS_END); +} + +// =========================== Test VMSTATE_FOOC =========================== +// Test the use cases of the vmstate macro, corresponding to the following C +// macro variants: +// * VMSTATE_FOOC: +// - VMSTATE_POINTER +// - VMSTATE_ARRAY_OF_POINTER +struct FooCWrapper([Opaque<*mut u8>; FOO_ARRAY_MAX]); // Though Opaque<> array is almost impossible. + +impl_vmstate_forward!(FooCWrapper); + +#[repr(C)] +struct FooC { + ptr: *const i32, + ptr_a: NonNull, + arr_ptr: [Box; FOO_ARRAY_MAX], + arr_ptr_wrap: FooCWrapper, +} + +unsafe impl Sync for FooC {} + +static VMSTATE_FOOC: VMStateDescription = VMStateDescriptionBuilder::::new() + .name(c"foo_c") + .version_id(3) + .minimum_version_id(1) + .fields(vmstate_fields! { + vmstate_of!(FooC, ptr).with_version_id(2), + vmstate_of!(FooC, ptr_a), + vmstate_of!(FooC, arr_ptr), + vmstate_of!(FooC, arr_ptr_wrap), + }) + .build(); + +const PTR_SIZE: usize = size_of::<*mut ()>(); + +#[test] +fn test_vmstate_pointer() { + 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!( + unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), + b"ptr\0" + ); + assert_eq!(foo_fields[0].offset, 0); + assert_eq!(foo_fields[0].num_offset, 0); + assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int32 }); + assert_eq!(foo_fields[0].version_id, 2); + assert_eq!(foo_fields[0].size, 4); + assert_eq!(foo_fields[0].num, 0); + assert_eq!( + foo_fields[0].flags.0, + VMStateFlags::VMS_SINGLE.0 | VMStateFlags::VMS_POINTER.0 + ); + assert!(foo_fields[0].vmsd.is_null()); + assert!(foo_fields[0].field_exists.is_none()); +} + +#[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::()); + 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.as_ref().fields, 6) }; + + // 3rd VMStateField ("arr_ptr") in VMSTATE_FOOC (corresponding to + // VMSTATE_ARRAY_OF_POINTER) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), + b"arr_ptr\0" + ); + assert_eq!(foo_fields[2].offset, 2 * PTR_SIZE); + assert_eq!(foo_fields[2].num_offset, 0); + assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 }); + assert_eq!(foo_fields[2].version_id, 0); + assert_eq!(foo_fields[2].size, PTR_SIZE); + assert_eq!(foo_fields[2].num, FOO_ARRAY_MAX as i32); + assert_eq!( + foo_fields[2].flags.0, + VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0 + ); + assert!(foo_fields[2].vmsd.is_null()); + assert!(foo_fields[2].field_exists.is_none()); +} + +#[test] +fn test_vmstate_macro_array_of_pointer_wrapped() { + 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) + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(), + b"arr_ptr_wrap\0" + ); + assert_eq!(foo_fields[3].offset, (FOO_ARRAY_MAX + 2) * PTR_SIZE); + assert_eq!(foo_fields[3].num_offset, 0); + assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_uint8 }); + assert_eq!(foo_fields[3].version_id, 0); + assert_eq!(foo_fields[3].size, PTR_SIZE); + assert_eq!(foo_fields[3].num, FOO_ARRAY_MAX as i32); + assert_eq!( + foo_fields[3].flags.0, + VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0 + ); + assert!(foo_fields[3].vmsd.is_null()); + assert!(foo_fields[3].field_exists.is_none()); + + // The last VMStateField in VMSTATE_FOOC. + assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END); +} + +// =========================== Test VMSTATE_FOOD =========================== +// Test the use cases of the vmstate macro, corresponding to the following C +// macro variants: +// * VMSTATE_FOOD: +// - VMSTATE_VALIDATE + +// Add more member fields when vmstate_of support "test" parameter. +struct FooD; + +impl FooD { + fn validate_food_0(&self, _version_id: u8) -> bool { + true + } + + fn validate_food_1(_state: &FooD, _version_id: u8) -> bool { + false + } +} + +fn validate_food_2(_state: &FooD, _version_id: u8) -> bool { + true +} + +static VMSTATE_FOOD: VMStateDescription = VMStateDescriptionBuilder::::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), + }) + .build(); + +#[test] +fn test_vmstate_validate() { + 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::(); + + // 1st VMStateField in VMSTATE_FOOD + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(), + b"foo_d_0\0" + ); + assert_eq!(foo_fields[0].offset, 0); + assert_eq!(foo_fields[0].num_offset, 0); + assert!(foo_fields[0].info.is_null()); + assert_eq!(foo_fields[0].version_id, 0); + assert_eq!(foo_fields[0].size, 0); + assert_eq!(foo_fields[0].num, 0); + assert_eq!( + foo_fields[0].flags.0, + VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_MUST_EXIST.0 + ); + assert!(foo_fields[0].vmsd.is_null()); + assert!(unsafe { foo_fields[0].field_exists.unwrap()(foo_d_p, 0) }); + + // 2nd VMStateField in VMSTATE_FOOD + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(), + b"foo_d_1\0" + ); + assert!(!unsafe { foo_fields[1].field_exists.unwrap()(foo_d_p, 1) }); + + // 3rd VMStateField in VMSTATE_FOOD + assert_eq!( + unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(), + b"foo_d_2\0" + ); + assert!(unsafe { foo_fields[2].field_exists.unwrap()(foo_d_p, 2) }); + + // The last VMStateField in VMSTATE_FOOD. + assert_eq!(foo_fields[3].flags, VMStateFlags::VMS_END); +} -- cgit 1.4.1