diff options
Diffstat (limited to 'rust')
30 files changed, 1361 insertions, 181 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 13d580c..b785c71 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3,6 +3,12 @@ version = 3 [[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] name = "arbitrary-int" version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -32,12 +38,28 @@ dependencies = [ ] [[package]] +name = "bits" +version = "0.1.0" +dependencies = [ + "qemu_api_macros", +] + +[[package]] name = "either" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] +name = "foreign" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ca1b5be8c9d320daf386f1809c7acc0cb09accbae795c2001953fa50585846" +dependencies = [ + "libc", +] + +[[package]] name = "hpet" version = "0.1.0" dependencies = [ @@ -66,6 +88,7 @@ version = "0.1.0" dependencies = [ "bilge", "bilge-impl", + "bits", "qemu_api", "qemu_api_macros", ] @@ -106,6 +129,8 @@ dependencies = [ name = "qemu_api" version = "0.1.0" dependencies = [ + "anyhow", + "foreign", "libc", "qemu_api_macros", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index d9faeec..0868e1b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "bits", "qemu-api-macros", "qemu-api", "hw/char/pl011", @@ -63,10 +64,10 @@ ignored_unit_patterns = "deny" implicit_clone = "deny" macro_use_imports = "deny" missing_safety_doc = "deny" -multiple_crate_versions = "deny" mut_mut = "deny" needless_bitwise_bool = "deny" needless_pass_by_ref_mut = "deny" +needless_update = "deny" no_effect_underscore_binding = "deny" option_option = "deny" or_fun_call = "deny" diff --git a/rust/bits/Cargo.toml b/rust/bits/Cargo.toml new file mode 100644 index 0000000..1ff38a4 --- /dev/null +++ b/rust/bits/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bits" +version = "0.1.0" +authors = ["Paolo Bonzini <pbonzini@redhat.com>"] +description = "const-friendly bit flags" +resolver = "2" +publish = false + +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +qemu_api_macros = { path = "../qemu-api-macros" } + +[lints] +workspace = true diff --git a/rust/bits/meson.build b/rust/bits/meson.build new file mode 100644 index 0000000..2a41e13 --- /dev/null +++ b/rust/bits/meson.build @@ -0,0 +1,16 @@ +_bits_rs = static_library( + 'bits', + 'src/lib.rs', + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + dependencies: [qemu_api_macros], +) + +bits_rs = declare_dependency(link_with: _bits_rs) + +rust.test('rust-bits-tests', _bits_rs, + suite: ['unit', 'rust']) + +rust.doctest('rust-bits-doctests', _bits_rs, + dependencies: bits_rs, + suite: ['doc', 'rust']) diff --git a/rust/bits/src/lib.rs b/rust/bits/src/lib.rs new file mode 100644 index 0000000..d485d6b --- /dev/null +++ b/rust/bits/src/lib.rs @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 or GPL-2.0-or-later + +/// # Definition entry point +/// +/// Define a struct with a single field of type $type. Include public constants +/// for each element listed in braces. +/// +/// The unnamed element at the end, if present, can be used to enlarge the set +/// of valid bits. Bits that are valid but not listed are treated normally for +/// the purpose of arithmetic operations, and are printed with their hexadecimal +/// value. +/// +/// The struct implements the following traits: [`BitAnd`](std::ops::BitAnd), +/// [`BitOr`](std::ops::BitOr), [`BitXor`](std::ops::BitXor), +/// [`Not`](std::ops::Not), [`Sub`](std::ops::Sub); [`Debug`](std::fmt::Debug), +/// [`Display`](std::fmt::Display), [`Binary`](std::fmt::Binary), +/// [`Octal`](std::fmt::Octal), [`LowerHex`](std::fmt::LowerHex), +/// [`UpperHex`](std::fmt::UpperHex); [`From`]`<type>`/[`Into`]`<type>` where +/// type is the type specified in the definition. +/// +/// ## Example +/// +/// ``` +/// # use bits::bits; +/// bits! { +/// pub struct Colors(u8) { +/// BLACK = 0, +/// RED = 1, +/// GREEN = 1 << 1, +/// BLUE = 1 << 2, +/// WHITE = (1 << 0) | (1 << 1) | (1 << 2), +/// } +/// } +/// ``` +/// +/// ``` +/// # use bits::bits; +/// # bits! { pub struct Colors(u8) { BLACK = 0, RED = 1, GREEN = 1 << 1, BLUE = 1 << 2, } } +/// +/// bits! { +/// pub struct Colors8(u8) { +/// BLACK = 0, +/// RED = 1, +/// GREEN = 1 << 1, +/// BLUE = 1 << 2, +/// WHITE = (1 << 0) | (1 << 1) | (1 << 2), +/// +/// _ = 255, +/// } +/// } +/// +/// // The previously defined struct ignores bits not explicitly defined. +/// assert_eq!( +/// Colors::from(255).into_bits(), +/// (Colors::RED | Colors::GREEN | Colors::BLUE).into_bits() +/// ); +/// +/// // Adding "_ = 255" makes it retain other bits as well. +/// assert_eq!(Colors8::from(255).into_bits(), 255); +/// +/// // all() does not include the additional bits, valid_bits() does +/// assert_eq!(Colors8::all().into_bits(), Colors::all().into_bits()); +/// assert_eq!(Colors8::valid_bits().into_bits(), 255); +/// ``` +/// +/// # Evaluation entry point +/// +/// Return a constant corresponding to the boolean expression `$expr`. +/// Identifiers in the expression correspond to values defined for the +/// type `$type`. Supported operators are `!` (unary), `-`, `&`, `^`, `|`. +/// +/// ## Examples +/// +/// ``` +/// # use bits::bits; +/// bits! { +/// pub struct Colors(u8) { +/// BLACK = 0, +/// RED = 1, +/// GREEN = 1 << 1, +/// BLUE = 1 << 2, +/// // same as "WHITE = 7", +/// WHITE = bits!(Self as u8: RED | GREEN | BLUE), +/// } +/// } +/// +/// let rgb = bits! { Colors: RED | GREEN | BLUE }; +/// assert_eq!(rgb, Colors::WHITE); +/// ``` +#[macro_export] +macro_rules! bits { + { + $(#[$struct_meta:meta])* + $struct_vis:vis struct $struct_name:ident($field_vis:vis $type:ty) { + $($(#[$const_meta:meta])* $const:ident = $val:expr),+ + $(,_ = $mask:expr)? + $(,)? + } + } => { + $(#[$struct_meta])* + #[derive(Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + $struct_vis struct $struct_name($field_vis $type); + + impl $struct_name { + $( #[allow(dead_code)] $(#[$const_meta])* + pub const $const: $struct_name = $struct_name($val); )+ + + #[doc(hidden)] + const VALID__: $type = $( Self::$const.0 )|+ $(|$mask)?; + + #[allow(dead_code)] + #[inline(always)] + pub const fn empty() -> Self { + Self(0) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn all() -> Self { + Self($( Self::$const.0 )|+) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn valid_bits() -> Self { + Self(Self::VALID__) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn valid(val: $type) -> bool { + (val & !Self::VALID__) == 0 + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn any_set(self, mask: Self) -> bool { + (self.0 & mask.0) != 0 + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn all_set(self, mask: Self) -> bool { + (self.0 & mask.0) == mask.0 + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn none_set(self, mask: Self) -> bool { + (self.0 & mask.0) == 0 + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn from_bits(value: $type) -> Self { + $struct_name(value) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn into_bits(self) -> $type { + self.0 + } + + #[allow(dead_code)] + #[inline(always)] + pub fn set(&mut self, rhs: Self) { + self.0 |= rhs.0; + } + + #[allow(dead_code)] + #[inline(always)] + pub fn clear(&mut self, rhs: Self) { + self.0 &= !rhs.0; + } + + #[allow(dead_code)] + #[inline(always)] + pub fn toggle(&mut self, rhs: Self) { + self.0 ^= rhs.0; + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn intersection(self, rhs: Self) -> Self { + $struct_name(self.0 & rhs.0) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn difference(self, rhs: Self) -> Self { + $struct_name(self.0 & !rhs.0) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn symmetric_difference(self, rhs: Self) -> Self { + $struct_name(self.0 ^ rhs.0) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn union(self, rhs: Self) -> Self { + $struct_name(self.0 | rhs.0) + } + + #[allow(dead_code)] + #[inline(always)] + pub const fn invert(self) -> Self { + $struct_name(self.0 ^ Self::VALID__) + } + } + + impl ::std::fmt::Binary for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + // If no width, use the highest valid bit + let width = f.width().unwrap_or((Self::VALID__.ilog2() + 1) as usize); + write!(f, "{:0>width$.precision$b}", self.0, + width = width, + precision = f.precision().unwrap_or(width)) + } + } + + impl ::std::fmt::LowerHex for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + <$type as ::std::fmt::LowerHex>::fmt(&self.0, f) + } + } + + impl ::std::fmt::Octal for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + <$type as ::std::fmt::Octal>::fmt(&self.0, f) + } + } + + impl ::std::fmt::UpperHex for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + <$type as ::std::fmt::UpperHex>::fmt(&self.0, f) + } + } + + impl ::std::fmt::Debug for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "{}({})", stringify!($struct_name), self) + } + } + + impl ::std::fmt::Display for $struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + use ::std::fmt::Display; + let mut first = true; + let mut left = self.0; + $(if Self::$const.0.is_power_of_two() && (self & Self::$const).0 != 0 { + if first { first = false } else { Display::fmt(&'|', f)?; } + Display::fmt(stringify!($const), f)?; + left -= Self::$const.0; + })+ + if first { + Display::fmt(&'0', f) + } else if left != 0 { + write!(f, "|{left:#x}") + } else { + Ok(()) + } + } + } + + impl ::std::cmp::PartialEq<$type> for $struct_name { + fn eq(&self, rhs: &$type) -> bool { + self.0 == *rhs + } + } + + impl ::std::ops::BitAnd<$struct_name> for &$struct_name { + type Output = $struct_name; + fn bitand(self, rhs: $struct_name) -> Self::Output { + $struct_name(self.0 & rhs.0) + } + } + + impl ::std::ops::BitAndAssign<$struct_name> for $struct_name { + fn bitand_assign(&mut self, rhs: $struct_name) { + self.0 = self.0 & rhs.0 + } + } + + impl ::std::ops::BitXor<$struct_name> for &$struct_name { + type Output = $struct_name; + fn bitxor(self, rhs: $struct_name) -> Self::Output { + $struct_name(self.0 ^ rhs.0) + } + } + + impl ::std::ops::BitXorAssign<$struct_name> for $struct_name { + fn bitxor_assign(&mut self, rhs: $struct_name) { + self.0 = self.0 ^ rhs.0 + } + } + + impl ::std::ops::BitOr<$struct_name> for &$struct_name { + type Output = $struct_name; + fn bitor(self, rhs: $struct_name) -> Self::Output { + $struct_name(self.0 | rhs.0) + } + } + + impl ::std::ops::BitOrAssign<$struct_name> for $struct_name { + fn bitor_assign(&mut self, rhs: $struct_name) { + self.0 = self.0 | rhs.0 + } + } + + impl ::std::ops::Sub<$struct_name> for &$struct_name { + type Output = $struct_name; + fn sub(self, rhs: $struct_name) -> Self::Output { + $struct_name(self.0 & !rhs.0) + } + } + + impl ::std::ops::SubAssign<$struct_name> for $struct_name { + fn sub_assign(&mut self, rhs: $struct_name) { + self.0 = self.0 - rhs.0 + } + } + + impl ::std::ops::Not for &$struct_name { + type Output = $struct_name; + fn not(self) -> Self::Output { + $struct_name(self.0 ^ $struct_name::VALID__) + } + } + + impl ::std::ops::BitAnd<$struct_name> for $struct_name { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + $struct_name(self.0 & rhs.0) + } + } + + impl ::std::ops::BitXor<$struct_name> for $struct_name { + type Output = Self; + fn bitxor(self, rhs: Self) -> Self::Output { + $struct_name(self.0 ^ rhs.0) + } + } + + impl ::std::ops::BitOr<$struct_name> for $struct_name { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + $struct_name(self.0 | rhs.0) + } + } + + impl ::std::ops::Sub<$struct_name> for $struct_name { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + $struct_name(self.0 & !rhs.0) + } + } + + impl ::std::ops::Not for $struct_name { + type Output = Self; + fn not(self) -> Self::Output { + $struct_name(self.0 ^ Self::VALID__) + } + } + + impl From<$struct_name> for $type { + fn from(x: $struct_name) -> $type { + x.0 + } + } + + impl From<$type> for $struct_name { + fn from(x: $type) -> Self { + $struct_name(x & Self::VALID__) + } + } + }; + + { $type:ty: $expr:expr } => { + ::qemu_api_macros::bits_const_internal! { $type @ ($expr) } + }; + + { $type:ty as $int_type:ty: $expr:expr } => { + (::qemu_api_macros::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type + }; +} + +#[cfg(test)] +mod test { + bits! { + pub struct InterruptMask(u32) { + OE = 1 << 10, + BE = 1 << 9, + PE = 1 << 8, + FE = 1 << 7, + RT = 1 << 6, + TX = 1 << 5, + RX = 1 << 4, + DSR = 1 << 3, + DCD = 1 << 2, + CTS = 1 << 1, + RI = 1 << 0, + + E = bits!(Self as u32: OE | BE | PE | FE), + MS = bits!(Self as u32: RI | DSR | DCD | CTS), + } + } + + #[test] + pub fn test_not() { + assert_eq!( + !InterruptMask::from(InterruptMask::RT.0), + InterruptMask::E | InterruptMask::MS | InterruptMask::TX | InterruptMask::RX + ); + } + + #[test] + pub fn test_and() { + assert_eq!( + InterruptMask::from(0), + InterruptMask::MS & InterruptMask::OE + ) + } + + #[test] + pub fn test_or() { + assert_eq!( + InterruptMask::E, + InterruptMask::OE | InterruptMask::BE | InterruptMask::PE | InterruptMask::FE + ); + } + + #[test] + pub fn test_xor() { + assert_eq!( + InterruptMask::E ^ InterruptMask::BE, + InterruptMask::OE | InterruptMask::PE | InterruptMask::FE + ); + } +} diff --git a/rust/clippy.toml b/rust/clippy.toml deleted file mode 100644 index 58a62c0..0000000 --- a/rust/clippy.toml +++ /dev/null @@ -1,3 +0,0 @@ -doc-valid-idents = ["PrimeCell", ".."] -allow-mixed-uninlined-format-args = false -msrv = "1.77.0" diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml index a1f431a..003ef96 100644 --- a/rust/hw/char/pl011/Cargo.toml +++ b/rust/hw/char/pl011/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["staticlib"] [dependencies] bilge = { version = "0.2.0" } bilge-impl = { version = "0.2.0" } +bits = { path = "../../../bits" } qemu_api = { path = "../../../qemu-api" } qemu_api_macros = { path = "../../../qemu-api-macros" } diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build index 547cca5..2a1be32 100644 --- a/rust/hw/char/pl011/meson.build +++ b/rust/hw/char/pl011/meson.build @@ -1,17 +1,12 @@ -subproject('bilge-0.2-rs', required: true) -subproject('bilge-impl-0.2-rs', required: true) - -bilge_dep = dependency('bilge-0.2-rs') -bilge_impl_dep = dependency('bilge-impl-0.2-rs') - _libpl011_rs = static_library( 'pl011', files('src/lib.rs'), override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', dependencies: [ - bilge_dep, - bilge_impl_dep, + bilge_rs, + bilge_impl_rs, + bits_rs, qemu_api, qemu_api_macros, ], @@ -21,6 +16,6 @@ rust_devices_ss.add(when: 'CONFIG_X_PL011_RUST', if_true: [declare_dependency( link_whole: [_libpl011_rs], # Putting proc macro crates in `dependencies` is necessary for Meson to find # them when compiling the root per-target static rust lib. - dependencies: [bilge_impl_dep, qemu_api_macros], + dependencies: [bilge_impl_rs, qemu_api_macros], variables: {'crate': 'pl011'}, )]) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index bde3be6..be8387f 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -85,8 +85,8 @@ pub struct PL011Registers { #[doc(alias = "cr")] pub control: registers::Control, pub dmacr: u32, - pub int_enabled: u32, - pub int_level: u32, + pub int_enabled: Interrupt, + pub int_level: Interrupt, pub read_fifo: Fifo, pub ilpr: u32, pub ibrd: u32, @@ -175,7 +175,7 @@ impl DeviceImpl for PL011State { fn vmsd() -> Option<&'static VMStateDescription> { Some(&device_class::VMSTATE_PL011) } - const REALIZE: Option<fn(&Self)> = Some(Self::realize); + const REALIZE: Option<fn(&Self) -> qemu_api::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for PL011State { @@ -199,9 +199,9 @@ impl PL011Registers { LCR_H => u32::from(self.line_control), CR => u32::from(self.control), FLS => self.ifl, - IMSC => self.int_enabled, - RIS => self.int_level, - MIS => self.int_level & self.int_enabled, + IMSC => u32::from(self.int_enabled), + RIS => u32::from(self.int_level), + MIS => u32::from(self.int_level & self.int_enabled), ICR => { // "The UARTICR Register is the interrupt clear register and is write-only" // Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR @@ -263,13 +263,13 @@ impl PL011Registers { self.set_read_trigger(); } IMSC => { - self.int_enabled = value; + self.int_enabled = Interrupt::from(value); return true; } RIS => {} MIS => {} ICR => { - self.int_level &= !value; + self.int_level &= !Interrupt::from(value); return true; } DMACR => { @@ -295,7 +295,7 @@ impl PL011Registers { self.flags.set_receive_fifo_empty(true); } if self.read_count + 1 == self.read_trigger { - self.int_level &= !Interrupt::RX.0; + self.int_level &= !Interrupt::RX; } self.receive_status_error_clear.set_from_data(c); *update = true; @@ -305,7 +305,7 @@ impl PL011Registers { fn write_data_register(&mut self, value: u32) -> bool { // interrupts always checked let _ = self.loopback_tx(value.into()); - self.int_level |= Interrupt::TX.0; + self.int_level |= Interrupt::TX; true } @@ -361,19 +361,19 @@ impl PL011Registers { // Change interrupts based on updated FR let mut il = self.int_level; - il &= !Interrupt::MS.0; + il &= !Interrupt::MS; if self.flags.data_set_ready() { - il |= Interrupt::DSR.0; + il |= Interrupt::DSR; } if self.flags.data_carrier_detect() { - il |= Interrupt::DCD.0; + il |= Interrupt::DCD; } if self.flags.clear_to_send() { - il |= Interrupt::CTS.0; + il |= Interrupt::CTS; } if self.flags.ring_indicator() { - il |= Interrupt::RI.0; + il |= Interrupt::RI; } self.int_level = il; true @@ -391,8 +391,8 @@ impl PL011Registers { self.line_control.reset(); self.receive_status_error_clear.reset(); self.dmacr = 0; - self.int_enabled = 0; - self.int_level = 0; + self.int_enabled = 0.into(); + self.int_level = 0.into(); self.ilpr = 0; self.ibrd = 0; self.fbrd = 0; @@ -451,7 +451,7 @@ impl PL011Registers { } if self.read_count == self.read_trigger { - self.int_level |= Interrupt::RX.0; + self.int_level |= Interrupt::RX; return true; } false @@ -480,13 +480,13 @@ impl PL011Registers { } impl PL011State { - /// Initializes a pre-allocated, unitialized instance of `PL011State`. + /// Initializes a pre-allocated, uninitialized instance of `PL011State`. /// /// # Safety /// /// `self` must point to a correctly sized and aligned location for the /// `PL011State` type. It must not be called more than once on the same - /// location/instance. All its fields are expected to hold unitialized + /// location/instance. All its fields are expected to hold uninitialized /// values with the sole exception of `parent_obj`. unsafe fn init(&mut self) { static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new() @@ -619,9 +619,10 @@ impl PL011State { } } - fn realize(&self) { + fn realize(&self) -> qemu_api::Result<()> { self.char_backend .enable_handlers(self, Self::can_receive, Self::receive, Self::event); + Ok(()) } fn reset_hold(&self, _type: ResetType) { @@ -632,7 +633,7 @@ impl PL011State { let regs = self.regs.borrow(); let flags = regs.int_level & regs.int_enabled; for (irq, i) in self.interrupts.iter().zip(IRQMASK) { - irq.set(flags & i != 0); + irq.set(flags.any_set(i)); } } @@ -642,14 +643,13 @@ impl PL011State { } /// Which bits in the interrupt status matter for each outbound IRQ line ? -const IRQMASK: [u32; 6] = [ - /* combined IRQ */ - Interrupt::E.0 | Interrupt::MS.0 | Interrupt::RT.0 | Interrupt::TX.0 | Interrupt::RX.0, - Interrupt::RX.0, - Interrupt::TX.0, - Interrupt::RT.0, - Interrupt::MS.0, - Interrupt::E.0, +const IRQMASK: [Interrupt; 6] = [ + Interrupt::all(), + Interrupt::RX, + Interrupt::TX, + Interrupt::RT, + Interrupt::MS, + Interrupt::E, ]; /// # Safety diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs index 690feb6..7ececd3 100644 --- a/rust/hw/char/pl011/src/registers.rs +++ b/rust/hw/char/pl011/src/registers.rs @@ -9,7 +9,8 @@ // https://developer.arm.com/documentation/ddi0183/latest/ use bilge::prelude::*; -use qemu_api::impl_vmstate_bitsized; +use bits::bits; +use qemu_api::{impl_vmstate_bitsized, impl_vmstate_forward}; /// Offset of each register from the base memory address of the device. #[doc(alias = "offset")] @@ -326,22 +327,24 @@ impl Default for Control { } } -/// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC -pub struct Interrupt(pub u32); +bits! { + /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC + #[derive(Default)] + pub struct Interrupt(u32) { + OE = 1 << 10, + BE = 1 << 9, + PE = 1 << 8, + FE = 1 << 7, + RT = 1 << 6, + TX = 1 << 5, + RX = 1 << 4, + DSR = 1 << 3, + DCD = 1 << 2, + CTS = 1 << 1, + RI = 1 << 0, -impl Interrupt { - pub const OE: Self = Self(1 << 10); - pub const BE: Self = Self(1 << 9); - pub const PE: Self = Self(1 << 8); - pub const FE: Self = Self(1 << 7); - pub const RT: Self = Self(1 << 6); - pub const TX: Self = Self(1 << 5); - pub const RX: Self = Self(1 << 4); - pub const DSR: Self = Self(1 << 3); - pub const DCD: Self = Self(1 << 2); - pub const CTS: Self = Self(1 << 1); - pub const RI: Self = Self(1 << 0); - - pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); - pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); + E = bits!(Self as u32: OE | BE | PE | FE), + MS = bits!(Self as u32: RI | DSR | DCD | CTS), + } } +impl_vmstate_forward!(Interrupt); diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/device.rs index 779681d..735b2fb 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -1,5 +1,5 @@ // Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later use std::{ @@ -12,7 +12,7 @@ use std::{ use qemu_api::{ bindings::{ address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool, - qdev_prop_uint32, qdev_prop_uint8, + qdev_prop_uint32, qdev_prop_usize, }, cell::{BqlCell, BqlRefCell}, irq::InterruptSource, @@ -36,9 +36,9 @@ use crate::fw_cfg::HPETFwConfig; const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes /// Minimum recommended hardware implementation. -const HPET_MIN_TIMERS: u8 = 3; +const HPET_MIN_TIMERS: usize = 3; /// Maximum timers in each timer block. -const HPET_MAX_TIMERS: u8 = 32; +const HPET_MAX_TIMERS: usize = 32; /// Flags that HPETState.flags supports. const HPET_FLAG_MSI_SUPPORT_SHIFT: usize = 0; @@ -561,8 +561,8 @@ pub struct HPETState { /// HPET timer array managed by this timer block. #[doc(alias = "timer")] - timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS as usize], - num_timers: BqlCell<u8>, + timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS], + num_timers: usize, num_timers_save: BqlCell<u8>, /// Instance id (HPET timer block ID). @@ -570,11 +570,6 @@ pub struct HPETState { } impl HPETState { - // Get num_timers with `usize` type, which is useful to play with array index. - fn get_num_timers(&self) -> usize { - self.num_timers.get().into() - } - const fn has_msi_flag(&self) -> bool { self.flags & (1 << HPET_FLAG_MSI_SUPPORT_SHIFT) != 0 } @@ -636,7 +631,7 @@ impl HPETState { self.hpet_offset .set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns()); - for timer in self.timers.iter().take(self.get_num_timers()) { + for timer in self.timers.iter().take(self.num_timers) { let mut t = timer.borrow_mut(); if t.is_int_enabled() && t.is_int_active() { @@ -648,7 +643,7 @@ impl HPETState { // Halt main counter and disable interrupt generation. self.counter.set(self.get_ticks()); - for timer in self.timers.iter().take(self.get_num_timers()) { + for timer in self.timers.iter().take(self.num_timers) { timer.borrow_mut().del_timer(); } } @@ -671,7 +666,7 @@ impl HPETState { let new_val = val << shift; let cleared = new_val & self.int_status.get(); - for (index, timer) in self.timers.iter().take(self.get_num_timers()).enumerate() { + for (index, timer) in self.timers.iter().take(self.num_timers).enumerate() { if cleared & (1 << index) != 0 { timer.borrow_mut().update_irq(false); } @@ -724,19 +719,17 @@ impl HPETState { } } - fn realize(&self) { + fn realize(&self) -> qemu_api::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}" + ))?; + } if self.int_route_cap == 0 { - // TODO: Add error binding: warn_report() - println!("Hpet's hpet-intcap property not initialized"); + Err("hpet.hpet-intcap property not initialized")?; } - self.hpet_id.set(HPETFwConfig::assign_hpet_id()); - - if self.num_timers.get() < HPET_MIN_TIMERS { - self.num_timers.set(HPET_MIN_TIMERS); - } else if self.num_timers.get() > HPET_MAX_TIMERS { - self.num_timers.set(HPET_MAX_TIMERS); - } + self.hpet_id.set(HPETFwConfig::assign_hpet_id()?); self.init_timer(); // 64-bit General Capabilities and ID Register; LegacyReplacementRoute. @@ -745,16 +738,17 @@ impl HPETState { 1 << HPET_CAP_COUNT_SIZE_CAP_SHIFT | 1 << HPET_CAP_LEG_RT_CAP_SHIFT | HPET_CAP_VENDER_ID_VALUE << HPET_CAP_VENDER_ID_SHIFT | - ((self.get_num_timers() - 1) as u64) << HPET_CAP_NUM_TIM_SHIFT | // indicate the last timer + ((self.num_timers - 1) as u64) << HPET_CAP_NUM_TIM_SHIFT | // indicate the last timer (HPET_CLK_PERIOD * FS_PER_NS) << HPET_CAP_CNT_CLK_PERIOD_SHIFT, // 10 ns ); self.init_gpio_in(2, HPETState::handle_legacy_irq); self.init_gpio_out(from_ref(&self.pit_enabled)); + Ok(()) } fn reset_hold(&self, _type: ResetType) { - for timer in self.timers.iter().take(self.get_num_timers()) { + for timer in self.timers.iter().take(self.num_timers) { timer.borrow_mut().reset(); } @@ -782,7 +776,7 @@ impl HPETState { GlobalRegister::try_from(addr).map(HPETRegister::Global) } else { let timer_id: usize = ((addr - 0x100) / 0x20) as usize; - if timer_id <= self.get_num_timers() { + if timer_id < self.num_timers { // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) TimerRegister::try_from(addr & 0x18) .map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg)) @@ -853,12 +847,12 @@ impl HPETState { * also added to the migration stream. Check that it matches the value * that was configured. */ - self.num_timers_save.set(self.num_timers.get()); + self.num_timers_save.set(self.num_timers as u8); 0 } fn post_load(&self, _version_id: u8) -> i32 { - for timer in self.timers.iter().take(self.get_num_timers()) { + for timer in self.timers.iter().take(self.num_timers) { let mut t = timer.borrow_mut(); t.cmp64 = t.calculate_cmp64(t.get_state().counter.get(), t.cmp); @@ -883,7 +877,7 @@ impl HPETState { } fn validate_num_timers(&self, _version_id: u8) -> bool { - self.num_timers.get() == self.num_timers_save.get() + self.num_timers == self.num_timers_save.get().into() } } @@ -910,7 +904,7 @@ qemu_api::declare_properties! { c"timers", HPETState, num_timers, - unsafe { &qdev_prop_uint8 }, + unsafe { &qdev_prop_usize }, u8, default = HPET_MIN_TIMERS ), @@ -1015,16 +1009,16 @@ const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match"; static VMSTATE_HPET: VMStateDescription = VMStateDescription { name: c"hpet".as_ptr(), version_id: 2, - minimum_version_id: 1, + minimum_version_id: 2, pre_save: Some(hpet_pre_save), post_load: Some(hpet_post_load), fields: vmstate_fields! { vmstate_of!(HPETState, config), vmstate_of!(HPETState, int_status), vmstate_of!(HPETState, counter), - vmstate_of!(HPETState, num_timers_save).with_version_id(2), + vmstate_of!(HPETState, num_timers_save), vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers), - vmstate_struct!(HPETState, timers[0 .. num_timers], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0), + vmstate_struct!(HPETState, timers[0 .. num_timers_save], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0), }, subsections: vmstate_subsections! { VMSTATE_HPET_RTC_IRQ_LEVEL, @@ -1042,7 +1036,7 @@ impl DeviceImpl for HPETState { Some(&VMSTATE_HPET) } - const REALIZE: Option<fn(&Self)> = Some(Self::realize); + const REALIZE: Option<fn(&Self) -> qemu_api::Result<()>> = Some(Self::realize); } impl ResettablePhasesImpl for HPETState { diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs index aa08d28..619d662 100644 --- a/rust/hw/timer/hpet/src/fw_cfg.rs +++ b/rust/hw/timer/hpet/src/fw_cfg.rs @@ -1,5 +1,5 @@ // Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later use std::ptr::addr_of_mut; @@ -36,7 +36,7 @@ pub static mut hpet_fw_cfg: HPETFwConfig = HPETFwConfig { }; impl HPETFwConfig { - pub(crate) fn assign_hpet_id() -> usize { + pub(crate) fn assign_hpet_id() -> Result<usize, &'static str> { assert!(bql_locked()); // SAFETY: all accesses go through these methods, which guarantee // that the accesses are protected by the BQL. @@ -48,13 +48,12 @@ impl HPETFwConfig { } if fw_cfg.count == 8 { - // TODO: Add error binding: error_setg() - panic!("Only 8 instances of HPET is allowed"); + Err("Only 8 instances of HPET are allowed")?; } let id: usize = fw_cfg.count.into(); fw_cfg.count += 1; - id + Ok(id) } pub(crate) fn update_hpet_cfg(hpet_id: usize, timer_block_id: u32, address: u64) { diff --git a/rust/hw/timer/hpet/src/lib.rs b/rust/hw/timer/hpet/src/lib.rs index 1954584..a95cf14 100644 --- a/rust/hw/timer/hpet/src/lib.rs +++ b/rust/hw/timer/hpet/src/lib.rs @@ -1,5 +1,5 @@ // Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later //! # HPET QEMU Device Model @@ -7,7 +7,7 @@ //! This library implements a device model for the IA-PC HPET (High //! Precision Event Timers) device in QEMU. +pub mod device; pub mod fw_cfg; -pub mod hpet; pub const TYPE_HPET: &::std::ffi::CStr = c"hpet"; diff --git a/rust/meson.build b/rust/meson.build index 91e52b8..99ae795 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -1,4 +1,27 @@ +subproject('anyhow-1-rs', required: true) +subproject('bilge-0.2-rs', required: true) +subproject('bilge-impl-0.2-rs', required: true) +subproject('foreign-0.3-rs', required: true) +subproject('libc-0.2-rs', required: true) + +anyhow_rs = dependency('anyhow-1-rs') +bilge_rs = dependency('bilge-0.2-rs') +bilge_impl_rs = dependency('bilge-impl-0.2-rs') +foreign_rs = dependency('foreign-0.3-rs') +libc_rs = dependency('libc-0.2-rs') + +subproject('proc-macro2-1-rs', required: true) +subproject('quote-1-rs', required: true) +subproject('syn-2-rs', required: true) + +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) + subdir('qemu-api-macros') +subdir('bits') subdir('qemu-api') subdir('hw') @@ -6,21 +29,9 @@ subdir('hw') cargo = find_program('cargo', required: false) if cargo.found() - run_target('clippy', - command: [config_host['MESON'], 'devenv', - '--workdir', '@CURRENT_SOURCE_DIR@', - cargo, 'clippy', '--tests'], - depends: bindings_rs) - run_target('rustfmt', command: [config_host['MESON'], 'devenv', '--workdir', '@CURRENT_SOURCE_DIR@', cargo, 'fmt'], depends: bindings_rs) - - run_target('rustdoc', - command: [config_host['MESON'], 'devenv', - '--workdir', '@CURRENT_SOURCE_DIR@', - cargo, 'doc', '--no-deps', '--document-private-items'], - depends: bindings_rs) endif diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-api-macros/meson.build index 6f94a4b..8610ce1 100644 --- a/rust/qemu-api-macros/meson.build +++ b/rust/qemu-api-macros/meson.build @@ -1,11 +1,3 @@ -subproject('proc-macro2-1-rs', required: true) -subproject('quote-1-rs', required: true) -subproject('syn-2-rs', required: true) - -quote_dep = dependency('quote-1-rs', native: true) -syn_dep = dependency('syn-2-rs', native: true) -proc_macro2_dep = dependency('proc-macro2-1-rs', native: true) - _qemu_api_macros_rs = rust.proc_macro( 'qemu_api_macros', files('src/lib.rs'), @@ -16,9 +8,9 @@ _qemu_api_macros_rs = rust.proc_macro( '--cfg', 'feature="proc-macro"', ], dependencies: [ - proc_macro2_dep, - quote_dep, - syn_dep, + proc_macro2_rs_native, + quote_rs_native, + syn_rs_native, ], ) diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-api-macros/src/bits.rs new file mode 100644 index 0000000..5ba8475 --- /dev/null +++ b/rust/qemu-api-macros/src/bits.rs @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 or GPL-2.0-or-later + +// shadowing is useful together with "if let" +#![allow(clippy::shadow_unrelated)] + +use proc_macro2::{ + Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, +}; + +use crate::utils::MacroError; + +pub struct BitsConstInternal { + typ: TokenTree, +} + +fn paren(ts: TokenStream) -> TokenTree { + TT::Group(Group::new(Delimiter::Parenthesis, ts)) +} + +fn ident(s: &'static str) -> TokenTree { + TT::Ident(Ident::new(s, Span::call_site())) +} + +fn punct(ch: char) -> TokenTree { + TT::Punct(Punct::new(ch, Spacing::Alone)) +} + +/// Implements a recursive-descent parser that translates Boolean expressions on +/// bitmasks to invocations of `const` functions defined by the `bits!` macro. +impl BitsConstInternal { + // primary ::= '(' or ')' + // | ident + // | '!' ident + fn parse_primary( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError> { + let next = match tok { + TT::Group(ref g) => { + if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { + return Err(MacroError::Message("expected parenthesis".into(), g.span())); + } + let mut stream = g.stream().into_iter(); + let Some(first_tok) = stream.next() else { + return Err(MacroError::Message( + "expected operand, found ')'".into(), + g.span(), + )); + }; + let mut output = TokenStream::new(); + // start from the lowest precedence + let next = self.parse_or(first_tok, &mut stream, &mut output)?; + if let Some(tok) = next { + return Err(MacroError::Message( + format!("unexpected token {tok}"), + tok.span(), + )); + } + out.extend(Some(paren(output))); + it.next() + } + TT::Ident(_) => { + let mut output = TokenStream::new(); + output.extend([ + self.typ.clone(), + TT::Punct(Punct::new(':', Spacing::Joint)), + TT::Punct(Punct::new(':', Spacing::Joint)), + tok, + ]); + out.extend(Some(paren(output))); + it.next() + } + TT::Punct(ref p) => { + if p.as_char() != '!' { + return Err(MacroError::Message("expected operand".into(), p.span())); + } + let Some(rhs_tok) = it.next() else { + return Err(MacroError::Message( + "expected operand at end of input".into(), + p.span(), + )); + }; + let next = self.parse_primary(rhs_tok, it, out)?; + out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]); + next + } + _ => { + return Err(MacroError::Message("unexpected literal".into(), tok.span())); + } + }; + Ok(next) + } + + fn parse_binop< + F: Fn( + &Self, + TokenTree, + &mut dyn Iterator<Item = TokenTree>, + &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError>, + >( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ch: char, + f: F, + method: &'static str, + ) -> Result<Option<TokenTree>, MacroError> { + let mut next = f(self, tok, it, out)?; + while next.is_some() { + let op = next.as_ref().unwrap(); + let TT::Punct(ref p) = op else { break }; + if p.as_char() != ch { + break; + } + + let Some(rhs_tok) = it.next() else { + return Err(MacroError::Message( + "expected operand at end of input".into(), + p.span(), + )); + }; + let mut rhs = TokenStream::new(); + next = f(self, rhs_tok, it, &mut rhs)?; + out.extend([punct('.'), ident(method), paren(rhs)]); + } + Ok(next) + } + + // sub ::= primary ('-' primary)* + pub fn parse_sub( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError> { + self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") + } + + // and ::= sub ('&' sub)* + fn parse_and( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError> { + self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") + } + + // xor ::= and ('&' and)* + fn parse_xor( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError> { + self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") + } + + // or ::= xor ('|' xor)* + pub fn parse_or( + &self, + tok: TokenTree, + it: &mut dyn Iterator<Item = TokenTree>, + out: &mut TokenStream, + ) -> Result<Option<TokenTree>, MacroError> { + self.parse_binop(tok, it, out, '|', Self::parse_xor, "union") + } + + pub fn parse( + it: &mut dyn Iterator<Item = TokenTree>, + ) -> Result<proc_macro2::TokenStream, MacroError> { + let mut pos = Span::call_site(); + let mut typ = proc_macro2::TokenStream::new(); + + // Gobble everything up to an `@` sign, which is followed by a + // parenthesized expression; that is, all token trees except the + // last two form the type. + let next = loop { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + match tok { + None => break None, + Some(TT::Punct(ref p)) if p.as_char() == '@' => { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + break tok; + } + Some(x) => typ.extend(Some(x)), + } + }; + + let Some(tok) = next else { + return Err(MacroError::Message( + "expected expression, do not call this macro directly".into(), + pos, + )); + }; + let TT::Group(ref _group) = tok else { + return Err(MacroError::Message( + "expected parenthesis, do not call this macro directly".into(), + tok.span(), + )); + }; + let mut out = TokenStream::new(); + let state = Self { + typ: TT::Group(Group::new(Delimiter::None, typ)), + }; + + let next = state.parse_primary(tok, it, &mut out)?; + + // A parenthesized expression is a single production of the grammar, + // so the input must have reached the last token. + if let Some(tok) = next { + return Err(MacroError::Message( + format!("unexpected token {tok}"), + tok.span(), + )); + } + Ok(out) + } +} diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index f97449b..c18bb4e 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -12,6 +12,9 @@ use syn::{ mod utils; use utils::MacroError; +mod bits; +use bits::BitsConstInternal; + fn get_fields<'a>( input: &'a DeriveInput, msg: &str, @@ -190,23 +193,52 @@ fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, Macr } #[rustfmt::skip::macros(quote)] +fn derive_tryinto_body( + name: &Ident, + variants: &Punctuated<Variant, Comma>, + repr: &Path, +) -> Result<proc_macro2::TokenStream, MacroError> { + let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); + + Ok(quote! { + #(const #discriminants: #repr = #name::#discriminants as #repr;)*; + match value { + #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* + _ => core::result::Result::Err(value), + } + }) +} + +#[rustfmt::skip::macros(quote)] fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; - let name = &input.ident; - let variants = get_variants(&input)?; - let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); + let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?; + let errmsg = format!("invalid value for {name}"); Ok(quote! { + impl #name { + #[allow(dead_code)] + pub const fn into_bits(self) -> #repr { + self as #repr + } + + #[allow(dead_code)] + pub const fn from_bits(value: #repr) -> Self { + match ({ + #body + }) { + Ok(x) => x, + Err(_) => panic!(#errmsg) + } + } + } impl core::convert::TryFrom<#repr> for #name { type Error = #repr; - fn try_from(value: #repr) -> Result<Self, Self::Error> { - #(const #discriminants: #repr = #name::#discriminants as #repr;)*; - match value { - #(#discriminants => Ok(Self::#discriminants),)* - _ => Err(value), - } + #[allow(ambiguous_associated_items)] + fn try_from(value: #repr) -> Result<Self, #repr> { + #body } } }) @@ -219,3 +251,12 @@ pub fn derive_tryinto(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +#[proc_macro] +pub fn bits_const_internal(ts: TokenStream) -> TokenStream { + let ts = proc_macro2::TokenStream::from(ts); + let mut it = ts.into_iter(); + + let expanded = BitsConstInternal::parse(&mut it).unwrap_or_else(Into::into); + TokenStream::from(expanded) +} diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml index c96cf50..db7000d 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/qemu-api/Cargo.toml @@ -15,7 +15,9 @@ rust-version.workspace = true [dependencies] qemu_api_macros = { path = "../qemu-api-macros" } +anyhow = "~1.0" libc = "0.2.162" +foreign = "~0.3.1" [features] default = ["debug_cell"] diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 1696df7..cac8595 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -2,8 +2,6 @@ _qemu_api_cfg = run_command(rustc_args, '--config-headers', config_host_h, '--features', files('Cargo.toml'), capture: true, check: true).stdout().strip().splitlines() -libc_dep = dependency('libc-0.2-rs') - # _qemu_api_cfg += ['--cfg', 'feature="allocator"'] if get_option('debug_mutex') _qemu_api_cfg += ['--cfg', 'feature="debug_cell"'] @@ -21,6 +19,7 @@ _qemu_api_rs = static_library( 'src/cell.rs', 'src/chardev.rs', 'src/errno.rs', + 'src/error.rs', 'src/irq.rs', 'src/memory.rs', 'src/module.rs', @@ -37,32 +36,24 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, - dependencies: [libc_dep, qemu_api_macros], + dependencies: [anyhow_rs, foreign_rs, libc_rs, qemu_api_macros, qemuutil_rs, + qom, hwcore, chardev, migration], ) rust.test('rust-qemu-api-tests', _qemu_api_rs, suite: ['unit', 'rust']) -qemu_api = declare_dependency(link_with: _qemu_api_rs) +qemu_api = declare_dependency(link_with: [_qemu_api_rs], + dependencies: [qemu_api_macros, qom, hwcore, chardev, migration]) -# Rust executables do not support objects, so add an intermediate step. -rust_qemu_api_objs = static_library( - 'rust_qemu_api_objs', - objects: [libqom.extract_all_objects(recursive: false), - libhwcore.extract_all_objects(recursive: false), - libchardev.extract_all_objects(recursive: false), - libcrypto.extract_all_objects(recursive: false), - libauthz.extract_all_objects(recursive: false), - libio.extract_all_objects(recursive: false), - libmigration.extract_all_objects(recursive: false)]) -rust_qemu_api_deps = declare_dependency( - dependencies: [ - qom_ss.dependencies(), - chardev_ss.dependencies(), - crypto_ss.dependencies(), - authz_ss.dependencies(), - io_ss.dependencies()], - link_whole: [rust_qemu_api_objs, libqemuutil]) +# Doctests are essentially integration tests, so they need the same dependencies. +# Note that running them requires the object files for C code, so place them +# in a separate suite that is run by the "build" CI jobs rather than "check". +rust.doctest('rust-qemu-api-doctests', + _qemu_api_rs, + protocol: 'rust', + dependencies: qemu_api, + suite: ['doc', 'rust']) test('rust-qemu-api-integration', executable( @@ -71,7 +62,7 @@ test('rust-qemu-api-integration', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: ['--test'], install: false, - dependencies: [qemu_api, qemu_api_macros, rust_qemu_api_deps]), + dependencies: [qemu_api]), args: [ '--test', '--test-threads', '1', '--format', 'pretty', diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 3c1d297..057de4b 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -11,6 +11,7 @@ clippy::restriction, clippy::style, clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, clippy::useless_transmute, clippy::missing_safety_doc )] diff --git a/rust/qemu-api/src/bitops.rs b/rust/qemu-api/src/bitops.rs index 023ec1a..b1e3a53 100644 --- a/rust/qemu-api/src/bitops.rs +++ b/rust/qemu-api/src/bitops.rs @@ -1,5 +1,5 @@ // Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later //! This module provides bit operation extensions to integer types. diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index 05ce09f..27063b0 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -225,27 +225,23 @@ use crate::bindings; /// An internal function that is used by doctests. pub fn bql_start_test() { - if cfg!(MESON) { - // SAFETY: integration tests are run with --test-threads=1, while - // unit tests and doctests are not multithreaded and do not have - // any BQL-protected data. Just set bql_locked to true. - unsafe { - bindings::rust_bql_mock_lock(); - } + // SAFETY: integration tests are run with --test-threads=1, while + // unit tests and doctests are not multithreaded and do not have + // any BQL-protected data. Just set bql_locked to true. + unsafe { + bindings::rust_bql_mock_lock(); } } pub fn bql_locked() -> bool { // SAFETY: the function does nothing but return a thread-local bool - !cfg!(MESON) || unsafe { bindings::bql_locked() } + unsafe { bindings::bql_locked() } } fn bql_block_unlock(increase: bool) { - if cfg!(MESON) { - // SAFETY: this only adjusts a counter - unsafe { - bindings::bql_block_unlock(increase); - } + // SAFETY: this only adjusts a counter + unsafe { + bindings::bql_block_unlock(increase); } } diff --git a/rust/qemu-api/src/error.rs b/rust/qemu-api/src/error.rs new file mode 100644 index 0000000..e114fc4 --- /dev/null +++ b/rust/qemu-api/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 +//! [`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<T> = std::result::Result<T, Error>; + +#[derive(Debug)] +pub struct Error { + msg: Option<Cow<'static, str>>, + /// Appends the print string of the error to the msg if not None + cause: Option<anyhow::Error>, + 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<String> 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<anyhow::Error> 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<Cow<'static, str>>, cause: impl Into<anyhow::Error>) -> 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<T: CloneToForeign>( + result: Result<T>, + 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<T>( + result: Result<T>, + errp: *mut *mut bindings::Error, + ) -> Option<T> { + 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, F: FnOnce() -> T>( + c_error: *mut bindings::Error, + f: F, + ) -> Result<T> { + // SAFETY: caller guarantees c_error is valid + let err = unsafe { Option::<Self>::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<Self> { + // SAFETY: all arguments are controlled by this function + unsafe { + let err: *mut c_void = libc::malloc(std::mem::size_of::<bindings::Error>()); + 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::<c_char>(), + 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::<u8>(), + 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 foreign::OwnedPointer; + + use super::*; + use crate::{assert_match, bindings}; + + #[track_caller] + fn error_for_test(msg: &CStr) -> OwnedPointer<Error> { + // 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::<bindings::Error>()); + 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::<c_char>(), + 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::<String, _>(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 234a94e..93902fc 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -19,6 +19,7 @@ pub mod callbacks; pub mod cell; pub mod chardev; pub mod errno; +pub mod error; pub mod irq; pub mod memory; pub mod module; @@ -34,6 +35,8 @@ use std::{ ffi::c_void, }; +pub use error::{Error, Result}; + #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] extern "C" { fn g_aligned_alloc0( diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 1279d7a..0610959 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -12,10 +12,11 @@ use std::{ pub use bindings::{ClockEvent, DeviceClass, Property, ResetType}; use crate::{ - bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, Error, ResettableClass}, + bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, ResettableClass}, callbacks::FnCall, cell::{bql_locked, Opaque}, chardev::Chardev, + error::{Error, Result}, irq::InterruptSource, prelude::*, qom::{ObjectClass, ObjectImpl, Owned}, @@ -108,7 +109,7 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { /// /// If not `None`, the parent class's `realize` method is overridden /// with the function pointed to by `REALIZE`. - const REALIZE: Option<fn(&Self)> = None; + const REALIZE: Option<fn(&Self) -> Result<()>> = None; /// An array providing the properties that the user can set on the /// device. Not a `const` because referencing statics in constants @@ -134,10 +135,13 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>( dev: *mut bindings::DeviceState, - _errp: *mut *mut Error, + errp: *mut *mut bindings::Error, ) { let state = NonNull::new(dev).unwrap().cast::<T>(); - T::REALIZE.unwrap()(unsafe { state.as_ref() }); + let result = T::REALIZE.unwrap()(unsafe { state.as_ref() }); + unsafe { + Error::ok_or_propagate(result, errp); + } } unsafe impl InterfaceType for ResettableClass { diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 41e5a5e..14f98fe 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -291,7 +291,7 @@ pub unsafe trait ObjectType: Sized { } /// Return the receiver as a const raw pointer to Object. - /// This is preferrable to `as_object_mut_ptr()` if a C + /// This is preferable to `as_object_mut_ptr()` if a C /// function only needs a `const Object *`. fn as_object_ptr(&self) -> *const bindings::Object { self.as_object().as_ptr() @@ -485,7 +485,7 @@ pub trait ObjectImpl: ObjectType + IsA<Object> { /// `INSTANCE_INIT` functions have been called. const INSTANCE_POST_INIT: Option<fn(&Self)> = None; - /// Called on descendent classes after all parent class initialization + /// Called on descendant classes after all parent class initialization /// has occurred, but before the class itself is initialized. This /// is only useful if a class is not a leaf, and can be used to undo /// the effects of copying the contents of the parent's class struct diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index 868bd88..0a2d111 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -1,5 +1,5 @@ // Copyright (C) 2024 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later use std::{ diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 9c8b239..812f390 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -9,7 +9,7 @@ //! * [`vmstate_unused!`](crate::vmstate_unused) and //! [`vmstate_of!`](crate::vmstate_of), which are used to express the //! migration format for a struct. This is based on the [`VMState`] trait, -//! which is defined by all migrateable types. +//! which is defined by all migratable types. //! //! * [`impl_vmstate_forward`](crate::impl_vmstate_forward) and //! [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), which help with diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs index ad0fc5c..bded836 100644 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ b/rust/qemu-api/tests/vmstate_tests.rs @@ -1,5 +1,5 @@ // Copyright (C) 2025 Intel Corporation. -// Author(s): Zhao Liu <zhai1.liu@intel.com> +// Author(s): Zhao Liu <zhao1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later use std::{ diff --git a/rust/wrapper.h b/rust/wrapper.h index beddd9a..6060d3b 100644 --- a/rust/wrapper.h +++ b/rust/wrapper.h @@ -60,6 +60,7 @@ typedef enum memory_order { #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" |