* [PATCH 01/11] Revert "rust: add PL011 device model"
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
@ 2024-10-24 14:02 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 02/11] rust: add PL011 device model Manos Pitsidianakis
` (10 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:02 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Patch was applied with invalid authorship by accident, which confuses
git tooling that look at git blame for contributors etc.
Patch will be re-applied with correct authorship right after this
commit.
This reverts commit d0f0cd5b1f7e9780753344548e17ad4df9fcf5d8.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
MAINTAINERS | 5 -
meson.build | 24 -
hw/arm/Kconfig | 30 +-
rust/Kconfig | 1 -
rust/hw/Kconfig | 2 -
rust/hw/char/Kconfig | 3 -
rust/hw/char/meson.build | 1 -
rust/hw/char/pl011/.gitignore | 2 -
rust/hw/char/pl011/Cargo.lock | 134 -----
rust/hw/char/pl011/Cargo.toml | 26 -
rust/hw/char/pl011/README.md | 31 --
rust/hw/char/pl011/meson.build | 26 -
rust/hw/char/pl011/src/device.rs | 599 ---------------------
rust/hw/char/pl011/src/device_class.rs | 70 ---
rust/hw/char/pl011/src/lib.rs | 586 --------------------
rust/hw/char/pl011/src/memory_ops.rs | 59 --
rust/hw/meson.build | 1 -
rust/meson.build | 2 -
scripts/archive-source.sh | 4 +-
scripts/make-release | 4 +-
scripts/rust/rust_root_crate.sh | 13 -
subprojects/.gitignore | 7 -
subprojects/arbitrary-int-1-rs.wrap | 7 -
subprojects/bilge-0.2-rs.wrap | 7 -
subprojects/bilge-impl-0.2-rs.wrap | 7 -
subprojects/either-1-rs.wrap | 7 -
subprojects/itertools-0.11-rs.wrap | 7 -
.../packagefiles/arbitrary-int-1-rs/meson.build | 19 -
subprojects/packagefiles/bilge-0.2-rs/meson.build | 29 -
.../packagefiles/bilge-impl-0.2-rs/meson.build | 45 --
subprojects/packagefiles/either-1-rs/meson.build | 24 -
.../packagefiles/itertools-0.11-rs/meson.build | 30 --
.../packagefiles/proc-macro-error-1-rs/meson.build | 40 --
.../proc-macro-error-attr-1-rs/meson.build | 32 --
.../packagefiles/unicode-ident-1-rs/meson.build | 20 -
subprojects/proc-macro-error-1-rs.wrap | 7 -
subprojects/proc-macro-error-attr-1-rs.wrap | 7 -
37 files changed, 12 insertions(+), 1906 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index c3bfa132fd6ecd61dd733b760a5e1ccd39613455..793c683aa6cccafca9c98b74b9a20d84211f041b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1137,11 +1137,6 @@ F: include/hw/*/microbit*.h
F: tests/qtest/microbit-test.c
F: docs/system/arm/nrf.rst
-ARM PL011 Rust device
-M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-S: Maintained
-F: rust/hw/char/pl011/
-
AVR Machines
-------------
diff --git a/meson.build b/meson.build
index c26c417de16ad9256a019a90fd89ea990bb3f948..0d617c551e61c90c7c357c62ff5da34437723947 100644
--- a/meson.build
+++ b/meson.build
@@ -3520,7 +3520,6 @@ qom_ss = ss.source_set()
system_ss = ss.source_set()
specific_fuzz_ss = ss.source_set()
specific_ss = ss.source_set()
-rust_devices_ss = ss.source_set()
stub_ss = ss.source_set()
trace_ss = ss.source_set()
user_ss = ss.source_set()
@@ -4068,29 +4067,6 @@ foreach target : target_dirs
arch_srcs += target_specific.sources()
arch_deps += target_specific.dependencies()
- if have_rust and have_system
- target_rust = rust_devices_ss.apply(config_target, strict: false)
- crates = []
- foreach dep : target_rust.dependencies()
- crates += dep.get_variable('crate')
- endforeach
- if crates.length() > 0
- rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
- output: 'rust_' + target.underscorify() + '.rs',
- command: [find_program('scripts/rust/rust_root_crate.sh')] + crates,
- capture: true,
- build_by_default: true,
- build_always_stale: true)
- rlib = static_library('rust_' + target.underscorify(),
- rlib_rs,
- dependencies: target_rust.dependencies(),
- override_options: ['rust_std=2021', 'build.rust_std=2021'],
- rust_args: rustc_args,
- rust_abi: 'c')
- arch_deps += declare_dependency(link_whole: [rlib])
- endif
- endif
-
# allow using headers from the dependencies but do not include the sources,
# because this emulator only needs those in "objects". For external
# dependencies, the full dependency is included below in the executable.
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index e7fd9338d11dadb7a18032c674927db3d9887bdd..53eb7bb3d0157bb6e8e078fe73ec66015ae0fe13 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -20,8 +20,7 @@ config ARM_VIRT
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL031 # RTC
select PL061 # GPIO
select GPIO_PWR
@@ -74,8 +73,7 @@ config HIGHBANK
select AHCI
select ARM_TIMER # sp804
select ARM_V7M
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL022 # SPI
select PL031 # RTC
select PL061 # GPIO
@@ -88,8 +86,7 @@ config INTEGRATOR
depends on TCG && ARM
select ARM_TIMER
select INTEGRATOR_DEBUG
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL031 # RTC
select PL041 # audio
select PL050 # keyboard/mouse
@@ -107,8 +104,7 @@ config MUSCA
default y
depends on TCG && ARM
select ARMSSE
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011
select PL031
select SPLIT_IRQ
select UNIMP
@@ -172,8 +168,7 @@ config REALVIEW
select WM8750 # audio codec
select LSI_SCSI_PCI
select PCI
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL031 # RTC
select PL041 # audio codec
select PL050 # keyboard/mouse
@@ -198,8 +193,7 @@ config SBSA_REF
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL031 # RTC
select PL061 # GPIO
select USB_XHCI_SYSBUS
@@ -223,8 +217,7 @@ config STELLARIS
select ARM_V7M
select CMSDK_APB_WATCHDOG
select I2C
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL022 # SPI
select PL061 # GPIO
select SSD0303 # OLED display
@@ -284,8 +277,7 @@ config VEXPRESS
select ARM_TIMER # sp804
select LAN9118
select PFLASH_CFI01
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select PL041 # audio codec
select PL181 # display
select REALVIEW
@@ -370,8 +362,7 @@ config RASPI
default y
depends on TCG && ARM
select FRAMEBUFFER
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011 # UART
select SDHCI
select USB_DWC2
select BCM2835_SPI
@@ -447,8 +438,7 @@ config XLNX_VERSAL
select ARM_GIC
select CPU_CLUSTER
select DEVICE_TREE
- select PL011 if !HAVE_RUST # UART
- select X_PL011_RUST if HAVE_RUST # UART
+ select PL011
select CADENCE
select VIRTIO_MMIO
select UNIMP
diff --git a/rust/Kconfig b/rust/Kconfig
index f9f5c3909887451f71360a7986d79e57fdb43c91..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/rust/Kconfig
+++ b/rust/Kconfig
@@ -1 +0,0 @@
-source hw/Kconfig
diff --git a/rust/hw/Kconfig b/rust/hw/Kconfig
deleted file mode 100644
index 4d934f30afe13ddff418db8ec9e8b8eb25a9e8d0..0000000000000000000000000000000000000000
--- a/rust/hw/Kconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-# devices Kconfig
-source char/Kconfig
diff --git a/rust/hw/char/Kconfig b/rust/hw/char/Kconfig
deleted file mode 100644
index a1732a9e97fe3211547e30bc9319382e6394ed5b..0000000000000000000000000000000000000000
--- a/rust/hw/char/Kconfig
+++ /dev/null
@@ -1,3 +0,0 @@
-config X_PL011_RUST
- bool
- default y if HAVE_RUST
diff --git a/rust/hw/char/meson.build b/rust/hw/char/meson.build
deleted file mode 100644
index 5716dc43ef6facdcf1cc963108347bbf4d12cf0e..0000000000000000000000000000000000000000
--- a/rust/hw/char/meson.build
+++ /dev/null
@@ -1 +0,0 @@
-subdir('pl011')
diff --git a/rust/hw/char/pl011/.gitignore b/rust/hw/char/pl011/.gitignore
deleted file mode 100644
index 71eaff2035d5a65b57ae32dfeecf3d87bbc7b396..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# Ignore generated bindings file overrides.
-src/bindings.rs.inc
diff --git a/rust/hw/char/pl011/Cargo.lock b/rust/hw/char/pl011/Cargo.lock
deleted file mode 100644
index b58cebb186e99efe184117bb931a341543d4466b..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/Cargo.lock
+++ /dev/null
@@ -1,134 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "arbitrary-int"
-version = "1.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d"
-
-[[package]]
-name = "bilge"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57"
-dependencies = [
- "arbitrary-int",
- "bilge-impl",
-]
-
-[[package]]
-name = "bilge-impl"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8"
-dependencies = [
- "itertools",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "either"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
-
-[[package]]
-name = "itertools"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "pl011"
-version = "0.1.0"
-dependencies = [
- "bilge",
- "bilge-impl",
- "qemu_api",
- "qemu_api_macros",
-]
-
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.84"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "qemu_api"
-version = "0.1.0"
-
-[[package]]
-name = "qemu_api_macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.66"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
-
-[[package]]
-name = "version_check"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml
deleted file mode 100644
index b089e3dded623131ee13b4af8145b84388755df7..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/Cargo.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[package]
-name = "pl011"
-version = "0.1.0"
-edition = "2021"
-authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
-license = "GPL-2.0-or-later"
-readme = "README.md"
-homepage = "https://www.qemu.org"
-description = "pl011 device model for QEMU"
-repository = "https://gitlab.com/epilys/rust-for-qemu"
-resolver = "2"
-publish = false
-keywords = []
-categories = []
-
-[lib]
-crate-type = ["staticlib"]
-
-[dependencies]
-bilge = { version = "0.2.0" }
-bilge-impl = { version = "0.2.0" }
-qemu_api = { path = "../../../qemu-api" }
-qemu_api_macros = { path = "../../../qemu-api-macros" }
-
-# Do not include in any global workspace
-[workspace]
diff --git a/rust/hw/char/pl011/README.md b/rust/hw/char/pl011/README.md
deleted file mode 100644
index cd7dea31634241cbf96b0be13f21d52bbd8ae750..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# PL011 QEMU Device Model
-
-This library implements a device model for the PrimeCell® UART (PL011)
-device in QEMU.
-
-## Build static lib
-
-Host build target must be explicitly specified:
-
-```sh
-cargo build --target x86_64-unknown-linux-gnu
-```
-
-Replace host target triplet if necessary.
-
-## Generate Rust documentation
-
-To generate docs for this crate, including private items:
-
-```sh
-cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
-```
-
-To include direct dependencies like `bilge` (bitmaps for register types):
-
-```sh
-cargo tree --depth 1 -e normal --prefix none \
- | cut -d' ' -f1 \
- | xargs printf -- '-p %s\n' \
- | xargs cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
-```
diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build
deleted file mode 100644
index 547cca5a96f7eef284caf1949380b65f7d015d92..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/meson.build
+++ /dev/null
@@ -1,26 +0,0 @@
-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,
- qemu_api,
- qemu_api_macros,
- ],
-)
-
-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],
- variables: {'crate': 'pl011'},
-)])
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
deleted file mode 100644
index c7193b41beec0b177dbc75ac0e43fcfea4c82bfb..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/src/device.rs
+++ /dev/null
@@ -1,599 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use core::{
- ffi::{c_int, c_uchar, c_uint, c_void, CStr},
- ptr::{addr_of, addr_of_mut, NonNull},
-};
-
-use qemu_api::{
- bindings::{self, *},
- definitions::ObjectImpl,
-};
-
-use crate::{
- memory_ops::PL011_OPS,
- registers::{self, Interrupt},
- RegisterOffset,
-};
-
-static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
-
-const DATA_BREAK: u32 = 1 << 10;
-
-/// QEMU sourced constant.
-pub const PL011_FIFO_DEPTH: usize = 16_usize;
-
-#[repr(C)]
-#[derive(Debug, qemu_api_macros::Object)]
-/// PL011 Device Model in QEMU
-pub struct PL011State {
- pub parent_obj: SysBusDevice,
- pub iomem: MemoryRegion,
- #[doc(alias = "fr")]
- pub flags: registers::Flags,
- #[doc(alias = "lcr")]
- pub line_control: registers::LineControl,
- #[doc(alias = "rsr")]
- pub receive_status_error_clear: registers::ReceiveStatusErrorClear,
- #[doc(alias = "cr")]
- pub control: registers::Control,
- pub dmacr: u32,
- pub int_enabled: u32,
- pub int_level: u32,
- pub read_fifo: [u32; PL011_FIFO_DEPTH],
- pub ilpr: u32,
- pub ibrd: u32,
- pub fbrd: u32,
- pub ifl: u32,
- pub read_pos: usize,
- pub read_count: usize,
- pub read_trigger: usize,
- #[doc(alias = "chr")]
- pub char_backend: CharBackend,
- /// QEMU interrupts
- ///
- /// ```text
- /// * sysbus MMIO region 0: device registers
- /// * sysbus IRQ 0: `UARTINTR` (combined interrupt line)
- /// * sysbus IRQ 1: `UARTRXINTR` (receive FIFO interrupt line)
- /// * sysbus IRQ 2: `UARTTXINTR` (transmit FIFO interrupt line)
- /// * sysbus IRQ 3: `UARTRTINTR` (receive timeout interrupt line)
- /// * sysbus IRQ 4: `UARTMSINTR` (momem status interrupt line)
- /// * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
- /// ```
- #[doc(alias = "irq")]
- pub interrupts: [qemu_irq; 6usize],
- #[doc(alias = "clk")]
- pub clock: NonNull<Clock>,
- #[doc(alias = "migrate_clk")]
- pub migrate_clock: bool,
-}
-
-impl ObjectImpl for PL011State {
- type Class = PL011Class;
- const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
- const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
- const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE);
- const ABSTRACT: bool = false;
- const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init);
- const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
- const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
-}
-
-#[repr(C)]
-pub struct PL011Class {
- _inner: [u8; 0],
-}
-
-impl qemu_api::definitions::Class for PL011Class {
- const CLASS_INIT: Option<
- unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
- > = Some(crate::device_class::pl011_class_init);
- const CLASS_BASE_INIT: Option<
- unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
- > = None;
-}
-
-#[used]
-pub static CLK_NAME: &CStr = c"clk";
-
-impl PL011State {
- /// Initializes a pre-allocated, unitialized instance of `PL011State`.
- ///
- /// # Safety
- ///
- /// `self` must point to a correctly sized and aligned location for the
- /// `PL011State` type. It must not be called more than once on the same
- /// location/instance. All its fields are expected to hold unitialized
- /// values with the sole exception of `parent_obj`.
- pub unsafe fn init(&mut self) {
- let dev = addr_of_mut!(*self).cast::<DeviceState>();
- // SAFETY:
- //
- // self and self.iomem are guaranteed to be valid at this point since callers
- // must make sure the `self` reference is valid.
- unsafe {
- memory_region_init_io(
- addr_of_mut!(self.iomem),
- addr_of_mut!(*self).cast::<Object>(),
- &PL011_OPS,
- addr_of_mut!(*self).cast::<c_void>(),
- Self::TYPE_INFO.name,
- 0x1000,
- );
- let sbd = addr_of_mut!(*self).cast::<SysBusDevice>();
- sysbus_init_mmio(sbd, addr_of_mut!(self.iomem));
- for irq in self.interrupts.iter_mut() {
- sysbus_init_irq(sbd, irq);
- }
- }
- // SAFETY:
- //
- // self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
- // we can overwrite the undefined value without side effects. This is
- // safe since all PL011State instances are created by QOM code which
- // calls this function to initialize the fields; therefore no code is
- // able to access an invalid self.clock value.
- unsafe {
- self.clock = NonNull::new(qdev_init_clock_in(
- dev,
- CLK_NAME.as_ptr(),
- None, /* pl011_clock_update */
- addr_of_mut!(*self).cast::<c_void>(),
- ClockEvent::ClockUpdate.0,
- ))
- .unwrap();
- }
- }
-
- pub fn read(
- &mut self,
- offset: hwaddr,
- _size: core::ffi::c_uint,
- ) -> std::ops::ControlFlow<u64, u64> {
- use RegisterOffset::*;
-
- std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
- Err(v) if (0x3f8..0x400).contains(&v) => {
- u64::from(PL011_ID_ARM[((offset - 0xfe0) >> 2) as usize])
- }
- Err(_) => {
- // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
- 0
- }
- Ok(DR) => {
- // s->flags &= ~PL011_FLAG_RXFF;
- self.flags.set_receive_fifo_full(false);
- let c = self.read_fifo[self.read_pos];
- if self.read_count > 0 {
- self.read_count -= 1;
- self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
- }
- if self.read_count == 0 {
- // self.flags |= PL011_FLAG_RXFE;
- self.flags.set_receive_fifo_empty(true);
- }
- if self.read_count + 1 == self.read_trigger {
- //self.int_level &= ~ INT_RX;
- self.int_level &= !registers::INT_RX;
- }
- // Update error bits.
- self.receive_status_error_clear = c.to_be_bytes()[3].into();
- self.update();
- // Must call qemu_chr_fe_accept_input, so return Continue:
- return std::ops::ControlFlow::Continue(c.into());
- }
- Ok(RSR) => u8::from(self.receive_status_error_clear).into(),
- Ok(FR) => u16::from(self.flags).into(),
- Ok(FBRD) => self.fbrd.into(),
- Ok(ILPR) => self.ilpr.into(),
- Ok(IBRD) => self.ibrd.into(),
- Ok(LCR_H) => u16::from(self.line_control).into(),
- Ok(CR) => {
- // We exercise our self-control.
- u16::from(self.control).into()
- }
- Ok(FLS) => self.ifl.into(),
- Ok(IMSC) => self.int_enabled.into(),
- Ok(RIS) => self.int_level.into(),
- Ok(MIS) => u64::from(self.int_level & self.int_enabled),
- Ok(ICR) => {
- // "The UARTICR Register is the interrupt clear register and is write-only"
- // Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR
- 0
- }
- Ok(DMACR) => self.dmacr.into(),
- })
- }
-
- pub fn write(&mut self, offset: hwaddr, value: u64) {
- // eprintln!("write offset {offset} value {value}");
- use RegisterOffset::*;
- let value: u32 = value as u32;
- match RegisterOffset::try_from(offset) {
- Err(_bad_offset) => {
- eprintln!("write bad offset {offset} value {value}");
- }
- Ok(DR) => {
- // ??? Check if transmitter is enabled.
- let ch: u8 = value as u8;
- // XXX this blocks entire thread. Rewrite to use
- // qemu_chr_fe_write and background I/O callbacks
-
- // SAFETY: self.char_backend is a valid CharBackend instance after it's been
- // initialized in realize().
- unsafe {
- qemu_chr_fe_write_all(addr_of_mut!(self.char_backend), &ch, 1);
- }
- self.loopback_tx(value);
- self.int_level |= registers::INT_TX;
- self.update();
- }
- Ok(RSR) => {
- self.receive_status_error_clear = 0.into();
- }
- Ok(FR) => {
- // flag writes are ignored
- }
- Ok(ILPR) => {
- self.ilpr = value;
- }
- Ok(IBRD) => {
- self.ibrd = value;
- }
- Ok(FBRD) => {
- self.fbrd = value;
- }
- Ok(LCR_H) => {
- let value = value as u16;
- let new_val: registers::LineControl = value.into();
- // Reset the FIFO state on FIFO enable or disable
- if bool::from(self.line_control.fifos_enabled())
- ^ bool::from(new_val.fifos_enabled())
- {
- self.reset_fifo();
- }
- if self.line_control.send_break() ^ new_val.send_break() {
- let mut break_enable: c_int = new_val.send_break().into();
- // SAFETY: self.char_backend is a valid CharBackend instance after it's been
- // initialized in realize().
- unsafe {
- qemu_chr_fe_ioctl(
- addr_of_mut!(self.char_backend),
- CHR_IOCTL_SERIAL_SET_BREAK as i32,
- addr_of_mut!(break_enable).cast::<c_void>(),
- );
- }
- self.loopback_break(break_enable > 0);
- }
- self.line_control = new_val;
- self.set_read_trigger();
- }
- Ok(CR) => {
- // ??? Need to implement the enable bit.
- let value = value as u16;
- self.control = value.into();
- self.loopback_mdmctrl();
- }
- Ok(FLS) => {
- self.ifl = value;
- self.set_read_trigger();
- }
- Ok(IMSC) => {
- self.int_enabled = value;
- self.update();
- }
- Ok(RIS) => {}
- Ok(MIS) => {}
- Ok(ICR) => {
- self.int_level &= !value;
- self.update();
- }
- Ok(DMACR) => {
- self.dmacr = value;
- if value & 3 > 0 {
- // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
- eprintln!("pl011: DMA not implemented");
- }
- }
- }
- }
-
- #[inline]
- fn loopback_tx(&mut self, value: u32) {
- if !self.loopback_enabled() {
- return;
- }
-
- // Caveat:
- //
- // In real hardware, TX loopback happens at the serial-bit level
- // and then reassembled by the RX logics back into bytes and placed
- // into the RX fifo. That is, loopback happens after TX fifo.
- //
- // Because the real hardware TX fifo is time-drained at the frame
- // rate governed by the configured serial format, some loopback
- // bytes in TX fifo may still be able to get into the RX fifo
- // that could be full at times while being drained at software
- // pace.
- //
- // In such scenario, the RX draining pace is the major factor
- // deciding which loopback bytes get into the RX fifo, unless
- // hardware flow-control is enabled.
- //
- // For simplicity, the above described is not emulated.
- self.put_fifo(value);
- }
-
- fn loopback_mdmctrl(&mut self) {
- if !self.loopback_enabled() {
- return;
- }
-
- /*
- * Loopback software-driven modem control outputs to modem status inputs:
- * FR.RI <= CR.Out2
- * FR.DCD <= CR.Out1
- * FR.CTS <= CR.RTS
- * FR.DSR <= CR.DTR
- *
- * The loopback happens immediately even if this call is triggered
- * by setting only CR.LBE.
- *
- * CTS/RTS updates due to enabled hardware flow controls are not
- * dealt with here.
- */
-
- //fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
- // PL011_FLAG_DSR | PL011_FLAG_CTS);
- //fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
- //fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
- //fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
- //fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
- //
- self.flags.set_ring_indicator(self.control.out_2());
- self.flags.set_data_carrier_detect(self.control.out_1());
- self.flags.set_clear_to_send(self.control.request_to_send());
- self.flags
- .set_data_set_ready(self.control.data_transmit_ready());
-
- // Change interrupts based on updated FR
- let mut il = self.int_level;
-
- il &= !Interrupt::MS;
- //il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
- //il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
- //il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
- //il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
-
- if self.flags.data_set_ready() {
- il |= Interrupt::DSR as u32;
- }
- if self.flags.data_carrier_detect() {
- il |= Interrupt::DCD as u32;
- }
- if self.flags.clear_to_send() {
- il |= Interrupt::CTS as u32;
- }
- if self.flags.ring_indicator() {
- il |= Interrupt::RI as u32;
- }
- self.int_level = il;
- self.update();
- }
-
- fn loopback_break(&mut self, enable: bool) {
- if enable {
- self.loopback_tx(DATA_BREAK);
- }
- }
-
- fn set_read_trigger(&mut self) {
- self.read_trigger = 1;
- }
-
- pub fn realize(&mut self) {
- // SAFETY: self.char_backend has the correct size and alignment for a
- // CharBackend object, and its callbacks are of the correct types.
- unsafe {
- qemu_chr_fe_set_handlers(
- addr_of_mut!(self.char_backend),
- Some(pl011_can_receive),
- Some(pl011_receive),
- Some(pl011_event),
- None,
- addr_of_mut!(*self).cast::<c_void>(),
- core::ptr::null_mut(),
- true,
- );
- }
- }
-
- pub fn reset(&mut self) {
- self.line_control.reset();
- self.receive_status_error_clear.reset();
- self.dmacr = 0;
- self.int_enabled = 0;
- self.int_level = 0;
- self.ilpr = 0;
- self.ibrd = 0;
- self.fbrd = 0;
- self.read_trigger = 1;
- self.ifl = 0x12;
- self.control.reset();
- self.flags = 0.into();
- self.reset_fifo();
- }
-
- pub fn reset_fifo(&mut self) {
- self.read_count = 0;
- self.read_pos = 0;
-
- /* Reset FIFO flags */
- self.flags.reset();
- }
-
- pub fn can_receive(&self) -> bool {
- // trace_pl011_can_receive(s->lcr, s->read_count, r);
- self.read_count < self.fifo_depth()
- }
-
- pub fn event(&mut self, event: QEMUChrEvent) {
- if event == bindings::QEMUChrEvent::CHR_EVENT_BREAK && !self.fifo_enabled() {
- self.put_fifo(DATA_BREAK);
- self.receive_status_error_clear.set_break_error(true);
- }
- }
-
- #[inline]
- pub fn fifo_enabled(&self) -> bool {
- matches!(self.line_control.fifos_enabled(), registers::Mode::FIFO)
- }
-
- #[inline]
- pub fn loopback_enabled(&self) -> bool {
- self.control.enable_loopback()
- }
-
- #[inline]
- pub fn fifo_depth(&self) -> usize {
- // Note: FIFO depth is expected to be power-of-2
- if self.fifo_enabled() {
- return PL011_FIFO_DEPTH;
- }
- 1
- }
-
- pub fn put_fifo(&mut self, value: c_uint) {
- let depth = self.fifo_depth();
- assert!(depth > 0);
- let slot = (self.read_pos + self.read_count) & (depth - 1);
- self.read_fifo[slot] = value;
- self.read_count += 1;
- // s->flags &= ~PL011_FLAG_RXFE;
- self.flags.set_receive_fifo_empty(false);
- if self.read_count == depth {
- //s->flags |= PL011_FLAG_RXFF;
- self.flags.set_receive_fifo_full(true);
- }
-
- if self.read_count == self.read_trigger {
- self.int_level |= registers::INT_RX;
- self.update();
- }
- }
-
- pub fn update(&self) {
- let flags = self.int_level & self.int_enabled;
- for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
- // SAFETY: self.interrupts have been initialized in init().
- unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
- }
- }
-}
-
-/// Which bits in the interrupt status matter for each outbound IRQ line ?
-pub const IRQMASK: [u32; 6] = [
- /* combined IRQ */
- Interrupt::E
- | Interrupt::MS
- | Interrupt::RT as u32
- | Interrupt::TX as u32
- | Interrupt::RX as u32,
- Interrupt::RX as u32,
- Interrupt::TX as u32,
- Interrupt::RT as u32,
- Interrupt::MS,
- Interrupt::E,
-];
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
- unsafe {
- debug_assert!(!opaque.is_null());
- let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- state.as_ref().can_receive().into()
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-///
-/// The buffer and size arguments must also be valid.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_receive(
- opaque: *mut core::ffi::c_void,
- buf: *const u8,
- size: core::ffi::c_int,
-) {
- unsafe {
- debug_assert!(!opaque.is_null());
- let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- if state.as_ref().loopback_enabled() {
- return;
- }
- if size > 0 {
- debug_assert!(!buf.is_null());
- state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
- }
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
- unsafe {
- debug_assert!(!opaque.is_null());
- let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- state.as_mut().event(event)
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer for `chr`.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_create(
- addr: u64,
- irq: qemu_irq,
- chr: *mut Chardev,
-) -> *mut DeviceState {
- unsafe {
- let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
- let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
-
- qdev_prop_set_chr(dev, bindings::TYPE_CHARDEV.as_ptr(), chr);
- sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut Error);
- sysbus_mmio_map(sysbus, 0, addr);
- sysbus_connect_irq(sysbus, 0, irq);
- dev
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
- unsafe {
- debug_assert!(!obj.is_null());
- let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
- state.as_mut().init();
- }
-}
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
deleted file mode 100644
index b7ab31af02d7bb50ae94be0b153baafc7ccfa375..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/src/device_class.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use core::ptr::NonNull;
-
-use qemu_api::{bindings::*, definitions::ObjectImpl};
-
-use crate::device::PL011State;
-
-#[used]
-pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
- name: PL011State::TYPE_INFO.name,
- unmigratable: true,
- ..unsafe { ::core::mem::MaybeUninit::<VMStateDescription>::zeroed().assume_init() }
-};
-
-qemu_api::declare_properties! {
- PL011_PROPERTIES,
- qemu_api::define_property!(
- c"chardev",
- PL011State,
- char_backend,
- unsafe { &qdev_prop_chr },
- CharBackend
- ),
- qemu_api::define_property!(
- c"migrate-clk",
- PL011State,
- migrate_clock,
- unsafe { &qdev_prop_bool },
- bool
- ),
-}
-
-qemu_api::device_class_init! {
- pl011_class_init,
- props => PL011_PROPERTIES,
- realize_fn => Some(pl011_realize),
- legacy_reset_fn => Some(pl011_reset),
- vmsd => VMSTATE_PL011,
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
- unsafe {
- assert!(!dev.is_null());
- let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
- state.as_mut().realize();
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
- unsafe {
- assert!(!dev.is_null());
- let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
- state.as_mut().reset();
- }
-}
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
deleted file mode 100644
index 2939ee50c99ceaacf6ec68127272d58814e33679..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/src/lib.rs
+++ /dev/null
@@ -1,586 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-//
-// PL011 QEMU Device Model
-//
-// This library implements a device model for the PrimeCell® UART (PL011)
-// device in QEMU.
-//
-#![doc = include_str!("../README.md")]
-//! # Library crate
-//!
-//! See [`PL011State`](crate::device::PL011State) for the device model type and
-//! the [`registers`] module for register types.
-
-#![deny(
- rustdoc::broken_intra_doc_links,
- rustdoc::redundant_explicit_links,
- clippy::correctness,
- clippy::suspicious,
- clippy::complexity,
- clippy::perf,
- clippy::cargo,
- clippy::nursery,
- clippy::style,
- // restriction group
- clippy::dbg_macro,
- clippy::as_underscore,
- clippy::assertions_on_result_states,
- // pedantic group
- clippy::doc_markdown,
- clippy::borrow_as_ptr,
- clippy::cast_lossless,
- clippy::option_if_let_else,
- clippy::missing_const_for_fn,
- clippy::cognitive_complexity,
- clippy::missing_safety_doc,
- )]
-
-extern crate bilge;
-extern crate bilge_impl;
-extern crate qemu_api;
-
-pub mod device;
-pub mod device_class;
-pub mod memory_ops;
-
-pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
-
-/// Offset of each register from the base memory address of the device.
-///
-/// # Source
-/// ARM DDI 0183G, Table 3-1 p.3-3
-#[doc(alias = "offset")]
-#[allow(non_camel_case_types)]
-#[repr(u64)]
-#[derive(Debug)]
-pub enum RegisterOffset {
- /// Data Register
- ///
- /// A write to this register initiates the actual data transmission
- #[doc(alias = "UARTDR")]
- DR = 0x000,
- /// Receive Status Register or Error Clear Register
- #[doc(alias = "UARTRSR")]
- #[doc(alias = "UARTECR")]
- RSR = 0x004,
- /// Flag Register
- ///
- /// A read of this register shows if transmission is complete
- #[doc(alias = "UARTFR")]
- FR = 0x018,
- /// Fractional Baud Rate Register
- ///
- /// responsible for baud rate speed
- #[doc(alias = "UARTFBRD")]
- FBRD = 0x028,
- /// `IrDA` Low-Power Counter Register
- #[doc(alias = "UARTILPR")]
- ILPR = 0x020,
- /// Integer Baud Rate Register
- ///
- /// Responsible for baud rate speed
- #[doc(alias = "UARTIBRD")]
- IBRD = 0x024,
- /// line control register (data frame format)
- #[doc(alias = "UARTLCR_H")]
- LCR_H = 0x02C,
- /// Toggle UART, transmission or reception
- #[doc(alias = "UARTCR")]
- CR = 0x030,
- /// Interrupt FIFO Level Select Register
- #[doc(alias = "UARTIFLS")]
- FLS = 0x034,
- /// Interrupt Mask Set/Clear Register
- #[doc(alias = "UARTIMSC")]
- IMSC = 0x038,
- /// Raw Interrupt Status Register
- #[doc(alias = "UARTRIS")]
- RIS = 0x03C,
- /// Masked Interrupt Status Register
- #[doc(alias = "UARTMIS")]
- MIS = 0x040,
- /// Interrupt Clear Register
- #[doc(alias = "UARTICR")]
- ICR = 0x044,
- /// DMA control Register
- #[doc(alias = "UARTDMACR")]
- DMACR = 0x048,
- ///// Reserved, offsets `0x04C` to `0x07C`.
- //Reserved = 0x04C,
-}
-
-impl core::convert::TryFrom<u64> for RegisterOffset {
- type Error = u64;
-
- fn try_from(value: u64) -> Result<Self, Self::Error> {
- macro_rules! case {
- ($($discriminant:ident),*$(,)*) => {
- /* check that matching on all macro arguments compiles, which means we are not
- * missing any enum value; if the type definition ever changes this will stop
- * compiling.
- */
- const fn _assert_exhaustive(val: RegisterOffset) {
- match val {
- $(RegisterOffset::$discriminant => (),)*
- }
- }
-
- match value {
- $(x if x == Self::$discriminant as u64 => Ok(Self::$discriminant),)*
- _ => Err(value),
- }
- }
- }
- case! { DR, RSR, FR, FBRD, ILPR, IBRD, LCR_H, CR, FLS, IMSC, RIS, MIS, ICR, DMACR }
- }
-}
-
-pub mod registers {
- //! Device registers exposed as typed structs which are backed by arbitrary
- //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc.
- //!
- //! All PL011 registers are essentially 32-bit wide, but are typed here as
- //! bitmaps with only the necessary width. That is, if a struct bitmap
- //! in this module is for example 16 bits long, it should be conceived
- //! as a 32-bit register where the unmentioned higher bits are always
- //! unused thus treated as zero when read or written.
- use bilge::prelude::*;
-
- // TODO: FIFO Mode has different semantics
- /// Data Register, `UARTDR`
- ///
- /// The `UARTDR` register is the data register.
- ///
- /// For words to be transmitted:
- ///
- /// - if the FIFOs are enabled, data written to this location is pushed onto
- /// the transmit
- /// FIFO
- /// - if the FIFOs are not enabled, data is stored in the transmitter
- /// holding register (the
- /// bottom word of the transmit FIFO).
- ///
- /// The write operation initiates transmission from the UART. The data is
- /// prefixed with a start bit, appended with the appropriate parity bit
- /// (if parity is enabled), and a stop bit. The resultant word is then
- /// transmitted.
- ///
- /// For received words:
- ///
- /// - if the FIFOs are enabled, the data byte and the 4-bit status (break,
- /// frame, parity,
- /// and overrun) is pushed onto the 12-bit wide receive FIFO
- /// - if the FIFOs are not enabled, the data byte and status are stored in
- /// the receiving
- /// holding register (the bottom word of the receive FIFO).
- ///
- /// The received data byte is read by performing reads from the `UARTDR`
- /// register along with the corresponding status information. The status
- /// information can also be read by a read of the `UARTRSR/UARTECR`
- /// register.
- ///
- /// # Note
- ///
- /// You must disable the UART before any of the control registers are
- /// reprogrammed. When the UART is disabled in the middle of
- /// transmission or reception, it completes the current character before
- /// stopping.
- ///
- /// # Source
- /// ARM DDI 0183G 3.3.1 Data Register, UARTDR
- #[bitsize(16)]
- #[derive(Clone, Copy, DebugBits, FromBits)]
- #[doc(alias = "UARTDR")]
- pub struct Data {
- _reserved: u4,
- pub data: u8,
- pub framing_error: bool,
- pub parity_error: bool,
- pub break_error: bool,
- pub overrun_error: bool,
- }
-
- // TODO: FIFO Mode has different semantics
- /// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR`
- ///
- /// The UARTRSR/UARTECR register is the receive status register/error clear
- /// register. Receive status can also be read from the `UARTRSR`
- /// register. If the status is read from this register, then the status
- /// information for break, framing and parity corresponds to the
- /// data character read from the [Data register](Data), `UARTDR` prior to
- /// reading the UARTRSR register. The status information for overrun is
- /// set immediately when an overrun condition occurs.
- ///
- ///
- /// # Note
- /// The received data character must be read first from the [Data
- /// Register](Data), `UARTDR` before reading the error status associated
- /// with that data character from the `UARTRSR` register. This read
- /// sequence cannot be reversed, because the `UARTRSR` register is
- /// updated only when a read occurs from the `UARTDR` register. However,
- /// the status information can also be obtained by reading the `UARTDR`
- /// register
- ///
- /// # Source
- /// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register,
- /// UARTRSR/UARTECR
- #[bitsize(8)]
- #[derive(Clone, Copy, DebugBits, FromBits)]
- pub struct ReceiveStatusErrorClear {
- pub framing_error: bool,
- pub parity_error: bool,
- pub break_error: bool,
- pub overrun_error: bool,
- _reserved_unpredictable: u4,
- }
-
- impl ReceiveStatusErrorClear {
- pub fn reset(&mut self) {
- // All the bits are cleared to 0 on reset.
- *self = 0.into();
- }
- }
-
- impl Default for ReceiveStatusErrorClear {
- fn default() -> Self {
- 0.into()
- }
- }
-
- #[bitsize(16)]
- #[derive(Clone, Copy, DebugBits, FromBits)]
- /// Flag Register, `UARTFR`
- #[doc(alias = "UARTFR")]
- pub struct Flags {
- /// CTS Clear to send. This bit is the complement of the UART clear to
- /// send, `nUARTCTS`, modem status input. That is, the bit is 1
- /// when `nUARTCTS` is LOW.
- pub clear_to_send: bool,
- /// DSR Data set ready. This bit is the complement of the UART data set
- /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when
- /// `nUARTDSR` is LOW.
- pub data_set_ready: bool,
- /// DCD Data carrier detect. This bit is the complement of the UART data
- /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is
- /// 1 when `nUARTDCD` is LOW.
- pub data_carrier_detect: bool,
- /// BUSY UART busy. If this bit is set to 1, the UART is busy
- /// transmitting data. This bit remains set until the complete
- /// byte, including all the stop bits, has been sent from the
- /// shift register. This bit is set as soon as the transmit FIFO
- /// becomes non-empty, regardless of whether the UART is enabled
- /// or not.
- pub busy: bool,
- /// RXFE Receive FIFO empty. The meaning of this bit depends on the
- /// state of the FEN bit in the UARTLCR_H register. If the FIFO
- /// is disabled, this bit is set when the receive holding
- /// register is empty. If the FIFO is enabled, the RXFE bit is
- /// set when the receive FIFO is empty.
- pub receive_fifo_empty: bool,
- /// TXFF Transmit FIFO full. The meaning of this bit depends on the
- /// state of the FEN bit in the UARTLCR_H register. If the FIFO
- /// is disabled, this bit is set when the transmit holding
- /// register is full. If the FIFO is enabled, the TXFF bit is
- /// set when the transmit FIFO is full.
- pub transmit_fifo_full: bool,
- /// RXFF Receive FIFO full. The meaning of this bit depends on the state
- /// of the FEN bit in the UARTLCR_H register. If the FIFO is
- /// disabled, this bit is set when the receive holding register
- /// is full. If the FIFO is enabled, the RXFF bit is set when
- /// the receive FIFO is full.
- pub receive_fifo_full: bool,
- /// Transmit FIFO empty. The meaning of this bit depends on the state of
- /// the FEN bit in the [Line Control register](LineControl),
- /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the
- /// transmit holding register is empty. If the FIFO is enabled,
- /// the TXFE bit is set when the transmit FIFO is empty. This
- /// bit does not indicate if there is data in the transmit shift
- /// register.
- pub transmit_fifo_empty: bool,
- /// `RI`, is `true` when `nUARTRI` is `LOW`.
- pub ring_indicator: bool,
- _reserved_zero_no_modify: u7,
- }
-
- impl Flags {
- pub fn reset(&mut self) {
- // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1
- self.set_receive_fifo_full(false);
- self.set_transmit_fifo_full(false);
- self.set_busy(false);
- self.set_receive_fifo_empty(true);
- self.set_transmit_fifo_empty(true);
- }
- }
-
- impl Default for Flags {
- fn default() -> Self {
- let mut ret: Self = 0.into();
- ret.reset();
- ret
- }
- }
-
- #[bitsize(16)]
- #[derive(Clone, Copy, DebugBits, FromBits)]
- /// Line Control Register, `UARTLCR_H`
- #[doc(alias = "UARTLCR_H")]
- pub struct LineControl {
- /// 15:8 - Reserved, do not modify, read as zero.
- _reserved_zero_no_modify: u8,
- /// 7 SPS Stick parity select.
- /// 0 = stick parity is disabled
- /// 1 = either:
- /// • if the EPS bit is 0 then the parity bit is transmitted and checked
- /// as a 1 • if the EPS bit is 1 then the parity bit is
- /// transmitted and checked as a 0. This bit has no effect when
- /// the PEN bit disables parity checking and generation. See Table 3-11
- /// on page 3-14 for the parity truth table.
- pub sticky_parity: bool,
- /// WLEN Word length. These bits indicate the number of data bits
- /// transmitted or received in a frame as follows: b11 = 8 bits
- /// b10 = 7 bits
- /// b01 = 6 bits
- /// b00 = 5 bits.
- pub word_length: WordLength,
- /// FEN Enable FIFOs:
- /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
- /// 1-byte-deep holding registers 1 = transmit and receive FIFO
- /// buffers are enabled (FIFO mode).
- pub fifos_enabled: Mode,
- /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits
- /// are transmitted at the end of the frame. The receive
- /// logic does not check for two stop bits being received.
- pub two_stops_bits: bool,
- /// EPS Even parity select. Controls the type of parity the UART uses
- /// during transmission and reception:
- /// - 0 = odd parity. The UART generates or checks for an odd number of
- /// 1s in the data and parity bits.
- /// - 1 = even parity. The UART generates or checks for an even number
- /// of 1s in the data and parity bits.
- /// This bit has no effect when the `PEN` bit disables parity checking
- /// and generation. See Table 3-11 on page 3-14 for the parity
- /// truth table.
- pub parity: Parity,
- /// 1 PEN Parity enable:
- ///
- /// - 0 = parity is disabled and no parity bit added to the data frame
- /// - 1 = parity checking and generation is enabled.
- ///
- /// See Table 3-11 on page 3-14 for the parity truth table.
- pub parity_enabled: bool,
- /// BRK Send break.
- ///
- /// If this bit is set to `1`, a low-level is continually output on the
- /// `UARTTXD` output, after completing transmission of the
- /// current character. For the proper execution of the break command,
- /// the software must set this bit for at least two complete
- /// frames. For normal use, this bit must be cleared to `0`.
- pub send_break: bool,
- }
-
- impl LineControl {
- pub fn reset(&mut self) {
- // All the bits are cleared to 0 when reset.
- *self = 0.into();
- }
- }
-
- impl Default for LineControl {
- fn default() -> Self {
- 0.into()
- }
- }
-
- #[bitsize(1)]
- #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
- /// `EPS` "Even parity select", field of [Line Control
- /// register](LineControl).
- pub enum Parity {
- /// - 0 = odd parity. The UART generates or checks for an odd number of
- /// 1s in the data and parity bits.
- Odd = 0,
- /// - 1 = even parity. The UART generates or checks for an even number
- /// of 1s in the data and parity bits.
- Even = 1,
- }
-
- #[bitsize(1)]
- #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
- /// `FEN` "Enable FIFOs" or Device mode, field of [Line Control
- /// register](LineControl).
- pub enum Mode {
- /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
- /// 1-byte-deep holding registers
- Character = 0,
- /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode).
- FIFO = 1,
- }
-
- impl From<Mode> for bool {
- fn from(val: Mode) -> Self {
- matches!(val, Mode::FIFO)
- }
- }
-
- #[bitsize(2)]
- #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
- /// `WLEN` Word length, field of [Line Control register](LineControl).
- ///
- /// These bits indicate the number of data bits transmitted or received in a
- /// frame as follows:
- pub enum WordLength {
- /// b11 = 8 bits
- _8Bits = 0b11,
- /// b10 = 7 bits
- _7Bits = 0b10,
- /// b01 = 6 bits
- _6Bits = 0b01,
- /// b00 = 5 bits.
- _5Bits = 0b00,
- }
-
- /// Control Register, `UARTCR`
- ///
- /// The `UARTCR` register is the control register. All the bits are cleared
- /// to `0` on reset except for bits `9` and `8` that are set to `1`.
- ///
- /// # Source
- /// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12
- #[bitsize(16)]
- #[doc(alias = "UARTCR")]
- #[derive(Clone, Copy, DebugBits, FromBits)]
- pub struct Control {
- /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled
- /// in the middle of transmission or reception, it completes the current
- /// character before stopping. 1 = the UART is enabled. Data
- /// transmission and reception occurs for either UART signals or SIR
- /// signals depending on the setting of the SIREN bit.
- pub enable_uart: bool,
- /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT`
- /// remains LOW (no light pulse generated), and signal transitions on
- /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is
- /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH,
- /// in the marking state. Signal transitions on UARTRXD or modem status
- /// inputs have no effect. This bit has no effect if the UARTEN bit
- /// disables the UART.
- pub enable_sir: bool,
- /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding
- /// mode. If this bit is cleared to 0, low-level bits are transmitted as
- /// an active high pulse with a width of 3/ 16th of the bit period. If
- /// this bit is set to 1, low-level bits are transmitted with a pulse
- /// width that is 3 times the period of the IrLPBaud16 input signal,
- /// regardless of the selected bit rate. Setting this bit uses less
- /// power, but might reduce transmission distances.
- pub sir_lowpower_irda_mode: u1,
- /// Reserved, do not modify, read as zero.
- _reserved_zero_no_modify: u4,
- /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is
- /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR
- /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed
- /// through to the SIRIN path. The SIRTEST bit in the test register must
- /// be set to 1 to override the normal half-duplex SIR operation. This
- /// must be the requirement for accessing the test registers during
- /// normal operation, and SIRTEST must be cleared to 0 when loopback
- /// testing is finished. This feature reduces the amount of external
- /// coupling required during system test. If this bit is set to 1, and
- /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the
- /// UARTRXD path. In either SIR mode or UART mode, when this bit is set,
- /// the modem outputs are also fed through to the modem inputs. This bit
- /// is cleared to 0 on reset, to disable loopback.
- pub enable_loopback: bool,
- /// `TXE` Transmit enable. If this bit is set to 1, the transmit section
- /// of the UART is enabled. Data transmission occurs for either UART
- /// signals, or SIR signals depending on the setting of the SIREN bit.
- /// When the UART is disabled in the middle of transmission, it
- /// completes the current character before stopping.
- pub enable_transmit: bool,
- /// `RXE` Receive enable. If this bit is set to 1, the receive section
- /// of the UART is enabled. Data reception occurs for either UART
- /// signals or SIR signals depending on the setting of the SIREN bit.
- /// When the UART is disabled in the middle of reception, it completes
- /// the current character before stopping.
- pub enable_receive: bool,
- /// `DTR` Data transmit ready. This bit is the complement of the UART
- /// data transmit ready, `nUARTDTR`, modem status output. That is, when
- /// the bit is programmed to a 1 then `nUARTDTR` is LOW.
- pub data_transmit_ready: bool,
- /// `RTS` Request to send. This bit is the complement of the UART
- /// request to send, `nUARTRTS`, modem status output. That is, when the
- /// bit is programmed to a 1 then `nUARTRTS` is LOW.
- pub request_to_send: bool,
- /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`)
- /// modem status output. That is, when the bit is programmed to a 1 the
- /// output is 0. For DTE this can be used as Data Carrier Detect (DCD).
- pub out_1: bool,
- /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`)
- /// modem status output. That is, when the bit is programmed to a 1, the
- /// output is 0. For DTE this can be used as Ring Indicator (RI).
- pub out_2: bool,
- /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1,
- /// RTS hardware flow control is enabled. Data is only requested when
- /// there is space in the receive FIFO for it to be received.
- pub rts_hardware_flow_control_enable: bool,
- /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1,
- /// CTS hardware flow control is enabled. Data is only transmitted when
- /// the `nUARTCTS` signal is asserted.
- pub cts_hardware_flow_control_enable: bool,
- }
-
- impl Control {
- pub fn reset(&mut self) {
- *self = 0.into();
- self.set_enable_receive(true);
- self.set_enable_transmit(true);
- }
- }
-
- impl Default for Control {
- fn default() -> Self {
- let mut ret: Self = 0.into();
- ret.reset();
- ret
- }
- }
-
- /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC
- pub const INT_OE: u32 = 1 << 10;
- pub const INT_BE: u32 = 1 << 9;
- pub const INT_PE: u32 = 1 << 8;
- pub const INT_FE: u32 = 1 << 7;
- pub const INT_RT: u32 = 1 << 6;
- pub const INT_TX: u32 = 1 << 5;
- pub const INT_RX: u32 = 1 << 4;
- pub const INT_DSR: u32 = 1 << 3;
- pub const INT_DCD: u32 = 1 << 2;
- pub const INT_CTS: u32 = 1 << 1;
- pub const INT_RI: u32 = 1 << 0;
- pub const INT_E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
- pub const INT_MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
-
- #[repr(u32)]
- pub enum Interrupt {
- OE = 1 << 10,
- BE = 1 << 9,
- PE = 1 << 8,
- FE = 1 << 7,
- RT = 1 << 6,
- TX = 1 << 5,
- RX = 1 << 4,
- DSR = 1 << 3,
- DCD = 1 << 2,
- CTS = 1 << 1,
- RI = 1 << 0,
- }
-
- impl Interrupt {
- pub const E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
- pub const MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
- }
-}
-
-// TODO: You must disable the UART before any of the control registers are
-// reprogrammed. When the UART is disabled in the middle of transmission or
-// reception, it completes the current character before stopping
diff --git a/rust/hw/char/pl011/src/memory_ops.rs b/rust/hw/char/pl011/src/memory_ops.rs
deleted file mode 100644
index 8d066ebf6d016fa30db004933751a854d7e59117..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/src/memory_ops.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use core::{mem::MaybeUninit, ptr::NonNull};
-
-use qemu_api::bindings::*;
-
-use crate::device::PL011State;
-
-pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps {
- read: Some(pl011_read),
- write: Some(pl011_write),
- read_with_attrs: None,
- write_with_attrs: None,
- endianness: device_endian::DEVICE_NATIVE_ENDIAN,
- valid: unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_1>::zeroed().assume_init() },
- impl_: MemoryRegionOps__bindgen_ty_2 {
- min_access_size: 4,
- max_access_size: 4,
- ..unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_2>::zeroed().assume_init() }
- },
-};
-
-#[no_mangle]
-unsafe extern "C" fn pl011_read(
- opaque: *mut core::ffi::c_void,
- addr: hwaddr,
- size: core::ffi::c_uint,
-) -> u64 {
- assert!(!opaque.is_null());
- let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
- let val = unsafe { state.as_mut().read(addr, size) };
- match val {
- std::ops::ControlFlow::Break(val) => val,
- std::ops::ControlFlow::Continue(val) => {
- // SAFETY: self.char_backend is a valid CharBackend instance after it's been
- // initialized in realize().
- let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) };
- unsafe { qemu_chr_fe_accept_input(cb_ptr) };
-
- val
- }
- }
-}
-
-#[no_mangle]
-unsafe extern "C" fn pl011_write(
- opaque: *mut core::ffi::c_void,
- addr: hwaddr,
- data: u64,
- _size: core::ffi::c_uint,
-) {
- unsafe {
- assert!(!opaque.is_null());
- let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- state.as_mut().write(addr, data)
- }
-}
diff --git a/rust/hw/meson.build b/rust/hw/meson.build
deleted file mode 100644
index 860196645e719624d2e2e6bc301b62b81ab2e19b..0000000000000000000000000000000000000000
--- a/rust/hw/meson.build
+++ /dev/null
@@ -1 +0,0 @@
-subdir('char')
diff --git a/rust/meson.build b/rust/meson.build
index def77389cddc52f5d4503840e9bdfb1207586fa2..7a32b1b195083571931ad589965c10ddaf6383b1 100644
--- a/rust/meson.build
+++ b/rust/meson.build
@@ -1,4 +1,2 @@
subdir('qemu-api-macros')
subdir('qemu-api')
-
-subdir('hw')
diff --git a/scripts/archive-source.sh b/scripts/archive-source.sh
index 30677c3ec9032ea01090f74602d839d1c571d012..62a2cf45d28ed5565076443b9f931a647d395542 100755
--- a/scripts/archive-source.sh
+++ b/scripts/archive-source.sh
@@ -27,9 +27,7 @@ sub_file="${sub_tdir}/submodule.tar"
# in their checkout, because the build environment is completely
# different to the host OS.
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
- berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
- bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
- proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
+ berkeley-testfloat-3 proc-macro2-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
sub_deinit=""
diff --git a/scripts/make-release b/scripts/make-release
index 8dc939124c4fd4abf3509c3b64c0588bc8810962..cf7d694ef73ba1c6c5afad5d211a42f6a6fe1577 100755
--- a/scripts/make-release
+++ b/scripts/make-release
@@ -18,9 +18,7 @@ fi
# Only include wraps that are invoked with subproject()
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
- berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
- bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
- proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
+ berkeley-testfloat-3 proc-macro2-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
src="$1"
diff --git a/scripts/rust/rust_root_crate.sh b/scripts/rust/rust_root_crate.sh
deleted file mode 100755
index 975bddf7f1a4c6ca7770f800bdc894cdff1f3ab1..0000000000000000000000000000000000000000
--- a/scripts/rust/rust_root_crate.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-cat <<EOF
-// @generated
-// This file is autogenerated by scripts/rust_root_crate.sh
-
-EOF
-
-for crate in $*; do
- echo "extern crate $crate;"
-done
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index 50f173f90dbe5dde1162cfe96636f63637574c74..b6888182ca4e5b59b90cfde1792a5770e9a5240a 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -6,13 +6,6 @@
/keycodemapdb
/libvfio-user
/slirp
-/arbitrary-int-1.2.7
-/bilge-0.2.0
-/bilge-impl-0.2.0
-/either-1.12.0
-/itertools-0.11.0
-/proc-macro-error-1.0.4
-/proc-macro-error-attr-1.0.4
/proc-macro2-1.0.84
/quote-1.0.36
/syn-2.0.66
diff --git a/subprojects/arbitrary-int-1-rs.wrap b/subprojects/arbitrary-int-1-rs.wrap
deleted file mode 100644
index e580538a8776001147bfc5c5c73df2bfccac6347..0000000000000000000000000000000000000000
--- a/subprojects/arbitrary-int-1-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = arbitrary-int-1.2.7
-source_url = https://crates.io/api/v1/crates/arbitrary-int/1.2.7/download
-source_filename = arbitrary-int-1.2.7.tar.gz
-source_hash = c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d
-#method = cargo
-patch_directory = arbitrary-int-1-rs
diff --git a/subprojects/bilge-0.2-rs.wrap b/subprojects/bilge-0.2-rs.wrap
deleted file mode 100644
index 7a4339d29896922c392e2e1952e02a1ee82b1874..0000000000000000000000000000000000000000
--- a/subprojects/bilge-0.2-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = bilge-0.2.0
-source_url = https://crates.io/api/v1/crates/bilge/0.2.0/download
-source_filename = bilge-0.2.0.tar.gz
-source_hash = dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57
-#method = cargo
-patch_directory = bilge-0.2-rs
diff --git a/subprojects/bilge-impl-0.2-rs.wrap b/subprojects/bilge-impl-0.2-rs.wrap
deleted file mode 100644
index eefb10c36c24007d48833748cf200d67b6ff7ee7..0000000000000000000000000000000000000000
--- a/subprojects/bilge-impl-0.2-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = bilge-impl-0.2.0
-source_url = https://crates.io/api/v1/crates/bilge-impl/0.2.0/download
-source_filename = bilge-impl-0.2.0.tar.gz
-source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8
-#method = cargo
-patch_directory = bilge-impl-0.2-rs
diff --git a/subprojects/either-1-rs.wrap b/subprojects/either-1-rs.wrap
deleted file mode 100644
index 6046712036c8db1b142d083ae6f8a2d58d6203ab..0000000000000000000000000000000000000000
--- a/subprojects/either-1-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = either-1.12.0
-source_url = https://crates.io/api/v1/crates/either/1.12.0/download
-source_filename = either-1.12.0.tar.gz
-source_hash = 3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b
-#method = cargo
-patch_directory = either-1-rs
diff --git a/subprojects/itertools-0.11-rs.wrap b/subprojects/itertools-0.11-rs.wrap
deleted file mode 100644
index 66b05252cd55ed3149b79b992a6d224e9789ba97..0000000000000000000000000000000000000000
--- a/subprojects/itertools-0.11-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = itertools-0.11.0
-source_url = https://crates.io/api/v1/crates/itertools/0.11.0/download
-source_filename = itertools-0.11.0.tar.gz
-source_hash = b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57
-#method = cargo
-patch_directory = itertools-0.11-rs
diff --git a/subprojects/packagefiles/arbitrary-int-1-rs/meson.build b/subprojects/packagefiles/arbitrary-int-1-rs/meson.build
deleted file mode 100644
index 34a189cbaec77ba6631c6a8cb40d7eb3f32099ee..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/arbitrary-int-1-rs/meson.build
+++ /dev/null
@@ -1,19 +0,0 @@
-project('arbitrary-int-1-rs', 'rust',
- version: '1.2.7',
- license: 'MIT',
- default_options: [])
-
-_arbitrary_int_rs = static_library(
- 'arbitrary_int',
- files('src/lib.rs'),
- gnu_symbol_visibility: 'hidden',
- override_options: ['rust_std=2021', 'build.rust_std=2021'],
- rust_abi: 'rust',
- dependencies: [],
-)
-
-arbitrary_int_dep = declare_dependency(
- link_with: _arbitrary_int_rs,
-)
-
-meson.override_dependency('arbitrary-int-1-rs', arbitrary_int_dep)
diff --git a/subprojects/packagefiles/bilge-0.2-rs/meson.build b/subprojects/packagefiles/bilge-0.2-rs/meson.build
deleted file mode 100644
index a6ed4a8f0cde70be0bf75cd1ad40bc64e3a11a3a..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/bilge-0.2-rs/meson.build
+++ /dev/null
@@ -1,29 +0,0 @@
-project(
- 'bilge-0.2-rs',
- 'rust',
- version : '0.2.0',
- license : 'MIT or Apache-2.0',
-)
-
-subproject('arbitrary-int-1-rs', required: true)
-subproject('bilge-impl-0.2-rs', required: true)
-
-arbitrary_int_dep = dependency('arbitrary-int-1-rs')
-bilge_impl_dep = dependency('bilge-impl-0.2-rs')
-
-lib = static_library(
- 'bilge',
- 'src/lib.rs',
- override_options : ['rust_std=2021', 'build.rust_std=2021'],
- rust_abi : 'rust',
- dependencies: [
- arbitrary_int_dep,
- bilge_impl_dep,
- ],
-)
-
-bilge_dep = declare_dependency(
- link_with : [lib],
-)
-
-meson.override_dependency('bilge-0.2-rs', bilge_dep)
diff --git a/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build b/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build
deleted file mode 100644
index 80243c7024d898826cc3c98ba5b78ef286b79ce9..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build
+++ /dev/null
@@ -1,45 +0,0 @@
-project('bilge-impl-0.2-rs', 'rust',
- version: '0.2.0',
- license: 'MIT OR Apache-2.0',
- default_options: [])
-
-subproject('itertools-0.11-rs', required: true)
-subproject('proc-macro-error-attr-1-rs', required: true)
-subproject('proc-macro-error-1-rs', required: true)
-subproject('quote-1-rs', required: true)
-subproject('syn-2-rs', required: true)
-subproject('proc-macro2-1-rs', required: true)
-
-itertools_dep = dependency('itertools-0.11-rs', native: true)
-proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
-proc_macro_error_dep = dependency('proc-macro-error-1-rs', native: 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)
-
-rust = import('rust')
-
-_bilge_impl_rs = rust.proc_macro(
- 'bilge_impl',
- files('src/lib.rs'),
- override_options: ['rust_std=2021', 'build.rust_std=2021'],
- rust_args: [
- '--cfg', 'use_fallback',
- '--cfg', 'feature="syn-error"',
- '--cfg', 'feature="proc-macro"',
- ],
- dependencies: [
- itertools_dep,
- proc_macro_error_attr_dep,
- proc_macro_error_dep,
- quote_dep,
- syn_dep,
- proc_macro2_dep,
- ],
-)
-
-bilge_impl_dep = declare_dependency(
- link_with: _bilge_impl_rs,
-)
-
-meson.override_dependency('bilge-impl-0.2-rs', bilge_impl_dep)
diff --git a/subprojects/packagefiles/either-1-rs/meson.build b/subprojects/packagefiles/either-1-rs/meson.build
deleted file mode 100644
index a5842eb3a6a63cf94883663758ca5eb11225ad58..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/either-1-rs/meson.build
+++ /dev/null
@@ -1,24 +0,0 @@
-project('either-1-rs', 'rust',
- version: '1.12.0',
- license: 'MIT OR Apache-2.0',
- default_options: [])
-
-_either_rs = static_library(
- 'either',
- files('src/lib.rs'),
- gnu_symbol_visibility: 'hidden',
- override_options: ['rust_std=2018', 'build.rust_std=2018'],
- rust_abi: 'rust',
- rust_args: [
- '--cfg', 'feature="use_std"',
- '--cfg', 'feature="use_alloc"',
- ],
- dependencies: [],
- native: true,
-)
-
-either_dep = declare_dependency(
- link_with: _either_rs,
-)
-
-meson.override_dependency('either-1-rs', either_dep, native: true)
diff --git a/subprojects/packagefiles/itertools-0.11-rs/meson.build b/subprojects/packagefiles/itertools-0.11-rs/meson.build
deleted file mode 100644
index 13d2d27019d215fb793c457e494be7387a7ef455..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/itertools-0.11-rs/meson.build
+++ /dev/null
@@ -1,30 +0,0 @@
-project('itertools-0.11-rs', 'rust',
- version: '0.11.0',
- license: 'MIT OR Apache-2.0',
- default_options: [])
-
-subproject('either-1-rs', required: true)
-
-either_dep = dependency('either-1-rs', native: true)
-
-_itertools_rs = static_library(
- 'itertools',
- files('src/lib.rs'),
- gnu_symbol_visibility: 'hidden',
- override_options: ['rust_std=2018', 'build.rust_std=2018'],
- rust_abi: 'rust',
- rust_args: [
- '--cfg', 'feature="use_std"',
- '--cfg', 'feature="use_alloc"',
- ],
- dependencies: [
- either_dep,
- ],
- native: true,
-)
-
-itertools_dep = declare_dependency(
- link_with: _itertools_rs,
-)
-
-meson.override_dependency('itertools-0.11-rs', itertools_dep, native: true)
diff --git a/subprojects/packagefiles/proc-macro-error-1-rs/meson.build b/subprojects/packagefiles/proc-macro-error-1-rs/meson.build
deleted file mode 100644
index 38ea7b89d39d3f131959f00665b51a49d5bc95e5..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/proc-macro-error-1-rs/meson.build
+++ /dev/null
@@ -1,40 +0,0 @@
-project('proc-macro-error-1-rs', 'rust',
- version: '1.0.4',
- license: 'MIT OR Apache-2.0',
- default_options: [])
-
-subproject('proc-macro-error-attr-1-rs', required: true)
-subproject('quote-1-rs', required: true)
-subproject('syn-2-rs', required: true)
-subproject('proc-macro2-1-rs', required: true)
-
-proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
-proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
-quote_dep = dependency('quote-1-rs', native: true)
-syn_dep = dependency('syn-2-rs', native: true)
-
-_proc_macro_error_rs = static_library(
- 'proc_macro_error',
- files('src/lib.rs'),
- override_options: ['rust_std=2018', 'build.rust_std=2018'],
- rust_abi: 'rust',
- rust_args: [
- '--cfg', 'use_fallback',
- '--cfg', 'feature="syn-error"',
- '--cfg', 'feature="proc-macro"',
- '-A', 'non_fmt_panics'
- ],
- dependencies: [
- proc_macro_error_attr_dep,
- proc_macro2_dep,
- quote_dep,
- syn_dep,
- ],
- native: true,
-)
-
-proc_macro_error_dep = declare_dependency(
- link_with: _proc_macro_error_rs,
-)
-
-meson.override_dependency('proc-macro-error-1-rs', proc_macro_error_dep, native: true)
diff --git a/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build b/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build
deleted file mode 100644
index d900c54cfd1ad6c8c415438c2e9dd51695c82150..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build
+++ /dev/null
@@ -1,32 +0,0 @@
-project('proc-macro-error-attr-1-rs', 'rust',
- version: '1.12.0',
- license: 'MIT OR Apache-2.0',
- default_options: [])
-
-subproject('proc-macro2-1-rs', required: true)
-subproject('quote-1-rs', required: true)
-
-proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
-quote_dep = dependency('quote-1-rs', native: true)
-
-rust = import('rust')
-_proc_macro_error_attr_rs = rust.proc_macro(
- 'proc_macro_error_attr',
- files('src/lib.rs'),
- override_options: ['rust_std=2018', 'build.rust_std=2018'],
- rust_args: [
- '--cfg', 'use_fallback',
- '--cfg', 'feature="syn-error"',
- '--cfg', 'feature="proc-macro"'
- ],
- dependencies: [
- proc_macro2_dep,
- quote_dep,
- ],
-)
-
-proc_macro_error_attr_dep = declare_dependency(
- link_with: _proc_macro_error_attr_rs,
-)
-
-meson.override_dependency('proc-macro-error-attr-1-rs', proc_macro_error_attr_dep, native: true)
diff --git a/subprojects/packagefiles/unicode-ident-1-rs/meson.build b/subprojects/packagefiles/unicode-ident-1-rs/meson.build
deleted file mode 100644
index 54f2376854504236689604f8ab08d351e4cceae9..0000000000000000000000000000000000000000
--- a/subprojects/packagefiles/unicode-ident-1-rs/meson.build
+++ /dev/null
@@ -1,20 +0,0 @@
-project('unicode-ident-1-rs', 'rust',
- version: '1.0.12',
- license: '(MIT OR Apache-2.0) AND Unicode-DFS-2016',
- default_options: [])
-
-_unicode_ident_rs = static_library(
- 'unicode_ident',
- files('src/lib.rs'),
- gnu_symbol_visibility: 'hidden',
- override_options: ['rust_std=2021', 'build.rust_std=2021'],
- rust_abi: 'rust',
- dependencies: [],
- native: true,
-)
-
-unicode_ident_dep = declare_dependency(
- link_with: _unicode_ident_rs,
-)
-
-meson.override_dependency('unicode-ident-1-rs', unicode_ident_dep, native: true)
diff --git a/subprojects/proc-macro-error-1-rs.wrap b/subprojects/proc-macro-error-1-rs.wrap
deleted file mode 100644
index b7db03b06a019562b2ad77bb252d50b7e83c0b33..0000000000000000000000000000000000000000
--- a/subprojects/proc-macro-error-1-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = proc-macro-error-1.0.4
-source_url = https://crates.io/api/v1/crates/proc-macro-error/1.0.4/download
-source_filename = proc-macro-error-1.0.4.tar.gz
-source_hash = da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c
-#method = cargo
-patch_directory = proc-macro-error-1-rs
diff --git a/subprojects/proc-macro-error-attr-1-rs.wrap b/subprojects/proc-macro-error-attr-1-rs.wrap
deleted file mode 100644
index d13d8a239ac259132cd905bc512e9024b2f30fb8..0000000000000000000000000000000000000000
--- a/subprojects/proc-macro-error-attr-1-rs.wrap
+++ /dev/null
@@ -1,7 +0,0 @@
-[wrap-file]
-directory = proc-macro-error-attr-1.0.4
-source_url = https://crates.io/api/v1/crates/proc-macro-error-attr/1.0.4/download
-source_filename = proc-macro-error-attr-1.0.4.tar.gz
-source_hash = a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869
-#method = cargo
-patch_directory = proc-macro-error-attr-1-rs
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 02/11] rust: add PL011 device model
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
2024-10-24 14:02 ` [PATCH 01/11] Revert "rust: add PL011 device model" Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
` (9 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
This commit adds a re-implementation of hw/char/pl011.c in Rust.
How to build:
1. Configure a QEMU build with:
--enable-system --target-list=aarch64-softmmu --enable-rust
2. Launching a VM with qemu-system-aarch64 should use the Rust version
of the pl011 device
Co-authored-by: Junjie Mao <junjie.mao@intel.com>
Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
MAINTAINERS | 5 +
meson.build | 24 +
hw/arm/Kconfig | 30 +-
rust/Kconfig | 1 +
rust/hw/Kconfig | 2 +
rust/hw/char/Kconfig | 3 +
rust/hw/char/meson.build | 1 +
rust/hw/char/pl011/.gitignore | 2 +
rust/hw/char/pl011/Cargo.lock | 134 +++++
rust/hw/char/pl011/Cargo.toml | 26 +
rust/hw/char/pl011/README.md | 31 ++
rust/hw/char/pl011/meson.build | 26 +
rust/hw/char/pl011/src/device.rs | 599 +++++++++++++++++++++
rust/hw/char/pl011/src/device_class.rs | 70 +++
rust/hw/char/pl011/src/lib.rs | 586 ++++++++++++++++++++
rust/hw/char/pl011/src/memory_ops.rs | 59 ++
rust/hw/meson.build | 1 +
rust/meson.build | 2 +
scripts/archive-source.sh | 4 +-
scripts/make-release | 4 +-
scripts/rust/rust_root_crate.sh | 13 +
subprojects/.gitignore | 7 +
subprojects/arbitrary-int-1-rs.wrap | 7 +
subprojects/bilge-0.2-rs.wrap | 7 +
subprojects/bilge-impl-0.2-rs.wrap | 7 +
subprojects/either-1-rs.wrap | 7 +
subprojects/itertools-0.11-rs.wrap | 7 +
.../packagefiles/arbitrary-int-1-rs/meson.build | 19 +
subprojects/packagefiles/bilge-0.2-rs/meson.build | 29 +
.../packagefiles/bilge-impl-0.2-rs/meson.build | 45 ++
subprojects/packagefiles/either-1-rs/meson.build | 24 +
.../packagefiles/itertools-0.11-rs/meson.build | 30 ++
.../packagefiles/proc-macro-error-1-rs/meson.build | 40 ++
.../proc-macro-error-attr-1-rs/meson.build | 32 ++
.../packagefiles/unicode-ident-1-rs/meson.build | 20 +
subprojects/proc-macro-error-1-rs.wrap | 7 +
subprojects/proc-macro-error-attr-1-rs.wrap | 7 +
37 files changed, 1906 insertions(+), 12 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 793c683aa6cccafca9c98b74b9a20d84211f041b..c3bfa132fd6ecd61dd733b760a5e1ccd39613455 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1137,6 +1137,11 @@ F: include/hw/*/microbit*.h
F: tests/qtest/microbit-test.c
F: docs/system/arm/nrf.rst
+ARM PL011 Rust device
+M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+S: Maintained
+F: rust/hw/char/pl011/
+
AVR Machines
-------------
diff --git a/meson.build b/meson.build
index 0d617c551e61c90c7c357c62ff5da34437723947..c26c417de16ad9256a019a90fd89ea990bb3f948 100644
--- a/meson.build
+++ b/meson.build
@@ -3520,6 +3520,7 @@ qom_ss = ss.source_set()
system_ss = ss.source_set()
specific_fuzz_ss = ss.source_set()
specific_ss = ss.source_set()
+rust_devices_ss = ss.source_set()
stub_ss = ss.source_set()
trace_ss = ss.source_set()
user_ss = ss.source_set()
@@ -4067,6 +4068,29 @@ foreach target : target_dirs
arch_srcs += target_specific.sources()
arch_deps += target_specific.dependencies()
+ if have_rust and have_system
+ target_rust = rust_devices_ss.apply(config_target, strict: false)
+ crates = []
+ foreach dep : target_rust.dependencies()
+ crates += dep.get_variable('crate')
+ endforeach
+ if crates.length() > 0
+ rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
+ output: 'rust_' + target.underscorify() + '.rs',
+ command: [find_program('scripts/rust/rust_root_crate.sh')] + crates,
+ capture: true,
+ build_by_default: true,
+ build_always_stale: true)
+ rlib = static_library('rust_' + target.underscorify(),
+ rlib_rs,
+ dependencies: target_rust.dependencies(),
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_args: rustc_args,
+ rust_abi: 'c')
+ arch_deps += declare_dependency(link_whole: [rlib])
+ endif
+ endif
+
# allow using headers from the dependencies but do not include the sources,
# because this emulator only needs those in "objects". For external
# dependencies, the full dependency is included below in the executable.
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 53eb7bb3d0157bb6e8e078fe73ec66015ae0fe13..e7fd9338d11dadb7a18032c674927db3d9887bdd 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -20,7 +20,8 @@ config ARM_VIRT
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC
select PL061 # GPIO
select GPIO_PWR
@@ -73,7 +74,8 @@ config HIGHBANK
select AHCI
select ARM_TIMER # sp804
select ARM_V7M
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL022 # SPI
select PL031 # RTC
select PL061 # GPIO
@@ -86,7 +88,8 @@ config INTEGRATOR
depends on TCG && ARM
select ARM_TIMER
select INTEGRATOR_DEBUG
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC
select PL041 # audio
select PL050 # keyboard/mouse
@@ -104,7 +107,8 @@ config MUSCA
default y
depends on TCG && ARM
select ARMSSE
- select PL011
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL031
select SPLIT_IRQ
select UNIMP
@@ -168,7 +172,8 @@ config REALVIEW
select WM8750 # audio codec
select LSI_SCSI_PCI
select PCI
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC
select PL041 # audio codec
select PL050 # keyboard/mouse
@@ -193,7 +198,8 @@ config SBSA_REF
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC
select PL061 # GPIO
select USB_XHCI_SYSBUS
@@ -217,7 +223,8 @@ config STELLARIS
select ARM_V7M
select CMSDK_APB_WATCHDOG
select I2C
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL022 # SPI
select PL061 # GPIO
select SSD0303 # OLED display
@@ -277,7 +284,8 @@ config VEXPRESS
select ARM_TIMER # sp804
select LAN9118
select PFLASH_CFI01
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select PL041 # audio codec
select PL181 # display
select REALVIEW
@@ -362,7 +370,8 @@ config RASPI
default y
depends on TCG && ARM
select FRAMEBUFFER
- select PL011 # UART
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select SDHCI
select USB_DWC2
select BCM2835_SPI
@@ -438,7 +447,8 @@ config XLNX_VERSAL
select ARM_GIC
select CPU_CLUSTER
select DEVICE_TREE
- select PL011
+ select PL011 if !HAVE_RUST # UART
+ select X_PL011_RUST if HAVE_RUST # UART
select CADENCE
select VIRTIO_MMIO
select UNIMP
diff --git a/rust/Kconfig b/rust/Kconfig
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f9f5c3909887451f71360a7986d79e57fdb43c91 100644
--- a/rust/Kconfig
+++ b/rust/Kconfig
@@ -0,0 +1 @@
+source hw/Kconfig
diff --git a/rust/hw/Kconfig b/rust/hw/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..4d934f30afe13ddff418db8ec9e8b8eb25a9e8d0
--- /dev/null
+++ b/rust/hw/Kconfig
@@ -0,0 +1,2 @@
+# devices Kconfig
+source char/Kconfig
diff --git a/rust/hw/char/Kconfig b/rust/hw/char/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..a1732a9e97fe3211547e30bc9319382e6394ed5b
--- /dev/null
+++ b/rust/hw/char/Kconfig
@@ -0,0 +1,3 @@
+config X_PL011_RUST
+ bool
+ default y if HAVE_RUST
diff --git a/rust/hw/char/meson.build b/rust/hw/char/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..5716dc43ef6facdcf1cc963108347bbf4d12cf0e
--- /dev/null
+++ b/rust/hw/char/meson.build
@@ -0,0 +1 @@
+subdir('pl011')
diff --git a/rust/hw/char/pl011/.gitignore b/rust/hw/char/pl011/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..71eaff2035d5a65b57ae32dfeecf3d87bbc7b396
--- /dev/null
+++ b/rust/hw/char/pl011/.gitignore
@@ -0,0 +1,2 @@
+# Ignore generated bindings file overrides.
+src/bindings.rs.inc
diff --git a/rust/hw/char/pl011/Cargo.lock b/rust/hw/char/pl011/Cargo.lock
new file mode 100644
index 0000000000000000000000000000000000000000..b58cebb186e99efe184117bb931a341543d4466b
--- /dev/null
+++ b/rust/hw/char/pl011/Cargo.lock
@@ -0,0 +1,134 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "arbitrary-int"
+version = "1.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d"
+
+[[package]]
+name = "bilge"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57"
+dependencies = [
+ "arbitrary-int",
+ "bilge-impl",
+]
+
+[[package]]
+name = "bilge-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8"
+dependencies = [
+ "itertools",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "pl011"
+version = "0.1.0"
+dependencies = [
+ "bilge",
+ "bilge-impl",
+ "qemu_api",
+ "qemu_api_macros",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "qemu_api"
+version = "0.1.0"
+
+[[package]]
+name = "qemu_api_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b089e3dded623131ee13b4af8145b84388755df7
--- /dev/null
+++ b/rust/hw/char/pl011/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "pl011"
+version = "0.1.0"
+edition = "2021"
+authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
+license = "GPL-2.0-or-later"
+readme = "README.md"
+homepage = "https://www.qemu.org"
+description = "pl011 device model for QEMU"
+repository = "https://gitlab.com/epilys/rust-for-qemu"
+resolver = "2"
+publish = false
+keywords = []
+categories = []
+
+[lib]
+crate-type = ["staticlib"]
+
+[dependencies]
+bilge = { version = "0.2.0" }
+bilge-impl = { version = "0.2.0" }
+qemu_api = { path = "../../../qemu-api" }
+qemu_api_macros = { path = "../../../qemu-api-macros" }
+
+# Do not include in any global workspace
+[workspace]
diff --git a/rust/hw/char/pl011/README.md b/rust/hw/char/pl011/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cd7dea31634241cbf96b0be13f21d52bbd8ae750
--- /dev/null
+++ b/rust/hw/char/pl011/README.md
@@ -0,0 +1,31 @@
+# PL011 QEMU Device Model
+
+This library implements a device model for the PrimeCell® UART (PL011)
+device in QEMU.
+
+## Build static lib
+
+Host build target must be explicitly specified:
+
+```sh
+cargo build --target x86_64-unknown-linux-gnu
+```
+
+Replace host target triplet if necessary.
+
+## Generate Rust documentation
+
+To generate docs for this crate, including private items:
+
+```sh
+cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
+```
+
+To include direct dependencies like `bilge` (bitmaps for register types):
+
+```sh
+cargo tree --depth 1 -e normal --prefix none \
+ | cut -d' ' -f1 \
+ | xargs printf -- '-p %s\n' \
+ | xargs cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
+```
diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..547cca5a96f7eef284caf1949380b65f7d015d92
--- /dev/null
+++ b/rust/hw/char/pl011/meson.build
@@ -0,0 +1,26 @@
+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,
+ qemu_api,
+ qemu_api_macros,
+ ],
+)
+
+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],
+ variables: {'crate': 'pl011'},
+)])
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c7193b41beec0b177dbc75ac0e43fcfea4c82bfb
--- /dev/null
+++ b/rust/hw/char/pl011/src/device.rs
@@ -0,0 +1,599 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use core::{
+ ffi::{c_int, c_uchar, c_uint, c_void, CStr},
+ ptr::{addr_of, addr_of_mut, NonNull},
+};
+
+use qemu_api::{
+ bindings::{self, *},
+ definitions::ObjectImpl,
+};
+
+use crate::{
+ memory_ops::PL011_OPS,
+ registers::{self, Interrupt},
+ RegisterOffset,
+};
+
+static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
+
+const DATA_BREAK: u32 = 1 << 10;
+
+/// QEMU sourced constant.
+pub const PL011_FIFO_DEPTH: usize = 16_usize;
+
+#[repr(C)]
+#[derive(Debug, qemu_api_macros::Object)]
+/// PL011 Device Model in QEMU
+pub struct PL011State {
+ pub parent_obj: SysBusDevice,
+ pub iomem: MemoryRegion,
+ #[doc(alias = "fr")]
+ pub flags: registers::Flags,
+ #[doc(alias = "lcr")]
+ pub line_control: registers::LineControl,
+ #[doc(alias = "rsr")]
+ pub receive_status_error_clear: registers::ReceiveStatusErrorClear,
+ #[doc(alias = "cr")]
+ pub control: registers::Control,
+ pub dmacr: u32,
+ pub int_enabled: u32,
+ pub int_level: u32,
+ pub read_fifo: [u32; PL011_FIFO_DEPTH],
+ pub ilpr: u32,
+ pub ibrd: u32,
+ pub fbrd: u32,
+ pub ifl: u32,
+ pub read_pos: usize,
+ pub read_count: usize,
+ pub read_trigger: usize,
+ #[doc(alias = "chr")]
+ pub char_backend: CharBackend,
+ /// QEMU interrupts
+ ///
+ /// ```text
+ /// * sysbus MMIO region 0: device registers
+ /// * sysbus IRQ 0: `UARTINTR` (combined interrupt line)
+ /// * sysbus IRQ 1: `UARTRXINTR` (receive FIFO interrupt line)
+ /// * sysbus IRQ 2: `UARTTXINTR` (transmit FIFO interrupt line)
+ /// * sysbus IRQ 3: `UARTRTINTR` (receive timeout interrupt line)
+ /// * sysbus IRQ 4: `UARTMSINTR` (momem status interrupt line)
+ /// * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
+ /// ```
+ #[doc(alias = "irq")]
+ pub interrupts: [qemu_irq; 6usize],
+ #[doc(alias = "clk")]
+ pub clock: NonNull<Clock>,
+ #[doc(alias = "migrate_clk")]
+ pub migrate_clock: bool,
+}
+
+impl ObjectImpl for PL011State {
+ type Class = PL011Class;
+ const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
+ const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
+ const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE);
+ const ABSTRACT: bool = false;
+ const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init);
+ const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
+ const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
+}
+
+#[repr(C)]
+pub struct PL011Class {
+ _inner: [u8; 0],
+}
+
+impl qemu_api::definitions::Class for PL011Class {
+ const CLASS_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
+ > = Some(crate::device_class::pl011_class_init);
+ const CLASS_BASE_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
+ > = None;
+}
+
+#[used]
+pub static CLK_NAME: &CStr = c"clk";
+
+impl PL011State {
+ /// Initializes a pre-allocated, unitialized instance of `PL011State`.
+ ///
+ /// # Safety
+ ///
+ /// `self` must point to a correctly sized and aligned location for the
+ /// `PL011State` type. It must not be called more than once on the same
+ /// location/instance. All its fields are expected to hold unitialized
+ /// values with the sole exception of `parent_obj`.
+ pub unsafe fn init(&mut self) {
+ let dev = addr_of_mut!(*self).cast::<DeviceState>();
+ // SAFETY:
+ //
+ // self and self.iomem are guaranteed to be valid at this point since callers
+ // must make sure the `self` reference is valid.
+ unsafe {
+ memory_region_init_io(
+ addr_of_mut!(self.iomem),
+ addr_of_mut!(*self).cast::<Object>(),
+ &PL011_OPS,
+ addr_of_mut!(*self).cast::<c_void>(),
+ Self::TYPE_INFO.name,
+ 0x1000,
+ );
+ let sbd = addr_of_mut!(*self).cast::<SysBusDevice>();
+ sysbus_init_mmio(sbd, addr_of_mut!(self.iomem));
+ for irq in self.interrupts.iter_mut() {
+ sysbus_init_irq(sbd, irq);
+ }
+ }
+ // SAFETY:
+ //
+ // self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
+ // we can overwrite the undefined value without side effects. This is
+ // safe since all PL011State instances are created by QOM code which
+ // calls this function to initialize the fields; therefore no code is
+ // able to access an invalid self.clock value.
+ unsafe {
+ self.clock = NonNull::new(qdev_init_clock_in(
+ dev,
+ CLK_NAME.as_ptr(),
+ None, /* pl011_clock_update */
+ addr_of_mut!(*self).cast::<c_void>(),
+ ClockEvent::ClockUpdate.0,
+ ))
+ .unwrap();
+ }
+ }
+
+ pub fn read(
+ &mut self,
+ offset: hwaddr,
+ _size: core::ffi::c_uint,
+ ) -> std::ops::ControlFlow<u64, u64> {
+ use RegisterOffset::*;
+
+ std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
+ Err(v) if (0x3f8..0x400).contains(&v) => {
+ u64::from(PL011_ID_ARM[((offset - 0xfe0) >> 2) as usize])
+ }
+ Err(_) => {
+ // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
+ 0
+ }
+ Ok(DR) => {
+ // s->flags &= ~PL011_FLAG_RXFF;
+ self.flags.set_receive_fifo_full(false);
+ let c = self.read_fifo[self.read_pos];
+ if self.read_count > 0 {
+ self.read_count -= 1;
+ self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
+ }
+ if self.read_count == 0 {
+ // self.flags |= PL011_FLAG_RXFE;
+ self.flags.set_receive_fifo_empty(true);
+ }
+ if self.read_count + 1 == self.read_trigger {
+ //self.int_level &= ~ INT_RX;
+ self.int_level &= !registers::INT_RX;
+ }
+ // Update error bits.
+ self.receive_status_error_clear = c.to_be_bytes()[3].into();
+ self.update();
+ // Must call qemu_chr_fe_accept_input, so return Continue:
+ return std::ops::ControlFlow::Continue(c.into());
+ }
+ Ok(RSR) => u8::from(self.receive_status_error_clear).into(),
+ Ok(FR) => u16::from(self.flags).into(),
+ Ok(FBRD) => self.fbrd.into(),
+ Ok(ILPR) => self.ilpr.into(),
+ Ok(IBRD) => self.ibrd.into(),
+ Ok(LCR_H) => u16::from(self.line_control).into(),
+ Ok(CR) => {
+ // We exercise our self-control.
+ u16::from(self.control).into()
+ }
+ Ok(FLS) => self.ifl.into(),
+ Ok(IMSC) => self.int_enabled.into(),
+ Ok(RIS) => self.int_level.into(),
+ Ok(MIS) => u64::from(self.int_level & self.int_enabled),
+ Ok(ICR) => {
+ // "The UARTICR Register is the interrupt clear register and is write-only"
+ // Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR
+ 0
+ }
+ Ok(DMACR) => self.dmacr.into(),
+ })
+ }
+
+ pub fn write(&mut self, offset: hwaddr, value: u64) {
+ // eprintln!("write offset {offset} value {value}");
+ use RegisterOffset::*;
+ let value: u32 = value as u32;
+ match RegisterOffset::try_from(offset) {
+ Err(_bad_offset) => {
+ eprintln!("write bad offset {offset} value {value}");
+ }
+ Ok(DR) => {
+ // ??? Check if transmitter is enabled.
+ let ch: u8 = value as u8;
+ // XXX this blocks entire thread. Rewrite to use
+ // qemu_chr_fe_write and background I/O callbacks
+
+ // SAFETY: self.char_backend is a valid CharBackend instance after it's been
+ // initialized in realize().
+ unsafe {
+ qemu_chr_fe_write_all(addr_of_mut!(self.char_backend), &ch, 1);
+ }
+ self.loopback_tx(value);
+ self.int_level |= registers::INT_TX;
+ self.update();
+ }
+ Ok(RSR) => {
+ self.receive_status_error_clear = 0.into();
+ }
+ Ok(FR) => {
+ // flag writes are ignored
+ }
+ Ok(ILPR) => {
+ self.ilpr = value;
+ }
+ Ok(IBRD) => {
+ self.ibrd = value;
+ }
+ Ok(FBRD) => {
+ self.fbrd = value;
+ }
+ Ok(LCR_H) => {
+ let value = value as u16;
+ let new_val: registers::LineControl = value.into();
+ // Reset the FIFO state on FIFO enable or disable
+ if bool::from(self.line_control.fifos_enabled())
+ ^ bool::from(new_val.fifos_enabled())
+ {
+ self.reset_fifo();
+ }
+ if self.line_control.send_break() ^ new_val.send_break() {
+ let mut break_enable: c_int = new_val.send_break().into();
+ // SAFETY: self.char_backend is a valid CharBackend instance after it's been
+ // initialized in realize().
+ unsafe {
+ qemu_chr_fe_ioctl(
+ addr_of_mut!(self.char_backend),
+ CHR_IOCTL_SERIAL_SET_BREAK as i32,
+ addr_of_mut!(break_enable).cast::<c_void>(),
+ );
+ }
+ self.loopback_break(break_enable > 0);
+ }
+ self.line_control = new_val;
+ self.set_read_trigger();
+ }
+ Ok(CR) => {
+ // ??? Need to implement the enable bit.
+ let value = value as u16;
+ self.control = value.into();
+ self.loopback_mdmctrl();
+ }
+ Ok(FLS) => {
+ self.ifl = value;
+ self.set_read_trigger();
+ }
+ Ok(IMSC) => {
+ self.int_enabled = value;
+ self.update();
+ }
+ Ok(RIS) => {}
+ Ok(MIS) => {}
+ Ok(ICR) => {
+ self.int_level &= !value;
+ self.update();
+ }
+ Ok(DMACR) => {
+ self.dmacr = value;
+ if value & 3 > 0 {
+ // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
+ eprintln!("pl011: DMA not implemented");
+ }
+ }
+ }
+ }
+
+ #[inline]
+ fn loopback_tx(&mut self, value: u32) {
+ if !self.loopback_enabled() {
+ return;
+ }
+
+ // Caveat:
+ //
+ // In real hardware, TX loopback happens at the serial-bit level
+ // and then reassembled by the RX logics back into bytes and placed
+ // into the RX fifo. That is, loopback happens after TX fifo.
+ //
+ // Because the real hardware TX fifo is time-drained at the frame
+ // rate governed by the configured serial format, some loopback
+ // bytes in TX fifo may still be able to get into the RX fifo
+ // that could be full at times while being drained at software
+ // pace.
+ //
+ // In such scenario, the RX draining pace is the major factor
+ // deciding which loopback bytes get into the RX fifo, unless
+ // hardware flow-control is enabled.
+ //
+ // For simplicity, the above described is not emulated.
+ self.put_fifo(value);
+ }
+
+ fn loopback_mdmctrl(&mut self) {
+ if !self.loopback_enabled() {
+ return;
+ }
+
+ /*
+ * Loopback software-driven modem control outputs to modem status inputs:
+ * FR.RI <= CR.Out2
+ * FR.DCD <= CR.Out1
+ * FR.CTS <= CR.RTS
+ * FR.DSR <= CR.DTR
+ *
+ * The loopback happens immediately even if this call is triggered
+ * by setting only CR.LBE.
+ *
+ * CTS/RTS updates due to enabled hardware flow controls are not
+ * dealt with here.
+ */
+
+ //fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
+ // PL011_FLAG_DSR | PL011_FLAG_CTS);
+ //fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
+ //fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
+ //fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
+ //fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
+ //
+ self.flags.set_ring_indicator(self.control.out_2());
+ self.flags.set_data_carrier_detect(self.control.out_1());
+ self.flags.set_clear_to_send(self.control.request_to_send());
+ self.flags
+ .set_data_set_ready(self.control.data_transmit_ready());
+
+ // Change interrupts based on updated FR
+ let mut il = self.int_level;
+
+ il &= !Interrupt::MS;
+ //il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
+ //il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
+ //il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
+ //il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
+
+ if self.flags.data_set_ready() {
+ il |= Interrupt::DSR as u32;
+ }
+ if self.flags.data_carrier_detect() {
+ il |= Interrupt::DCD as u32;
+ }
+ if self.flags.clear_to_send() {
+ il |= Interrupt::CTS as u32;
+ }
+ if self.flags.ring_indicator() {
+ il |= Interrupt::RI as u32;
+ }
+ self.int_level = il;
+ self.update();
+ }
+
+ fn loopback_break(&mut self, enable: bool) {
+ if enable {
+ self.loopback_tx(DATA_BREAK);
+ }
+ }
+
+ fn set_read_trigger(&mut self) {
+ self.read_trigger = 1;
+ }
+
+ pub fn realize(&mut self) {
+ // SAFETY: self.char_backend has the correct size and alignment for a
+ // CharBackend object, and its callbacks are of the correct types.
+ unsafe {
+ qemu_chr_fe_set_handlers(
+ addr_of_mut!(self.char_backend),
+ Some(pl011_can_receive),
+ Some(pl011_receive),
+ Some(pl011_event),
+ None,
+ addr_of_mut!(*self).cast::<c_void>(),
+ core::ptr::null_mut(),
+ true,
+ );
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.line_control.reset();
+ self.receive_status_error_clear.reset();
+ self.dmacr = 0;
+ self.int_enabled = 0;
+ self.int_level = 0;
+ self.ilpr = 0;
+ self.ibrd = 0;
+ self.fbrd = 0;
+ self.read_trigger = 1;
+ self.ifl = 0x12;
+ self.control.reset();
+ self.flags = 0.into();
+ self.reset_fifo();
+ }
+
+ pub fn reset_fifo(&mut self) {
+ self.read_count = 0;
+ self.read_pos = 0;
+
+ /* Reset FIFO flags */
+ self.flags.reset();
+ }
+
+ pub fn can_receive(&self) -> bool {
+ // trace_pl011_can_receive(s->lcr, s->read_count, r);
+ self.read_count < self.fifo_depth()
+ }
+
+ pub fn event(&mut self, event: QEMUChrEvent) {
+ if event == bindings::QEMUChrEvent::CHR_EVENT_BREAK && !self.fifo_enabled() {
+ self.put_fifo(DATA_BREAK);
+ self.receive_status_error_clear.set_break_error(true);
+ }
+ }
+
+ #[inline]
+ pub fn fifo_enabled(&self) -> bool {
+ matches!(self.line_control.fifos_enabled(), registers::Mode::FIFO)
+ }
+
+ #[inline]
+ pub fn loopback_enabled(&self) -> bool {
+ self.control.enable_loopback()
+ }
+
+ #[inline]
+ pub fn fifo_depth(&self) -> usize {
+ // Note: FIFO depth is expected to be power-of-2
+ if self.fifo_enabled() {
+ return PL011_FIFO_DEPTH;
+ }
+ 1
+ }
+
+ pub fn put_fifo(&mut self, value: c_uint) {
+ let depth = self.fifo_depth();
+ assert!(depth > 0);
+ let slot = (self.read_pos + self.read_count) & (depth - 1);
+ self.read_fifo[slot] = value;
+ self.read_count += 1;
+ // s->flags &= ~PL011_FLAG_RXFE;
+ self.flags.set_receive_fifo_empty(false);
+ if self.read_count == depth {
+ //s->flags |= PL011_FLAG_RXFF;
+ self.flags.set_receive_fifo_full(true);
+ }
+
+ if self.read_count == self.read_trigger {
+ self.int_level |= registers::INT_RX;
+ self.update();
+ }
+ }
+
+ pub fn update(&self) {
+ let flags = self.int_level & self.int_enabled;
+ for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
+ // SAFETY: self.interrupts have been initialized in init().
+ unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
+ }
+ }
+}
+
+/// Which bits in the interrupt status matter for each outbound IRQ line ?
+pub const IRQMASK: [u32; 6] = [
+ /* combined IRQ */
+ Interrupt::E
+ | Interrupt::MS
+ | Interrupt::RT as u32
+ | Interrupt::TX as u32
+ | Interrupt::RX as u32,
+ Interrupt::RX as u32,
+ Interrupt::TX as u32,
+ Interrupt::RT as u32,
+ Interrupt::MS,
+ Interrupt::E,
+];
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_ref().can_receive().into()
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+///
+/// The buffer and size arguments must also be valid.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_receive(
+ opaque: *mut core::ffi::c_void,
+ buf: *const u8,
+ size: core::ffi::c_int,
+) {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ if state.as_ref().loopback_enabled() {
+ return;
+ }
+ if size > 0 {
+ debug_assert!(!buf.is_null());
+ state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
+ }
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_mut().event(event)
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer for `chr`.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_create(
+ addr: u64,
+ irq: qemu_irq,
+ chr: *mut Chardev,
+) -> *mut DeviceState {
+ unsafe {
+ let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
+ let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
+
+ qdev_prop_set_chr(dev, bindings::TYPE_CHARDEV.as_ptr(), chr);
+ sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut Error);
+ sysbus_mmio_map(sysbus, 0, addr);
+ sysbus_connect_irq(sysbus, 0, irq);
+ dev
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
+ unsafe {
+ debug_assert!(!obj.is_null());
+ let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
+ state.as_mut().init();
+ }
+}
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b7ab31af02d7bb50ae94be0b153baafc7ccfa375
--- /dev/null
+++ b/rust/hw/char/pl011/src/device_class.rs
@@ -0,0 +1,70 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use core::ptr::NonNull;
+
+use qemu_api::{bindings::*, definitions::ObjectImpl};
+
+use crate::device::PL011State;
+
+#[used]
+pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
+ name: PL011State::TYPE_INFO.name,
+ unmigratable: true,
+ ..unsafe { ::core::mem::MaybeUninit::<VMStateDescription>::zeroed().assume_init() }
+};
+
+qemu_api::declare_properties! {
+ PL011_PROPERTIES,
+ qemu_api::define_property!(
+ c"chardev",
+ PL011State,
+ char_backend,
+ unsafe { &qdev_prop_chr },
+ CharBackend
+ ),
+ qemu_api::define_property!(
+ c"migrate-clk",
+ PL011State,
+ migrate_clock,
+ unsafe { &qdev_prop_bool },
+ bool
+ ),
+}
+
+qemu_api::device_class_init! {
+ pl011_class_init,
+ props => PL011_PROPERTIES,
+ realize_fn => Some(pl011_realize),
+ legacy_reset_fn => Some(pl011_reset),
+ vmsd => VMSTATE_PL011,
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
+ unsafe {
+ assert!(!dev.is_null());
+ let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
+ state.as_mut().realize();
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011State`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+#[no_mangle]
+pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
+ unsafe {
+ assert!(!dev.is_null());
+ let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
+ state.as_mut().reset();
+ }
+}
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2939ee50c99ceaacf6ec68127272d58814e33679
--- /dev/null
+++ b/rust/hw/char/pl011/src/lib.rs
@@ -0,0 +1,586 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// PL011 QEMU Device Model
+//
+// This library implements a device model for the PrimeCell® UART (PL011)
+// device in QEMU.
+//
+#![doc = include_str!("../README.md")]
+//! # Library crate
+//!
+//! See [`PL011State`](crate::device::PL011State) for the device model type and
+//! the [`registers`] module for register types.
+
+#![deny(
+ rustdoc::broken_intra_doc_links,
+ rustdoc::redundant_explicit_links,
+ clippy::correctness,
+ clippy::suspicious,
+ clippy::complexity,
+ clippy::perf,
+ clippy::cargo,
+ clippy::nursery,
+ clippy::style,
+ // restriction group
+ clippy::dbg_macro,
+ clippy::as_underscore,
+ clippy::assertions_on_result_states,
+ // pedantic group
+ clippy::doc_markdown,
+ clippy::borrow_as_ptr,
+ clippy::cast_lossless,
+ clippy::option_if_let_else,
+ clippy::missing_const_for_fn,
+ clippy::cognitive_complexity,
+ clippy::missing_safety_doc,
+ )]
+
+extern crate bilge;
+extern crate bilge_impl;
+extern crate qemu_api;
+
+pub mod device;
+pub mod device_class;
+pub mod memory_ops;
+
+pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
+
+/// Offset of each register from the base memory address of the device.
+///
+/// # Source
+/// ARM DDI 0183G, Table 3-1 p.3-3
+#[doc(alias = "offset")]
+#[allow(non_camel_case_types)]
+#[repr(u64)]
+#[derive(Debug)]
+pub enum RegisterOffset {
+ /// Data Register
+ ///
+ /// A write to this register initiates the actual data transmission
+ #[doc(alias = "UARTDR")]
+ DR = 0x000,
+ /// Receive Status Register or Error Clear Register
+ #[doc(alias = "UARTRSR")]
+ #[doc(alias = "UARTECR")]
+ RSR = 0x004,
+ /// Flag Register
+ ///
+ /// A read of this register shows if transmission is complete
+ #[doc(alias = "UARTFR")]
+ FR = 0x018,
+ /// Fractional Baud Rate Register
+ ///
+ /// responsible for baud rate speed
+ #[doc(alias = "UARTFBRD")]
+ FBRD = 0x028,
+ /// `IrDA` Low-Power Counter Register
+ #[doc(alias = "UARTILPR")]
+ ILPR = 0x020,
+ /// Integer Baud Rate Register
+ ///
+ /// Responsible for baud rate speed
+ #[doc(alias = "UARTIBRD")]
+ IBRD = 0x024,
+ /// line control register (data frame format)
+ #[doc(alias = "UARTLCR_H")]
+ LCR_H = 0x02C,
+ /// Toggle UART, transmission or reception
+ #[doc(alias = "UARTCR")]
+ CR = 0x030,
+ /// Interrupt FIFO Level Select Register
+ #[doc(alias = "UARTIFLS")]
+ FLS = 0x034,
+ /// Interrupt Mask Set/Clear Register
+ #[doc(alias = "UARTIMSC")]
+ IMSC = 0x038,
+ /// Raw Interrupt Status Register
+ #[doc(alias = "UARTRIS")]
+ RIS = 0x03C,
+ /// Masked Interrupt Status Register
+ #[doc(alias = "UARTMIS")]
+ MIS = 0x040,
+ /// Interrupt Clear Register
+ #[doc(alias = "UARTICR")]
+ ICR = 0x044,
+ /// DMA control Register
+ #[doc(alias = "UARTDMACR")]
+ DMACR = 0x048,
+ ///// Reserved, offsets `0x04C` to `0x07C`.
+ //Reserved = 0x04C,
+}
+
+impl core::convert::TryFrom<u64> for RegisterOffset {
+ type Error = u64;
+
+ fn try_from(value: u64) -> Result<Self, Self::Error> {
+ macro_rules! case {
+ ($($discriminant:ident),*$(,)*) => {
+ /* check that matching on all macro arguments compiles, which means we are not
+ * missing any enum value; if the type definition ever changes this will stop
+ * compiling.
+ */
+ const fn _assert_exhaustive(val: RegisterOffset) {
+ match val {
+ $(RegisterOffset::$discriminant => (),)*
+ }
+ }
+
+ match value {
+ $(x if x == Self::$discriminant as u64 => Ok(Self::$discriminant),)*
+ _ => Err(value),
+ }
+ }
+ }
+ case! { DR, RSR, FR, FBRD, ILPR, IBRD, LCR_H, CR, FLS, IMSC, RIS, MIS, ICR, DMACR }
+ }
+}
+
+pub mod registers {
+ //! Device registers exposed as typed structs which are backed by arbitrary
+ //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc.
+ //!
+ //! All PL011 registers are essentially 32-bit wide, but are typed here as
+ //! bitmaps with only the necessary width. That is, if a struct bitmap
+ //! in this module is for example 16 bits long, it should be conceived
+ //! as a 32-bit register where the unmentioned higher bits are always
+ //! unused thus treated as zero when read or written.
+ use bilge::prelude::*;
+
+ // TODO: FIFO Mode has different semantics
+ /// Data Register, `UARTDR`
+ ///
+ /// The `UARTDR` register is the data register.
+ ///
+ /// For words to be transmitted:
+ ///
+ /// - if the FIFOs are enabled, data written to this location is pushed onto
+ /// the transmit
+ /// FIFO
+ /// - if the FIFOs are not enabled, data is stored in the transmitter
+ /// holding register (the
+ /// bottom word of the transmit FIFO).
+ ///
+ /// The write operation initiates transmission from the UART. The data is
+ /// prefixed with a start bit, appended with the appropriate parity bit
+ /// (if parity is enabled), and a stop bit. The resultant word is then
+ /// transmitted.
+ ///
+ /// For received words:
+ ///
+ /// - if the FIFOs are enabled, the data byte and the 4-bit status (break,
+ /// frame, parity,
+ /// and overrun) is pushed onto the 12-bit wide receive FIFO
+ /// - if the FIFOs are not enabled, the data byte and status are stored in
+ /// the receiving
+ /// holding register (the bottom word of the receive FIFO).
+ ///
+ /// The received data byte is read by performing reads from the `UARTDR`
+ /// register along with the corresponding status information. The status
+ /// information can also be read by a read of the `UARTRSR/UARTECR`
+ /// register.
+ ///
+ /// # Note
+ ///
+ /// You must disable the UART before any of the control registers are
+ /// reprogrammed. When the UART is disabled in the middle of
+ /// transmission or reception, it completes the current character before
+ /// stopping.
+ ///
+ /// # Source
+ /// ARM DDI 0183G 3.3.1 Data Register, UARTDR
+ #[bitsize(16)]
+ #[derive(Clone, Copy, DebugBits, FromBits)]
+ #[doc(alias = "UARTDR")]
+ pub struct Data {
+ _reserved: u4,
+ pub data: u8,
+ pub framing_error: bool,
+ pub parity_error: bool,
+ pub break_error: bool,
+ pub overrun_error: bool,
+ }
+
+ // TODO: FIFO Mode has different semantics
+ /// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR`
+ ///
+ /// The UARTRSR/UARTECR register is the receive status register/error clear
+ /// register. Receive status can also be read from the `UARTRSR`
+ /// register. If the status is read from this register, then the status
+ /// information for break, framing and parity corresponds to the
+ /// data character read from the [Data register](Data), `UARTDR` prior to
+ /// reading the UARTRSR register. The status information for overrun is
+ /// set immediately when an overrun condition occurs.
+ ///
+ ///
+ /// # Note
+ /// The received data character must be read first from the [Data
+ /// Register](Data), `UARTDR` before reading the error status associated
+ /// with that data character from the `UARTRSR` register. This read
+ /// sequence cannot be reversed, because the `UARTRSR` register is
+ /// updated only when a read occurs from the `UARTDR` register. However,
+ /// the status information can also be obtained by reading the `UARTDR`
+ /// register
+ ///
+ /// # Source
+ /// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register,
+ /// UARTRSR/UARTECR
+ #[bitsize(8)]
+ #[derive(Clone, Copy, DebugBits, FromBits)]
+ pub struct ReceiveStatusErrorClear {
+ pub framing_error: bool,
+ pub parity_error: bool,
+ pub break_error: bool,
+ pub overrun_error: bool,
+ _reserved_unpredictable: u4,
+ }
+
+ impl ReceiveStatusErrorClear {
+ pub fn reset(&mut self) {
+ // All the bits are cleared to 0 on reset.
+ *self = 0.into();
+ }
+ }
+
+ impl Default for ReceiveStatusErrorClear {
+ fn default() -> Self {
+ 0.into()
+ }
+ }
+
+ #[bitsize(16)]
+ #[derive(Clone, Copy, DebugBits, FromBits)]
+ /// Flag Register, `UARTFR`
+ #[doc(alias = "UARTFR")]
+ pub struct Flags {
+ /// CTS Clear to send. This bit is the complement of the UART clear to
+ /// send, `nUARTCTS`, modem status input. That is, the bit is 1
+ /// when `nUARTCTS` is LOW.
+ pub clear_to_send: bool,
+ /// DSR Data set ready. This bit is the complement of the UART data set
+ /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when
+ /// `nUARTDSR` is LOW.
+ pub data_set_ready: bool,
+ /// DCD Data carrier detect. This bit is the complement of the UART data
+ /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is
+ /// 1 when `nUARTDCD` is LOW.
+ pub data_carrier_detect: bool,
+ /// BUSY UART busy. If this bit is set to 1, the UART is busy
+ /// transmitting data. This bit remains set until the complete
+ /// byte, including all the stop bits, has been sent from the
+ /// shift register. This bit is set as soon as the transmit FIFO
+ /// becomes non-empty, regardless of whether the UART is enabled
+ /// or not.
+ pub busy: bool,
+ /// RXFE Receive FIFO empty. The meaning of this bit depends on the
+ /// state of the FEN bit in the UARTLCR_H register. If the FIFO
+ /// is disabled, this bit is set when the receive holding
+ /// register is empty. If the FIFO is enabled, the RXFE bit is
+ /// set when the receive FIFO is empty.
+ pub receive_fifo_empty: bool,
+ /// TXFF Transmit FIFO full. The meaning of this bit depends on the
+ /// state of the FEN bit in the UARTLCR_H register. If the FIFO
+ /// is disabled, this bit is set when the transmit holding
+ /// register is full. If the FIFO is enabled, the TXFF bit is
+ /// set when the transmit FIFO is full.
+ pub transmit_fifo_full: bool,
+ /// RXFF Receive FIFO full. The meaning of this bit depends on the state
+ /// of the FEN bit in the UARTLCR_H register. If the FIFO is
+ /// disabled, this bit is set when the receive holding register
+ /// is full. If the FIFO is enabled, the RXFF bit is set when
+ /// the receive FIFO is full.
+ pub receive_fifo_full: bool,
+ /// Transmit FIFO empty. The meaning of this bit depends on the state of
+ /// the FEN bit in the [Line Control register](LineControl),
+ /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the
+ /// transmit holding register is empty. If the FIFO is enabled,
+ /// the TXFE bit is set when the transmit FIFO is empty. This
+ /// bit does not indicate if there is data in the transmit shift
+ /// register.
+ pub transmit_fifo_empty: bool,
+ /// `RI`, is `true` when `nUARTRI` is `LOW`.
+ pub ring_indicator: bool,
+ _reserved_zero_no_modify: u7,
+ }
+
+ impl Flags {
+ pub fn reset(&mut self) {
+ // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1
+ self.set_receive_fifo_full(false);
+ self.set_transmit_fifo_full(false);
+ self.set_busy(false);
+ self.set_receive_fifo_empty(true);
+ self.set_transmit_fifo_empty(true);
+ }
+ }
+
+ impl Default for Flags {
+ fn default() -> Self {
+ let mut ret: Self = 0.into();
+ ret.reset();
+ ret
+ }
+ }
+
+ #[bitsize(16)]
+ #[derive(Clone, Copy, DebugBits, FromBits)]
+ /// Line Control Register, `UARTLCR_H`
+ #[doc(alias = "UARTLCR_H")]
+ pub struct LineControl {
+ /// 15:8 - Reserved, do not modify, read as zero.
+ _reserved_zero_no_modify: u8,
+ /// 7 SPS Stick parity select.
+ /// 0 = stick parity is disabled
+ /// 1 = either:
+ /// • if the EPS bit is 0 then the parity bit is transmitted and checked
+ /// as a 1 • if the EPS bit is 1 then the parity bit is
+ /// transmitted and checked as a 0. This bit has no effect when
+ /// the PEN bit disables parity checking and generation. See Table 3-11
+ /// on page 3-14 for the parity truth table.
+ pub sticky_parity: bool,
+ /// WLEN Word length. These bits indicate the number of data bits
+ /// transmitted or received in a frame as follows: b11 = 8 bits
+ /// b10 = 7 bits
+ /// b01 = 6 bits
+ /// b00 = 5 bits.
+ pub word_length: WordLength,
+ /// FEN Enable FIFOs:
+ /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
+ /// 1-byte-deep holding registers 1 = transmit and receive FIFO
+ /// buffers are enabled (FIFO mode).
+ pub fifos_enabled: Mode,
+ /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits
+ /// are transmitted at the end of the frame. The receive
+ /// logic does not check for two stop bits being received.
+ pub two_stops_bits: bool,
+ /// EPS Even parity select. Controls the type of parity the UART uses
+ /// during transmission and reception:
+ /// - 0 = odd parity. The UART generates or checks for an odd number of
+ /// 1s in the data and parity bits.
+ /// - 1 = even parity. The UART generates or checks for an even number
+ /// of 1s in the data and parity bits.
+ /// This bit has no effect when the `PEN` bit disables parity checking
+ /// and generation. See Table 3-11 on page 3-14 for the parity
+ /// truth table.
+ pub parity: Parity,
+ /// 1 PEN Parity enable:
+ ///
+ /// - 0 = parity is disabled and no parity bit added to the data frame
+ /// - 1 = parity checking and generation is enabled.
+ ///
+ /// See Table 3-11 on page 3-14 for the parity truth table.
+ pub parity_enabled: bool,
+ /// BRK Send break.
+ ///
+ /// If this bit is set to `1`, a low-level is continually output on the
+ /// `UARTTXD` output, after completing transmission of the
+ /// current character. For the proper execution of the break command,
+ /// the software must set this bit for at least two complete
+ /// frames. For normal use, this bit must be cleared to `0`.
+ pub send_break: bool,
+ }
+
+ impl LineControl {
+ pub fn reset(&mut self) {
+ // All the bits are cleared to 0 when reset.
+ *self = 0.into();
+ }
+ }
+
+ impl Default for LineControl {
+ fn default() -> Self {
+ 0.into()
+ }
+ }
+
+ #[bitsize(1)]
+ #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
+ /// `EPS` "Even parity select", field of [Line Control
+ /// register](LineControl).
+ pub enum Parity {
+ /// - 0 = odd parity. The UART generates or checks for an odd number of
+ /// 1s in the data and parity bits.
+ Odd = 0,
+ /// - 1 = even parity. The UART generates or checks for an even number
+ /// of 1s in the data and parity bits.
+ Even = 1,
+ }
+
+ #[bitsize(1)]
+ #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
+ /// `FEN` "Enable FIFOs" or Device mode, field of [Line Control
+ /// register](LineControl).
+ pub enum Mode {
+ /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
+ /// 1-byte-deep holding registers
+ Character = 0,
+ /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode).
+ FIFO = 1,
+ }
+
+ impl From<Mode> for bool {
+ fn from(val: Mode) -> Self {
+ matches!(val, Mode::FIFO)
+ }
+ }
+
+ #[bitsize(2)]
+ #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
+ /// `WLEN` Word length, field of [Line Control register](LineControl).
+ ///
+ /// These bits indicate the number of data bits transmitted or received in a
+ /// frame as follows:
+ pub enum WordLength {
+ /// b11 = 8 bits
+ _8Bits = 0b11,
+ /// b10 = 7 bits
+ _7Bits = 0b10,
+ /// b01 = 6 bits
+ _6Bits = 0b01,
+ /// b00 = 5 bits.
+ _5Bits = 0b00,
+ }
+
+ /// Control Register, `UARTCR`
+ ///
+ /// The `UARTCR` register is the control register. All the bits are cleared
+ /// to `0` on reset except for bits `9` and `8` that are set to `1`.
+ ///
+ /// # Source
+ /// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12
+ #[bitsize(16)]
+ #[doc(alias = "UARTCR")]
+ #[derive(Clone, Copy, DebugBits, FromBits)]
+ pub struct Control {
+ /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled
+ /// in the middle of transmission or reception, it completes the current
+ /// character before stopping. 1 = the UART is enabled. Data
+ /// transmission and reception occurs for either UART signals or SIR
+ /// signals depending on the setting of the SIREN bit.
+ pub enable_uart: bool,
+ /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT`
+ /// remains LOW (no light pulse generated), and signal transitions on
+ /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is
+ /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH,
+ /// in the marking state. Signal transitions on UARTRXD or modem status
+ /// inputs have no effect. This bit has no effect if the UARTEN bit
+ /// disables the UART.
+ pub enable_sir: bool,
+ /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding
+ /// mode. If this bit is cleared to 0, low-level bits are transmitted as
+ /// an active high pulse with a width of 3/ 16th of the bit period. If
+ /// this bit is set to 1, low-level bits are transmitted with a pulse
+ /// width that is 3 times the period of the IrLPBaud16 input signal,
+ /// regardless of the selected bit rate. Setting this bit uses less
+ /// power, but might reduce transmission distances.
+ pub sir_lowpower_irda_mode: u1,
+ /// Reserved, do not modify, read as zero.
+ _reserved_zero_no_modify: u4,
+ /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is
+ /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR
+ /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed
+ /// through to the SIRIN path. The SIRTEST bit in the test register must
+ /// be set to 1 to override the normal half-duplex SIR operation. This
+ /// must be the requirement for accessing the test registers during
+ /// normal operation, and SIRTEST must be cleared to 0 when loopback
+ /// testing is finished. This feature reduces the amount of external
+ /// coupling required during system test. If this bit is set to 1, and
+ /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the
+ /// UARTRXD path. In either SIR mode or UART mode, when this bit is set,
+ /// the modem outputs are also fed through to the modem inputs. This bit
+ /// is cleared to 0 on reset, to disable loopback.
+ pub enable_loopback: bool,
+ /// `TXE` Transmit enable. If this bit is set to 1, the transmit section
+ /// of the UART is enabled. Data transmission occurs for either UART
+ /// signals, or SIR signals depending on the setting of the SIREN bit.
+ /// When the UART is disabled in the middle of transmission, it
+ /// completes the current character before stopping.
+ pub enable_transmit: bool,
+ /// `RXE` Receive enable. If this bit is set to 1, the receive section
+ /// of the UART is enabled. Data reception occurs for either UART
+ /// signals or SIR signals depending on the setting of the SIREN bit.
+ /// When the UART is disabled in the middle of reception, it completes
+ /// the current character before stopping.
+ pub enable_receive: bool,
+ /// `DTR` Data transmit ready. This bit is the complement of the UART
+ /// data transmit ready, `nUARTDTR`, modem status output. That is, when
+ /// the bit is programmed to a 1 then `nUARTDTR` is LOW.
+ pub data_transmit_ready: bool,
+ /// `RTS` Request to send. This bit is the complement of the UART
+ /// request to send, `nUARTRTS`, modem status output. That is, when the
+ /// bit is programmed to a 1 then `nUARTRTS` is LOW.
+ pub request_to_send: bool,
+ /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`)
+ /// modem status output. That is, when the bit is programmed to a 1 the
+ /// output is 0. For DTE this can be used as Data Carrier Detect (DCD).
+ pub out_1: bool,
+ /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`)
+ /// modem status output. That is, when the bit is programmed to a 1, the
+ /// output is 0. For DTE this can be used as Ring Indicator (RI).
+ pub out_2: bool,
+ /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1,
+ /// RTS hardware flow control is enabled. Data is only requested when
+ /// there is space in the receive FIFO for it to be received.
+ pub rts_hardware_flow_control_enable: bool,
+ /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1,
+ /// CTS hardware flow control is enabled. Data is only transmitted when
+ /// the `nUARTCTS` signal is asserted.
+ pub cts_hardware_flow_control_enable: bool,
+ }
+
+ impl Control {
+ pub fn reset(&mut self) {
+ *self = 0.into();
+ self.set_enable_receive(true);
+ self.set_enable_transmit(true);
+ }
+ }
+
+ impl Default for Control {
+ fn default() -> Self {
+ let mut ret: Self = 0.into();
+ ret.reset();
+ ret
+ }
+ }
+
+ /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC
+ pub const INT_OE: u32 = 1 << 10;
+ pub const INT_BE: u32 = 1 << 9;
+ pub const INT_PE: u32 = 1 << 8;
+ pub const INT_FE: u32 = 1 << 7;
+ pub const INT_RT: u32 = 1 << 6;
+ pub const INT_TX: u32 = 1 << 5;
+ pub const INT_RX: u32 = 1 << 4;
+ pub const INT_DSR: u32 = 1 << 3;
+ pub const INT_DCD: u32 = 1 << 2;
+ pub const INT_CTS: u32 = 1 << 1;
+ pub const INT_RI: u32 = 1 << 0;
+ pub const INT_E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
+ pub const INT_MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
+
+ #[repr(u32)]
+ pub enum Interrupt {
+ OE = 1 << 10,
+ BE = 1 << 9,
+ PE = 1 << 8,
+ FE = 1 << 7,
+ RT = 1 << 6,
+ TX = 1 << 5,
+ RX = 1 << 4,
+ DSR = 1 << 3,
+ DCD = 1 << 2,
+ CTS = 1 << 1,
+ RI = 1 << 0,
+ }
+
+ impl Interrupt {
+ pub const E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
+ pub const MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
+ }
+}
+
+// TODO: You must disable the UART before any of the control registers are
+// reprogrammed. When the UART is disabled in the middle of transmission or
+// reception, it completes the current character before stopping
diff --git a/rust/hw/char/pl011/src/memory_ops.rs b/rust/hw/char/pl011/src/memory_ops.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8d066ebf6d016fa30db004933751a854d7e59117
--- /dev/null
+++ b/rust/hw/char/pl011/src/memory_ops.rs
@@ -0,0 +1,59 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use core::{mem::MaybeUninit, ptr::NonNull};
+
+use qemu_api::bindings::*;
+
+use crate::device::PL011State;
+
+pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps {
+ read: Some(pl011_read),
+ write: Some(pl011_write),
+ read_with_attrs: None,
+ write_with_attrs: None,
+ endianness: device_endian::DEVICE_NATIVE_ENDIAN,
+ valid: unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_1>::zeroed().assume_init() },
+ impl_: MemoryRegionOps__bindgen_ty_2 {
+ min_access_size: 4,
+ max_access_size: 4,
+ ..unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_2>::zeroed().assume_init() }
+ },
+};
+
+#[no_mangle]
+unsafe extern "C" fn pl011_read(
+ opaque: *mut core::ffi::c_void,
+ addr: hwaddr,
+ size: core::ffi::c_uint,
+) -> u64 {
+ assert!(!opaque.is_null());
+ let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
+ let val = unsafe { state.as_mut().read(addr, size) };
+ match val {
+ std::ops::ControlFlow::Break(val) => val,
+ std::ops::ControlFlow::Continue(val) => {
+ // SAFETY: self.char_backend is a valid CharBackend instance after it's been
+ // initialized in realize().
+ let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) };
+ unsafe { qemu_chr_fe_accept_input(cb_ptr) };
+
+ val
+ }
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn pl011_write(
+ opaque: *mut core::ffi::c_void,
+ addr: hwaddr,
+ data: u64,
+ _size: core::ffi::c_uint,
+) {
+ unsafe {
+ assert!(!opaque.is_null());
+ let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_mut().write(addr, data)
+ }
+}
diff --git a/rust/hw/meson.build b/rust/hw/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..860196645e719624d2e2e6bc301b62b81ab2e19b
--- /dev/null
+++ b/rust/hw/meson.build
@@ -0,0 +1 @@
+subdir('char')
diff --git a/rust/meson.build b/rust/meson.build
index 7a32b1b195083571931ad589965c10ddaf6383b1..def77389cddc52f5d4503840e9bdfb1207586fa2 100644
--- a/rust/meson.build
+++ b/rust/meson.build
@@ -1,2 +1,4 @@
subdir('qemu-api-macros')
subdir('qemu-api')
+
+subdir('hw')
diff --git a/scripts/archive-source.sh b/scripts/archive-source.sh
index 62a2cf45d28ed5565076443b9f931a647d395542..30677c3ec9032ea01090f74602d839d1c571d012 100755
--- a/scripts/archive-source.sh
+++ b/scripts/archive-source.sh
@@ -27,7 +27,9 @@ sub_file="${sub_tdir}/submodule.tar"
# in their checkout, because the build environment is completely
# different to the host OS.
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
- berkeley-testfloat-3 proc-macro2-1-rs quote-1-rs
+ berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
+ bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
+ proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
sub_deinit=""
diff --git a/scripts/make-release b/scripts/make-release
index cf7d694ef73ba1c6c5afad5d211a42f6a6fe1577..8dc939124c4fd4abf3509c3b64c0588bc8810962 100755
--- a/scripts/make-release
+++ b/scripts/make-release
@@ -18,7 +18,9 @@ fi
# Only include wraps that are invoked with subproject()
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
- berkeley-testfloat-3 proc-macro2-1-rs quote-1-rs
+ berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
+ bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
+ proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
src="$1"
diff --git a/scripts/rust/rust_root_crate.sh b/scripts/rust/rust_root_crate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..975bddf7f1a4c6ca7770f800bdc894cdff1f3ab1
--- /dev/null
+++ b/scripts/rust/rust_root_crate.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -eu
+
+cat <<EOF
+// @generated
+// This file is autogenerated by scripts/rust_root_crate.sh
+
+EOF
+
+for crate in $*; do
+ echo "extern crate $crate;"
+done
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index b6888182ca4e5b59b90cfde1792a5770e9a5240a..50f173f90dbe5dde1162cfe96636f63637574c74 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -6,6 +6,13 @@
/keycodemapdb
/libvfio-user
/slirp
+/arbitrary-int-1.2.7
+/bilge-0.2.0
+/bilge-impl-0.2.0
+/either-1.12.0
+/itertools-0.11.0
+/proc-macro-error-1.0.4
+/proc-macro-error-attr-1.0.4
/proc-macro2-1.0.84
/quote-1.0.36
/syn-2.0.66
diff --git a/subprojects/arbitrary-int-1-rs.wrap b/subprojects/arbitrary-int-1-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..e580538a8776001147bfc5c5c73df2bfccac6347
--- /dev/null
+++ b/subprojects/arbitrary-int-1-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = arbitrary-int-1.2.7
+source_url = https://crates.io/api/v1/crates/arbitrary-int/1.2.7/download
+source_filename = arbitrary-int-1.2.7.tar.gz
+source_hash = c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d
+#method = cargo
+patch_directory = arbitrary-int-1-rs
diff --git a/subprojects/bilge-0.2-rs.wrap b/subprojects/bilge-0.2-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..7a4339d29896922c392e2e1952e02a1ee82b1874
--- /dev/null
+++ b/subprojects/bilge-0.2-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = bilge-0.2.0
+source_url = https://crates.io/api/v1/crates/bilge/0.2.0/download
+source_filename = bilge-0.2.0.tar.gz
+source_hash = dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57
+#method = cargo
+patch_directory = bilge-0.2-rs
diff --git a/subprojects/bilge-impl-0.2-rs.wrap b/subprojects/bilge-impl-0.2-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..eefb10c36c24007d48833748cf200d67b6ff7ee7
--- /dev/null
+++ b/subprojects/bilge-impl-0.2-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = bilge-impl-0.2.0
+source_url = https://crates.io/api/v1/crates/bilge-impl/0.2.0/download
+source_filename = bilge-impl-0.2.0.tar.gz
+source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8
+#method = cargo
+patch_directory = bilge-impl-0.2-rs
diff --git a/subprojects/either-1-rs.wrap b/subprojects/either-1-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..6046712036c8db1b142d083ae6f8a2d58d6203ab
--- /dev/null
+++ b/subprojects/either-1-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = either-1.12.0
+source_url = https://crates.io/api/v1/crates/either/1.12.0/download
+source_filename = either-1.12.0.tar.gz
+source_hash = 3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b
+#method = cargo
+patch_directory = either-1-rs
diff --git a/subprojects/itertools-0.11-rs.wrap b/subprojects/itertools-0.11-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..66b05252cd55ed3149b79b992a6d224e9789ba97
--- /dev/null
+++ b/subprojects/itertools-0.11-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = itertools-0.11.0
+source_url = https://crates.io/api/v1/crates/itertools/0.11.0/download
+source_filename = itertools-0.11.0.tar.gz
+source_hash = b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57
+#method = cargo
+patch_directory = itertools-0.11-rs
diff --git a/subprojects/packagefiles/arbitrary-int-1-rs/meson.build b/subprojects/packagefiles/arbitrary-int-1-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..34a189cbaec77ba6631c6a8cb40d7eb3f32099ee
--- /dev/null
+++ b/subprojects/packagefiles/arbitrary-int-1-rs/meson.build
@@ -0,0 +1,19 @@
+project('arbitrary-int-1-rs', 'rust',
+ version: '1.2.7',
+ license: 'MIT',
+ default_options: [])
+
+_arbitrary_int_rs = static_library(
+ 'arbitrary_int',
+ files('src/lib.rs'),
+ gnu_symbol_visibility: 'hidden',
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_abi: 'rust',
+ dependencies: [],
+)
+
+arbitrary_int_dep = declare_dependency(
+ link_with: _arbitrary_int_rs,
+)
+
+meson.override_dependency('arbitrary-int-1-rs', arbitrary_int_dep)
diff --git a/subprojects/packagefiles/bilge-0.2-rs/meson.build b/subprojects/packagefiles/bilge-0.2-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..a6ed4a8f0cde70be0bf75cd1ad40bc64e3a11a3a
--- /dev/null
+++ b/subprojects/packagefiles/bilge-0.2-rs/meson.build
@@ -0,0 +1,29 @@
+project(
+ 'bilge-0.2-rs',
+ 'rust',
+ version : '0.2.0',
+ license : 'MIT or Apache-2.0',
+)
+
+subproject('arbitrary-int-1-rs', required: true)
+subproject('bilge-impl-0.2-rs', required: true)
+
+arbitrary_int_dep = dependency('arbitrary-int-1-rs')
+bilge_impl_dep = dependency('bilge-impl-0.2-rs')
+
+lib = static_library(
+ 'bilge',
+ 'src/lib.rs',
+ override_options : ['rust_std=2021', 'build.rust_std=2021'],
+ rust_abi : 'rust',
+ dependencies: [
+ arbitrary_int_dep,
+ bilge_impl_dep,
+ ],
+)
+
+bilge_dep = declare_dependency(
+ link_with : [lib],
+)
+
+meson.override_dependency('bilge-0.2-rs', bilge_dep)
diff --git a/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build b/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..80243c7024d898826cc3c98ba5b78ef286b79ce9
--- /dev/null
+++ b/subprojects/packagefiles/bilge-impl-0.2-rs/meson.build
@@ -0,0 +1,45 @@
+project('bilge-impl-0.2-rs', 'rust',
+ version: '0.2.0',
+ license: 'MIT OR Apache-2.0',
+ default_options: [])
+
+subproject('itertools-0.11-rs', required: true)
+subproject('proc-macro-error-attr-1-rs', required: true)
+subproject('proc-macro-error-1-rs', required: true)
+subproject('quote-1-rs', required: true)
+subproject('syn-2-rs', required: true)
+subproject('proc-macro2-1-rs', required: true)
+
+itertools_dep = dependency('itertools-0.11-rs', native: true)
+proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
+proc_macro_error_dep = dependency('proc-macro-error-1-rs', native: 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)
+
+rust = import('rust')
+
+_bilge_impl_rs = rust.proc_macro(
+ 'bilge_impl',
+ files('src/lib.rs'),
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_args: [
+ '--cfg', 'use_fallback',
+ '--cfg', 'feature="syn-error"',
+ '--cfg', 'feature="proc-macro"',
+ ],
+ dependencies: [
+ itertools_dep,
+ proc_macro_error_attr_dep,
+ proc_macro_error_dep,
+ quote_dep,
+ syn_dep,
+ proc_macro2_dep,
+ ],
+)
+
+bilge_impl_dep = declare_dependency(
+ link_with: _bilge_impl_rs,
+)
+
+meson.override_dependency('bilge-impl-0.2-rs', bilge_impl_dep)
diff --git a/subprojects/packagefiles/either-1-rs/meson.build b/subprojects/packagefiles/either-1-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..a5842eb3a6a63cf94883663758ca5eb11225ad58
--- /dev/null
+++ b/subprojects/packagefiles/either-1-rs/meson.build
@@ -0,0 +1,24 @@
+project('either-1-rs', 'rust',
+ version: '1.12.0',
+ license: 'MIT OR Apache-2.0',
+ default_options: [])
+
+_either_rs = static_library(
+ 'either',
+ files('src/lib.rs'),
+ gnu_symbol_visibility: 'hidden',
+ override_options: ['rust_std=2018', 'build.rust_std=2018'],
+ rust_abi: 'rust',
+ rust_args: [
+ '--cfg', 'feature="use_std"',
+ '--cfg', 'feature="use_alloc"',
+ ],
+ dependencies: [],
+ native: true,
+)
+
+either_dep = declare_dependency(
+ link_with: _either_rs,
+)
+
+meson.override_dependency('either-1-rs', either_dep, native: true)
diff --git a/subprojects/packagefiles/itertools-0.11-rs/meson.build b/subprojects/packagefiles/itertools-0.11-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..13d2d27019d215fb793c457e494be7387a7ef455
--- /dev/null
+++ b/subprojects/packagefiles/itertools-0.11-rs/meson.build
@@ -0,0 +1,30 @@
+project('itertools-0.11-rs', 'rust',
+ version: '0.11.0',
+ license: 'MIT OR Apache-2.0',
+ default_options: [])
+
+subproject('either-1-rs', required: true)
+
+either_dep = dependency('either-1-rs', native: true)
+
+_itertools_rs = static_library(
+ 'itertools',
+ files('src/lib.rs'),
+ gnu_symbol_visibility: 'hidden',
+ override_options: ['rust_std=2018', 'build.rust_std=2018'],
+ rust_abi: 'rust',
+ rust_args: [
+ '--cfg', 'feature="use_std"',
+ '--cfg', 'feature="use_alloc"',
+ ],
+ dependencies: [
+ either_dep,
+ ],
+ native: true,
+)
+
+itertools_dep = declare_dependency(
+ link_with: _itertools_rs,
+)
+
+meson.override_dependency('itertools-0.11-rs', itertools_dep, native: true)
diff --git a/subprojects/packagefiles/proc-macro-error-1-rs/meson.build b/subprojects/packagefiles/proc-macro-error-1-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..38ea7b89d39d3f131959f00665b51a49d5bc95e5
--- /dev/null
+++ b/subprojects/packagefiles/proc-macro-error-1-rs/meson.build
@@ -0,0 +1,40 @@
+project('proc-macro-error-1-rs', 'rust',
+ version: '1.0.4',
+ license: 'MIT OR Apache-2.0',
+ default_options: [])
+
+subproject('proc-macro-error-attr-1-rs', required: true)
+subproject('quote-1-rs', required: true)
+subproject('syn-2-rs', required: true)
+subproject('proc-macro2-1-rs', required: true)
+
+proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
+proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
+quote_dep = dependency('quote-1-rs', native: true)
+syn_dep = dependency('syn-2-rs', native: true)
+
+_proc_macro_error_rs = static_library(
+ 'proc_macro_error',
+ files('src/lib.rs'),
+ override_options: ['rust_std=2018', 'build.rust_std=2018'],
+ rust_abi: 'rust',
+ rust_args: [
+ '--cfg', 'use_fallback',
+ '--cfg', 'feature="syn-error"',
+ '--cfg', 'feature="proc-macro"',
+ '-A', 'non_fmt_panics'
+ ],
+ dependencies: [
+ proc_macro_error_attr_dep,
+ proc_macro2_dep,
+ quote_dep,
+ syn_dep,
+ ],
+ native: true,
+)
+
+proc_macro_error_dep = declare_dependency(
+ link_with: _proc_macro_error_rs,
+)
+
+meson.override_dependency('proc-macro-error-1-rs', proc_macro_error_dep, native: true)
diff --git a/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build b/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..d900c54cfd1ad6c8c415438c2e9dd51695c82150
--- /dev/null
+++ b/subprojects/packagefiles/proc-macro-error-attr-1-rs/meson.build
@@ -0,0 +1,32 @@
+project('proc-macro-error-attr-1-rs', 'rust',
+ version: '1.12.0',
+ license: 'MIT OR Apache-2.0',
+ default_options: [])
+
+subproject('proc-macro2-1-rs', required: true)
+subproject('quote-1-rs', required: true)
+
+proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
+quote_dep = dependency('quote-1-rs', native: true)
+
+rust = import('rust')
+_proc_macro_error_attr_rs = rust.proc_macro(
+ 'proc_macro_error_attr',
+ files('src/lib.rs'),
+ override_options: ['rust_std=2018', 'build.rust_std=2018'],
+ rust_args: [
+ '--cfg', 'use_fallback',
+ '--cfg', 'feature="syn-error"',
+ '--cfg', 'feature="proc-macro"'
+ ],
+ dependencies: [
+ proc_macro2_dep,
+ quote_dep,
+ ],
+)
+
+proc_macro_error_attr_dep = declare_dependency(
+ link_with: _proc_macro_error_attr_rs,
+)
+
+meson.override_dependency('proc-macro-error-attr-1-rs', proc_macro_error_attr_dep, native: true)
diff --git a/subprojects/packagefiles/unicode-ident-1-rs/meson.build b/subprojects/packagefiles/unicode-ident-1-rs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..54f2376854504236689604f8ab08d351e4cceae9
--- /dev/null
+++ b/subprojects/packagefiles/unicode-ident-1-rs/meson.build
@@ -0,0 +1,20 @@
+project('unicode-ident-1-rs', 'rust',
+ version: '1.0.12',
+ license: '(MIT OR Apache-2.0) AND Unicode-DFS-2016',
+ default_options: [])
+
+_unicode_ident_rs = static_library(
+ 'unicode_ident',
+ files('src/lib.rs'),
+ gnu_symbol_visibility: 'hidden',
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_abi: 'rust',
+ dependencies: [],
+ native: true,
+)
+
+unicode_ident_dep = declare_dependency(
+ link_with: _unicode_ident_rs,
+)
+
+meson.override_dependency('unicode-ident-1-rs', unicode_ident_dep, native: true)
diff --git a/subprojects/proc-macro-error-1-rs.wrap b/subprojects/proc-macro-error-1-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..b7db03b06a019562b2ad77bb252d50b7e83c0b33
--- /dev/null
+++ b/subprojects/proc-macro-error-1-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = proc-macro-error-1.0.4
+source_url = https://crates.io/api/v1/crates/proc-macro-error/1.0.4/download
+source_filename = proc-macro-error-1.0.4.tar.gz
+source_hash = da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c
+#method = cargo
+patch_directory = proc-macro-error-1-rs
diff --git a/subprojects/proc-macro-error-attr-1-rs.wrap b/subprojects/proc-macro-error-attr-1-rs.wrap
new file mode 100644
index 0000000000000000000000000000000000000000..d13d8a239ac259132cd905bc512e9024b2f30fb8
--- /dev/null
+++ b/subprojects/proc-macro-error-attr-1-rs.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = proc-macro-error-attr-1.0.4
+source_url = https://crates.io/api/v1/crates/proc-macro-error-attr/1.0.4/download
+source_filename = proc-macro-error-attr-1.0.4.tar.gz
+source_hash = a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869
+#method = cargo
+patch_directory = proc-macro-error-attr-1-rs
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
2024-10-24 14:02 ` [PATCH 01/11] Revert "rust: add PL011 device model" Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 02/11] rust: add PL011 device model Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 15:13 ` Alex Bennée
` (2 more replies)
2024-10-24 14:03 ` [PATCH 04/11] rust: add support for migration in device models Manos Pitsidianakis
` (8 subsequent siblings)
11 siblings, 3 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Add a new derive procedural macro to declare device models. Add
corresponding DeviceImpl trait after already existing ObjectImpl trait.
At the same time, add instance_init, instance_post_init,
instance_finalize methods to the ObjectImpl trait and call them from the
ObjectImplUnsafe trait, which is generated by the procedural macro.
This allows all the boilerplate device model registration to be handled
by macros, and all pertinent details to be declared through proc macro
attributes or trait associated constants and methods.
The device class can now be generated automatically and the name can be
optionally overridden:
------------------------ >8 ------------------------
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
#[device(class_name_override = PL011Class)]
/// PL011 Device Model in QEMU
pub struct PL011State {
------------------------ >8 ------------------------
Properties are now marked as field attributes:
------------------------ >8 ------------------------
#[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
pub char_backend: CharBackend,
------------------------ >8 ------------------------
Object methods (instance_init, etc) methods are now trait methods:
------------------------ >8 ------------------------
/// Trait a type must implement to be registered with QEMU.
pub trait ObjectImpl {
type Class: ClassImpl;
const TYPE_NAME: &'static CStr;
const PARENT_TYPE_NAME: Option<&'static CStr>;
const ABSTRACT: bool;
unsafe fn instance_init(&mut self) {}
fn instance_post_init(&mut self) {}
fn instance_finalize(&mut self) {}
}
------------------------ >8 ------------------------
Device methods (realize/reset etc) are now safe idiomatic trait methods:
------------------------ >8 ------------------------
/// Implementation methods for device types.
pub trait DeviceImpl: ObjectImpl {
fn realize(&mut self) {}
fn reset(&mut self) {}
}
------------------------ >8 ------------------------
The derive Device macro is responsible for creating all the extern "C" FFI
functions that QEMU needs to call these methods.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 124 +++-----
rust/hw/char/pl011/src/device_class.rs | 70 -----
rust/hw/char/pl011/src/lib.rs | 1 -
rust/qemu-api-macros/src/device.rs | 433 ++++++++++++++++++++++++++
rust/qemu-api-macros/src/lib.rs | 45 +--
rust/qemu-api-macros/src/object.rs | 107 +++++++
rust/qemu-api-macros/src/symbols.rs | 55 ++++
rust/qemu-api-macros/src/utilities.rs | 152 +++++++++
rust/qemu-api/meson.build | 3 +-
rust/qemu-api/src/definitions.rs | 97 ------
rust/qemu-api/src/device_class.rs | 128 --------
rust/qemu-api/src/lib.rs | 6 +-
rust/qemu-api/src/objects.rs | 90 ++++++
rust/qemu-api/src/tests.rs | 49 ---
subprojects/packagefiles/syn-2-rs/meson.build | 1 +
15 files changed, 902 insertions(+), 459 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index c7193b41beec0b177dbc75ac0e43fcfea4c82bfb..c469877b1ca70dd1a02e3a2449c65ad3e57c93ae 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -9,7 +9,7 @@
use qemu_api::{
bindings::{self, *},
- definitions::ObjectImpl,
+ objects::*,
};
use crate::{
@@ -26,7 +26,8 @@
pub const PL011_FIFO_DEPTH: usize = 16_usize;
#[repr(C)]
-#[derive(Debug, qemu_api_macros::Object)]
+#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
+#[device(class_name_override = PL011Class)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
@@ -51,6 +52,7 @@ pub struct PL011State {
pub read_count: usize,
pub read_trigger: usize,
#[doc(alias = "chr")]
+ #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
pub char_backend: CharBackend,
/// QEMU interrupts
///
@@ -68,38 +70,17 @@ pub struct PL011State {
#[doc(alias = "clk")]
pub clock: NonNull<Clock>,
#[doc(alias = "migrate_clk")]
+ #[property(name = c"migrate-clk", qdev_prop = qdev_prop_bool)]
pub migrate_clock: bool,
}
impl ObjectImpl for PL011State {
type Class = PL011Class;
- const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
+
const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE);
const ABSTRACT: bool = false;
- const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init);
- const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
- const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
-}
-#[repr(C)]
-pub struct PL011Class {
- _inner: [u8; 0],
-}
-
-impl qemu_api::definitions::Class for PL011Class {
- const CLASS_INIT: Option<
- unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
- > = Some(crate::device_class::pl011_class_init);
- const CLASS_BASE_INIT: Option<
- unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
- > = None;
-}
-
-#[used]
-pub static CLK_NAME: &CStr = c"clk";
-
-impl PL011State {
/// Initializes a pre-allocated, unitialized instance of `PL011State`.
///
/// # Safety
@@ -108,7 +89,7 @@ impl PL011State {
/// `PL011State` type. It must not be called more than once on the same
/// location/instance. All its fields are expected to hold unitialized
/// values with the sole exception of `parent_obj`.
- pub unsafe fn init(&mut self) {
+ unsafe fn instance_init(&mut self) {
let dev = addr_of_mut!(*self).cast::<DeviceState>();
// SAFETY:
//
@@ -120,7 +101,7 @@ pub unsafe fn init(&mut self) {
addr_of_mut!(*self).cast::<Object>(),
&PL011_OPS,
addr_of_mut!(*self).cast::<c_void>(),
- Self::TYPE_INFO.name,
+ Self::TYPE_NAME.as_ptr(),
0x1000,
);
let sbd = addr_of_mut!(*self).cast::<SysBusDevice>();
@@ -147,7 +128,49 @@ pub unsafe fn init(&mut self) {
.unwrap();
}
}
+}
+impl DeviceImpl for PL011State {
+ fn realize(&mut self) {
+ // SAFETY: self.char_backend has the correct size and alignment for a
+ // CharBackend object, and its callbacks are of the correct types.
+ unsafe {
+ qemu_chr_fe_set_handlers(
+ addr_of_mut!(self.char_backend),
+ Some(pl011_can_receive),
+ Some(pl011_receive),
+ Some(pl011_event),
+ None,
+ addr_of_mut!(*self).cast::<c_void>(),
+ core::ptr::null_mut(),
+ true,
+ );
+ }
+ }
+
+ fn reset(&mut self) {
+ self.line_control.reset();
+ self.receive_status_error_clear.reset();
+ self.dmacr = 0;
+ self.int_enabled = 0;
+ self.int_level = 0;
+ self.ilpr = 0;
+ self.ibrd = 0;
+ self.fbrd = 0;
+ self.read_trigger = 1;
+ self.ifl = 0x12;
+ self.control.reset();
+ self.flags = 0.into();
+ self.reset_fifo();
+ }
+}
+
+impl qemu_api::objects::Migrateable for PL011State {}
+
+#[used]
+pub static CLK_NAME: &CStr = c"clk";
+
+impl PL011State {
pub fn read(
&mut self,
offset: hwaddr,
@@ -394,39 +417,6 @@ fn set_read_trigger(&mut self) {
self.read_trigger = 1;
}
- pub fn realize(&mut self) {
- // SAFETY: self.char_backend has the correct size and alignment for a
- // CharBackend object, and its callbacks are of the correct types.
- unsafe {
- qemu_chr_fe_set_handlers(
- addr_of_mut!(self.char_backend),
- Some(pl011_can_receive),
- Some(pl011_receive),
- Some(pl011_event),
- None,
- addr_of_mut!(*self).cast::<c_void>(),
- core::ptr::null_mut(),
- true,
- );
- }
- }
-
- pub fn reset(&mut self) {
- self.line_control.reset();
- self.receive_status_error_clear.reset();
- self.dmacr = 0;
- self.int_enabled = 0;
- self.int_level = 0;
- self.ilpr = 0;
- self.ibrd = 0;
- self.fbrd = 0;
- self.read_trigger = 1;
- self.ifl = 0x12;
- self.control.reset();
- self.flags = 0.into();
- self.reset_fifo();
- }
-
pub fn reset_fifo(&mut self) {
self.read_count = 0;
self.read_pos = 0;
@@ -583,17 +573,3 @@ pub fn update(&self) {
dev
}
}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
- unsafe {
- debug_assert!(!obj.is_null());
- let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
- state.as_mut().init();
- }
-}
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
deleted file mode 100644
index b7ab31af02d7bb50ae94be0b153baafc7ccfa375..0000000000000000000000000000000000000000
--- a/rust/hw/char/pl011/src/device_class.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use core::ptr::NonNull;
-
-use qemu_api::{bindings::*, definitions::ObjectImpl};
-
-use crate::device::PL011State;
-
-#[used]
-pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
- name: PL011State::TYPE_INFO.name,
- unmigratable: true,
- ..unsafe { ::core::mem::MaybeUninit::<VMStateDescription>::zeroed().assume_init() }
-};
-
-qemu_api::declare_properties! {
- PL011_PROPERTIES,
- qemu_api::define_property!(
- c"chardev",
- PL011State,
- char_backend,
- unsafe { &qdev_prop_chr },
- CharBackend
- ),
- qemu_api::define_property!(
- c"migrate-clk",
- PL011State,
- migrate_clock,
- unsafe { &qdev_prop_bool },
- bool
- ),
-}
-
-qemu_api::device_class_init! {
- pl011_class_init,
- props => PL011_PROPERTIES,
- realize_fn => Some(pl011_realize),
- legacy_reset_fn => Some(pl011_reset),
- vmsd => VMSTATE_PL011,
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
- unsafe {
- assert!(!dev.is_null());
- let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
- state.as_mut().realize();
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
- unsafe {
- assert!(!dev.is_null());
- let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
- state.as_mut().reset();
- }
-}
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
index 2939ee50c99ceaacf6ec68127272d58814e33679..f4d9cce4b01f605cfcbec7ea87c8b2009d77ee52 100644
--- a/rust/hw/char/pl011/src/lib.rs
+++ b/rust/hw/char/pl011/src/lib.rs
@@ -42,7 +42,6 @@
extern crate qemu_api;
pub mod device;
-pub mod device_class;
pub mod memory_ops;
pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
diff --git a/rust/qemu-api-macros/src/device.rs b/rust/qemu-api-macros/src/device.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3b965576a065601cd5c97d5ab6a2501f96d16a61
--- /dev/null
+++ b/rust/qemu-api-macros/src/device.rs
@@ -0,0 +1,433 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use proc_macro::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{
+ parse::{Parse, ParseStream},
+ Result,
+};
+use syn::{parse_macro_input, DeriveInput};
+
+use crate::{symbols::*, utilities::*};
+
+#[derive(Debug, Default)]
+struct DeriveContainer {
+ category: Option<syn::Path>,
+ class_name: Option<syn::Ident>,
+ class_name_override: Option<syn::Ident>,
+}
+
+impl Parse for DeriveContainer {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let _: syn::Token![#] = input.parse()?;
+ let bracketed;
+ _ = syn::bracketed!(bracketed in input);
+ assert_eq!(DEVICE, bracketed.parse::<syn::Ident>()?);
+ let mut retval = Self {
+ category: None,
+ class_name: None,
+ class_name_override: None,
+ };
+ let content;
+ _ = syn::parenthesized!(content in bracketed);
+ while !content.is_empty() {
+ let value: syn::Ident = content.parse()?;
+ if value == CLASS_NAME {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.class_name.is_some() {
+ panic!("{} can only be used at most once", CLASS_NAME);
+ }
+ retval.class_name = Some(content.parse()?);
+ } else if value == CLASS_NAME_OVERRIDE {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.class_name_override.is_some() {
+ panic!("{} can only be used at most once", CLASS_NAME_OVERRIDE);
+ }
+ retval.class_name_override = Some(content.parse()?);
+ } else if value == CATEGORY {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.category.is_some() {
+ panic!("{} can only be used at most once", CATEGORY);
+ }
+ let lit: syn::LitStr = content.parse()?;
+ let path: syn::Path = lit.parse()?;
+ retval.category = Some(path);
+ } else {
+ panic!("unrecognized token `{}`", value);
+ }
+
+ if !content.is_empty() {
+ let _: syn::Token![,] = content.parse()?;
+ }
+ }
+ if retval.class_name.is_some() && retval.class_name_override.is_some() {
+ panic!(
+ "Cannot define `{}` and `{}` at the same time",
+ CLASS_NAME, CLASS_NAME_OVERRIDE
+ );
+ }
+ Ok(retval)
+ }
+}
+
+#[derive(Debug)]
+struct QdevProperty {
+ name: Option<syn::LitCStr>,
+ qdev_prop: Option<syn::Path>,
+}
+
+impl Parse for QdevProperty {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let _: syn::Token![#] = input.parse()?;
+ let bracketed;
+ _ = syn::bracketed!(bracketed in input);
+ assert_eq!(PROPERTY, bracketed.parse::<syn::Ident>()?);
+ let mut retval = Self {
+ name: None,
+ qdev_prop: None,
+ };
+ let content;
+ _ = syn::parenthesized!(content in bracketed);
+ while !content.is_empty() {
+ let value: syn::Ident = content.parse()?;
+ if value == NAME {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.name.is_some() {
+ panic!("{} can only be used at most once", NAME);
+ }
+ retval.name = Some(content.parse()?);
+ } else if value == QDEV_PROP {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.qdev_prop.is_some() {
+ panic!("{} can only be used at most once", QDEV_PROP);
+ }
+ retval.qdev_prop = Some(content.parse()?);
+ } else {
+ panic!("unrecognized token `{}`", value);
+ }
+
+ if !content.is_empty() {
+ let _: syn::Token![,] = content.parse()?;
+ }
+ }
+ Ok(retval)
+ }
+}
+
+pub fn derive_device(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ assert_is_repr_c_struct(&input, "Device");
+
+ let derive_container: DeriveContainer = input
+ .attrs
+ .iter()
+ .find(|a| a.path() == DEVICE)
+ .map(|a| syn::parse(a.to_token_stream().into()).expect("could not parse device attr"))
+ .unwrap_or_default();
+ let (qdev_properties_static, qdev_properties_expanded) = make_qdev_properties(&input);
+ let class_expanded = gen_device_class(derive_container, qdev_properties_static, &input.ident);
+ let name = input.ident;
+
+ let realize_fn = format_ident!("__{}_realize_generated", name);
+ let reset_fn = format_ident!("__{}_reset_generated", name);
+
+ let expanded = quote! {
+ unsafe impl ::qemu_api::objects::DeviceImplUnsafe for #name {
+ const REALIZE: ::core::option::Option<
+ unsafe extern "C" fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ errp: *mut *mut ::qemu_api::bindings::Error,
+ ),
+ > = Some(#realize_fn);
+ const RESET: ::core::option::Option<
+ unsafe extern "C" fn(dev: *mut ::qemu_api::bindings::DeviceState),
+ > = Some(#reset_fn);
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #realize_fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ errp: *mut *mut ::qemu_api::bindings::Error,
+ ) {
+ let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #reset_fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ ) {
+ let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
+ }
+ }
+
+ #qdev_properties_expanded
+ #class_expanded
+ };
+
+ TokenStream::from(expanded)
+}
+
+fn make_qdev_properties(input: &DeriveInput) -> (syn::Ident, proc_macro2::TokenStream) {
+ let name = &input.ident;
+
+ let qdev_properties: Vec<(syn::Field, QdevProperty)> = match &input.data {
+ syn::Data::Struct(syn::DataStruct {
+ fields: syn::Fields::Named(fields),
+ ..
+ }) => fields
+ .named
+ .iter()
+ .map(|f| {
+ f.attrs
+ .iter()
+ .filter(|a| a.path() == PROPERTY)
+ .map(|a| (f.clone(), a.clone()))
+ })
+ .flatten()
+ .map(|(f, a)| {
+ (
+ f.clone(),
+ syn::parse(a.to_token_stream().into()).expect("could not parse property attr"),
+ )
+ })
+ .collect::<Vec<(syn::Field, QdevProperty)>>(),
+ _other => unreachable!(),
+ };
+
+ let mut properties_expanded = quote! {
+ unsafe { ::core::mem::MaybeUninit::<::qemu_api::bindings::Property>::zeroed().assume_init() }
+ };
+ let prop_len = qdev_properties.len() + 1;
+ for (field, prop) in qdev_properties {
+ let prop_name = prop.name.as_ref().unwrap();
+ let field_name = field.ident.as_ref().unwrap();
+ let qdev_prop = prop.qdev_prop.as_ref().unwrap();
+ let prop = quote! {
+ ::qemu_api::bindings::Property {
+ name: ::core::ffi::CStr::as_ptr(#prop_name),
+ info: unsafe { &#qdev_prop },
+ offset: ::core::mem::offset_of!(#name, #field_name) as _,
+ bitnr: 0,
+ bitmask: 0,
+ set_default: false,
+ defval: ::qemu_api::bindings::Property__bindgen_ty_1 { i: 0 },
+ arrayoffset: 0,
+ arrayinfo: ::core::ptr::null(),
+ arrayfieldsize: 0,
+ link_type: ::core::ptr::null(),
+ }
+ };
+ properties_expanded = quote! {
+ #prop,
+ #properties_expanded
+ };
+ }
+ let properties_ident = format_ident!("__{}_QDEV_PROPERTIES", name);
+ let expanded = quote! {
+ #[no_mangle]
+ pub static mut #properties_ident: [::qemu_api::bindings::Property; #prop_len] = [#properties_expanded];
+ };
+ (properties_ident, expanded)
+}
+
+fn gen_device_class(
+ derive_container: DeriveContainer,
+ qdev_properties_static: syn::Ident,
+ name: &syn::Ident,
+) -> proc_macro2::TokenStream {
+ let (class_name, class_def) = match (
+ derive_container.class_name_override,
+ derive_container.class_name,
+ ) {
+ (Some(class_name), _) => {
+ let class_expanded = quote! {
+ #[repr(C)]
+ pub struct #class_name {
+ _inner: [u8; 0],
+ }
+ };
+ (class_name, class_expanded)
+ }
+ (None, Some(class_name)) => (class_name, quote! {}),
+ (None, None) => {
+ let class_name = format_ident!("{}Class", name);
+ let class_expanded = quote! {
+ #[repr(C)]
+ pub struct #class_name {
+ _inner: [u8; 0],
+ }
+ };
+ (class_name, class_expanded)
+ }
+ };
+ let class_init_fn = format_ident!("__{}_class_init_generated", class_name);
+ let class_base_init_fn = format_ident!("__{}_class_base_init_generated", class_name);
+
+ let (vmsd, vmsd_impl) = {
+ let (i, vmsd) = make_vmstate(name);
+ (quote! { &#i }, vmsd)
+ };
+ let category = if let Some(category) = derive_container.category {
+ quote! {
+ const BITS_PER_LONG: u32 = ::core::ffi::c_ulong::BITS;
+ let _: ::qemu_api::bindings::DeviceCategory = #category;
+ let nr: ::core::ffi::c_ulong = #category as _;
+ let mask = 1 << (nr as u32 % BITS_PER_LONG);
+ let p = ::core::ptr::addr_of_mut!(dc.as_mut().categories).offset((nr as u32 / BITS_PER_LONG) as isize);
+ let p: *mut ::core::ffi::c_ulong = p.cast();
+ let categories = p.read_unaligned();
+ p.write_unaligned(categories | mask);
+ }
+ } else {
+ quote! {}
+ };
+ let props = quote! {
+ ::qemu_api::bindings::device_class_set_props(dc.as_mut(), #qdev_properties_static.as_mut_ptr());
+ };
+
+ quote! {
+ #class_def
+
+ impl ::qemu_api::objects::ClassImpl for #class_name {
+ type Object = #name;
+ }
+
+ unsafe impl ::qemu_api::objects::ClassImplUnsafe for #class_name {
+ const CLASS_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
+ > = Some(#class_init_fn);
+ const CLASS_BASE_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
+ > = Some(#class_base_init_fn);
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #class_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
+ {
+ {
+ let mut dc =
+ ::core::ptr::NonNull::new(klass.cast::<::qemu_api::bindings::DeviceClass>()).unwrap();
+ unsafe {
+ dc.as_mut().realize =
+ <#name as ::qemu_api::objects::DeviceImplUnsafe>::REALIZE;
+ ::qemu_api::bindings::device_class_set_legacy_reset(
+ dc.as_mut(),
+ <#name as ::qemu_api::objects::DeviceImplUnsafe>::RESET
+ );
+ dc.as_mut().vmsd = #vmsd;
+ #props
+ #category
+ }
+ }
+ let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
+ unsafe {
+ ::qemu_api::objects::ClassImpl::class_init(klass.as_mut(), data);
+ }
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #class_base_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
+ {
+ let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
+ unsafe {
+ ::qemu_api::objects::ClassImpl::class_base_init(klass.as_mut(), data);
+ }
+ }
+ }
+
+ #vmsd_impl
+ }
+}
+
+fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStream) {
+ let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
+
+ let pre_load = format_ident!("__{}_pre_load_generated", name);
+ let post_load = format_ident!("__{}_post_load_generated", name);
+ let pre_save = format_ident!("__{}_pre_save_generated", name);
+ let post_save = format_ident!("__{}_post_save_generated", name);
+ let needed = format_ident!("__{}_needed_generated", name);
+ let dev_unplug_pending = format_ident!("__{}_dev_unplug_pending_generated", name);
+
+ let migrateable_fish = quote! {<#name as ::qemu_api::objects::Migrateable>};
+ let vmstate_description = quote! {
+ #[used]
+ #[allow(non_upper_case_globals)]
+ pub static #vmstate_description_ident: ::qemu_api::bindings::VMStateDescription = ::qemu_api::bindings::VMStateDescription {
+ name: if let Some(name) = #migrateable_fish::NAME {
+ name.as_ptr()
+ } else {
+ <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
+ },
+ unmigratable: #migrateable_fish::UNMIGRATABLE,
+ early_setup: #migrateable_fish::EARLY_SETUP,
+ version_id: #migrateable_fish::VERSION_ID,
+ minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
+ priority: #migrateable_fish::PRIORITY,
+ pre_load: Some(#pre_load),
+ post_load: Some(#post_load),
+ pre_save: Some(#pre_save),
+ post_save: Some(#post_save),
+ needed: Some(#needed),
+ dev_unplug_pending: Some(#dev_unplug_pending),
+ fields: ::core::ptr::null(),
+ subsections: ::core::ptr::null(),
+ };
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void, version_id: core::ffi::c_int) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_load(instance.as_mut(), version_id)
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) -> bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::needed(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::ffi::c_void) -> bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
+ }
+ }
+ };
+
+ let expanded = quote! {
+ #vmstate_description
+ };
+ (vmstate_description_ident, expanded)
+}
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index 59aba592d9ae4c5a4cdfdc6f9b9b08363b8a57e5..7753a853fae72fc87e6dc642cf076c6d0c736345 100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -2,42 +2,21 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
+#![allow(dead_code)]
+
use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
+
+mod device;
+mod object;
+mod symbols;
+mod utilities;
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as DeriveInput);
-
- let name = input.ident;
- let module_static = format_ident!("__{}_LOAD_MODULE", name);
-
- let expanded = quote! {
- #[allow(non_upper_case_globals)]
- #[used]
- #[cfg_attr(target_os = "linux", link_section = ".ctors")]
- #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
- #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
- pub static #module_static: extern "C" fn() = {
- extern "C" fn __register() {
- unsafe {
- ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
- }
- }
-
- extern "C" fn __load() {
- unsafe {
- ::qemu_api::bindings::register_module_init(
- Some(__register),
- ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
- );
- }
- }
-
- __load
- };
- };
+ object::derive_object(input)
+}
- TokenStream::from(expanded)
+#[proc_macro_derive(Device, attributes(device, property))]
+pub fn derive_device(input: TokenStream) -> TokenStream {
+ device::derive_device(input)
}
diff --git a/rust/qemu-api-macros/src/object.rs b/rust/qemu-api-macros/src/object.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f808069aea42de752dea7524fef64467427f105c
--- /dev/null
+++ b/rust/qemu-api-macros/src/object.rs
@@ -0,0 +1,107 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, DeriveInput};
+
+use crate::utilities::*;
+
+pub fn derive_object(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ assert_is_repr_c_struct(&input, "Object");
+
+ let name = input.ident;
+ let module_static = format_ident!("__{}_LOAD_MODULE", name);
+
+ let ctors = quote! {
+ #[allow(non_upper_case_globals)]
+ #[used]
+ #[cfg_attr(target_os = "linux", link_section = ".ctors")]
+ #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
+ #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
+ pub static #module_static: extern "C" fn() = {
+ extern "C" fn __register() {
+ unsafe {
+ ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO);
+ }
+ }
+
+ extern "C" fn __load() {
+ unsafe {
+ ::qemu_api::bindings::register_module_init(
+ Some(__register),
+ ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
+ );
+ }
+ }
+
+ __load
+ };
+ };
+
+ let instance_init = format_ident!("__{}_instance_init_generated", name);
+ let instance_post_init = format_ident!("__{}_instance_post_init_generated", name);
+ let instance_finalize = format_ident!("__{}_instance_finalize_generated", name);
+
+ let obj_impl_unsafe = quote! {
+ unsafe impl ::qemu_api::objects::ObjectImplUnsafe for #name {
+ const TYPE_INFO: ::qemu_api::bindings::TypeInfo =
+ ::qemu_api::bindings::TypeInfo {
+ name: <Self as ::qemu_api::objects::ObjectImpl>::TYPE_NAME.as_ptr(),
+ parent: if let Some(pname) = <Self as ::qemu_api::objects::ObjectImpl>::PARENT_TYPE_NAME {
+ pname.as_ptr()
+ } else {
+ ::core::ptr::null()
+ },
+ instance_size: ::core::mem::size_of::<Self>() as ::qemu_api::bindings::size_t,
+ instance_align: ::core::mem::align_of::<Self>() as ::qemu_api::bindings::size_t,
+ instance_init: <Self as ::qemu_api::objects::ObjectImplUnsafe>::INSTANCE_INIT,
+ instance_post_init: <Self as ::qemu_api::objects::ObjectImplUnsafe>::INSTANCE_POST_INIT,
+ instance_finalize: <Self as ::qemu_api::objects::ObjectImplUnsafe>::INSTANCE_FINALIZE,
+ abstract_: <Self as ::qemu_api::objects::ObjectImpl>::ABSTRACT,
+ class_size: ::core::mem::size_of::<<Self as ::qemu_api::objects::ObjectImpl>::Class>() as ::qemu_api::bindings::size_t,
+ class_init: <<Self as ::qemu_api::objects::ObjectImpl>::Class as ::qemu_api::objects::ClassImplUnsafe>::CLASS_INIT,
+ class_base_init: <<Self as ::qemu_api::objects::ObjectImpl>::Class as ::qemu_api::objects::ClassImplUnsafe>::CLASS_BASE_INIT,
+ class_data: ::core::ptr::null_mut(),
+ interfaces: ::core::ptr::null_mut(),
+ };
+ const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut ::qemu_api::bindings::Object)> = Some(#instance_init);
+ const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut ::qemu_api::bindings::Object)> = Some(#instance_post_init);
+ const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut ::qemu_api::bindings::Object)> = Some(#instance_finalize);
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #instance_init(obj: *mut ::qemu_api::bindings::Object) {
+ let mut instance = NonNull::new(obj.cast::<#name>()).expect(concat!("Expected obj to be a non-null pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::ObjectImpl::instance_init(instance.as_mut());
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #instance_post_init(obj: *mut ::qemu_api::bindings::Object) {
+ let mut instance = NonNull::new(obj.cast::<#name>()).expect(concat!("Expected obj to be a non-null pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::ObjectImpl::instance_post_init(instance.as_mut());
+ }
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #instance_finalize(obj: *mut ::qemu_api::bindings::Object) {
+ let mut instance = NonNull::new(obj.cast::<#name>()).expect(concat!("Expected obj to be a non-null pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::ObjectImpl::instance_finalize(instance.as_mut());
+ }
+ }
+ };
+
+ let expanded = quote! {
+ #obj_impl_unsafe
+
+ #ctors
+ };
+ TokenStream::from(expanded)
+}
diff --git a/rust/qemu-api-macros/src/symbols.rs b/rust/qemu-api-macros/src/symbols.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f73768d228ed2b4d478c18336db56cb11e70f012
--- /dev/null
+++ b/rust/qemu-api-macros/src/symbols.rs
@@ -0,0 +1,55 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use core::fmt;
+use syn::{Ident, Path};
+
+#[derive(Copy, Clone, Debug)]
+pub struct Symbol(&'static str);
+
+pub const DEVICE: Symbol = Symbol("device");
+pub const NAME: Symbol = Symbol("name");
+pub const CATEGORY: Symbol = Symbol("category");
+pub const CLASS_NAME: Symbol = Symbol("class_name");
+pub const CLASS_NAME_OVERRIDE: Symbol = Symbol("class_name_override");
+pub const QDEV_PROP: Symbol = Symbol("qdev_prop");
+pub const MIGRATEABLE: Symbol = Symbol("migrateable");
+pub const PROPERTIES: Symbol = Symbol("properties");
+pub const PROPERTY: Symbol = Symbol("property");
+
+impl PartialEq<Symbol> for Ident {
+ fn eq(&self, word: &Symbol) -> bool {
+ self == word.0
+ }
+}
+
+impl<'a> PartialEq<Symbol> for &'a Ident {
+ fn eq(&self, word: &Symbol) -> bool {
+ *self == word.0
+ }
+}
+
+impl PartialEq<Symbol> for Path {
+ fn eq(&self, word: &Symbol) -> bool {
+ self.is_ident(word.0)
+ }
+}
+
+impl<'a> PartialEq<Symbol> for &'a Path {
+ fn eq(&self, word: &Symbol) -> bool {
+ self.is_ident(word.0)
+ }
+}
+
+impl PartialEq<Ident> for Symbol {
+ fn eq(&self, ident: &Ident) -> bool {
+ ident == self.0
+ }
+}
+
+impl fmt::Display for Symbol {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(fmt)
+ }
+}
diff --git a/rust/qemu-api-macros/src/utilities.rs b/rust/qemu-api-macros/src/utilities.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bd8776539aa0bb3bcaa023bd88d962efe1431746
--- /dev/null
+++ b/rust/qemu-api-macros/src/utilities.rs
@@ -0,0 +1,152 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use syn::{parenthesized, token, Data, DeriveInput, LitInt};
+
+#[derive(Default)]
+pub enum Abi {
+ #[default]
+ Rust,
+ C,
+ Transparent,
+ Other(String),
+}
+
+#[derive(Default)]
+pub struct Repr {
+ pub abi: Abi,
+ /// whether the attribute was declared in the definition.
+ pub present: bool,
+ pub align: Option<usize>,
+ pub packed: Option<usize>,
+}
+
+impl core::fmt::Display for Repr {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
+ write!(fmt, "repr(")?;
+ match &self.abi {
+ Abi::C => write!(fmt, "C")?,
+ Abi::Rust => write!(fmt, "Rust")?,
+ Abi::Transparent => write!(fmt, "transparent")?,
+ Abi::Other(s) => write!(fmt, "{}", s)?,
+ }
+ if self.align.is_some() || self.packed.is_some() {
+ write!(fmt, ", ")?;
+ if let Some(v) = self.align {
+ write!(fmt, "align({})", v)?;
+ if self.packed.is_some() {
+ write!(fmt, ", ")?;
+ }
+ }
+ match self.packed {
+ Some(1) => write!(fmt, "packed")?,
+ Some(n) => write!(fmt, "packed({})", n)?,
+ None => {}
+ }
+ }
+ write!(fmt, ")")
+ }
+}
+
+impl Repr {
+ pub fn detect_repr(attrs: &[syn::Attribute]) -> Self {
+ let mut repr = Self::default();
+
+ // We don't validate the repr attribute; if it's invalid rustc will complain
+ // anyway.
+ for attr in attrs {
+ if attr.path().is_ident("repr") {
+ repr.present = true;
+ if let Err(err) = attr.parse_nested_meta(|meta| {
+ // #[repr(C)]
+ if meta.path.is_ident("C") {
+ repr.abi = Abi::C;
+ return Ok(());
+ }
+
+ // #[repr(Rust)]
+ if meta.path.is_ident("Rust") {
+ repr.abi = Abi::Rust;
+ return Ok(());
+ }
+
+ // #[repr(transparent)]
+ if meta.path.is_ident("transparent") {
+ repr.abi = Abi::Transparent;
+ return Ok(());
+ }
+
+ // #[repr(align(N))]
+ if meta.path.is_ident("align") {
+ let content;
+ parenthesized!(content in meta.input);
+ let lit: LitInt = content.parse()?;
+ let n: usize = lit.base10_parse()?;
+ repr.align = Some(n);
+ return Ok(());
+ }
+
+ // #[repr(packed)] or #[repr(packed(N))], omitted N means 1
+ if meta.path.is_ident("packed") {
+ repr.packed = if meta.input.peek(token::Paren) {
+ let content;
+ parenthesized!(content in meta.input);
+ let lit: LitInt = content.parse()?;
+ let n: usize = lit.base10_parse()?;
+ Some(n)
+ } else {
+ Some(1)
+ };
+ return Ok(());
+ }
+
+ if let Some(i) = meta.path.get_ident() {
+ repr.abi = Abi::Other(i.to_string());
+ }
+
+ Err(meta.error("unrecognized repr"))
+ }) {
+ println!("Error while processing Object Derive macro: {}", err);
+ }
+ }
+ }
+ repr
+ }
+}
+
+pub fn assert_is_repr_c_struct(input: &DeriveInput, derive_macro: &'static str) {
+ if !matches!(input.data, Data::Struct(_)) {
+ panic!(
+ "`{}` derive macro can only be used with structs, and `{}` is {}",
+ derive_macro,
+ input.ident,
+ match input.data {
+ Data::Struct(_) => unreachable!(),
+ Data::Enum(_) => "enum",
+ Data::Union(_) => "union",
+ }
+ );
+ }
+ match Repr::detect_repr(&input.attrs) {
+ Repr { abi: Abi::C, .. } => { /* all good */ }
+ Repr {
+ abi: Abi::Transparent,
+ ..
+ } => {
+ // If the data layout is `transparent`, then its representation
+ // depends on the ABI of the wrapped type. We cannot
+ // detect it here.
+ }
+ other => {
+ panic!(
+ "`{}` derive macro can only be used with repr(C) structs, and `{}` {} \
+ {}\nHint: Annotate the struct with `#[repr(C)]`.",
+ derive_macro,
+ input.ident,
+ if other.present { "is" } else { "defaults to" },
+ other,
+ );
+ }
+ }
+}
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index c72d34b607df1da90185046f4d9c26b3cb6c6523..0bd70b59afcc005251135802897954789b068e6e 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -3,8 +3,7 @@ _qemu_api_rs = static_library(
structured_sources(
[
'src/lib.rs',
- 'src/definitions.rs',
- 'src/device_class.rs',
+ 'src/objects.rs',
],
{'.' : bindings_rs},
),
diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs
deleted file mode 100644
index 60bd3f8aaa65ae131a9c4628a96ac52f590d7324..0000000000000000000000000000000000000000
--- a/rust/qemu-api/src/definitions.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-//! Definitions required by QEMU when registering a device.
-
-use ::core::ffi::{c_void, CStr};
-
-use crate::bindings::{Object, ObjectClass, TypeInfo};
-
-/// Trait a type must implement to be registered with QEMU.
-pub trait ObjectImpl {
- type Class;
- const TYPE_INFO: TypeInfo;
- const TYPE_NAME: &'static CStr;
- const PARENT_TYPE_NAME: Option<&'static CStr>;
- const ABSTRACT: bool;
- const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
- const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
- const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
-}
-
-pub trait Class {
- const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
- const CLASS_BASE_INIT: Option<
- unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
- >;
-}
-
-#[macro_export]
-macro_rules! module_init {
- ($func:expr, $type:expr) => {
- #[used]
- #[cfg_attr(target_os = "linux", link_section = ".ctors")]
- #[cfg_attr(target_os = "macos", 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 __load() {
- unsafe {
- $crate::bindings::register_module_init(Some($func), $type);
- }
- }
-
- __load
- };
- };
- (qom: $func:ident => $body:block) => {
- // NOTE: To have custom identifiers for the ctor func we need to either supply
- // them directly as a macro argument or create them with a proc macro.
- #[used]
- #[cfg_attr(target_os = "linux", link_section = ".ctors")]
- #[cfg_attr(target_os = "macos", 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 __load() {
- #[no_mangle]
- unsafe extern "C" fn $func() {
- $body
- }
-
- unsafe {
- $crate::bindings::register_module_init(
- Some($func),
- $crate::bindings::module_init_type::MODULE_INIT_QOM,
- );
- }
- }
-
- __load
- };
- };
-}
-
-#[macro_export]
-macro_rules! type_info {
- ($t:ty) => {
- $crate::bindings::TypeInfo {
- name: <$t as $crate::definitions::ObjectImpl>::TYPE_NAME.as_ptr(),
- parent: if let Some(pname) = <$t as $crate::definitions::ObjectImpl>::PARENT_TYPE_NAME {
- pname.as_ptr()
- } else {
- ::core::ptr::null_mut()
- },
- instance_size: ::core::mem::size_of::<$t>() as $crate::bindings::size_t,
- instance_align: ::core::mem::align_of::<$t>() as $crate::bindings::size_t,
- instance_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_INIT,
- instance_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
- instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
- abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
- class_size: ::core::mem::size_of::<<$t as $crate::definitions::ObjectImpl>::Class>() as $crate::bindings::size_t,
- class_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_INIT,
- class_base_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_BASE_INIT,
- class_data: ::core::ptr::null_mut(),
- interfaces: ::core::ptr::null_mut(),
- };
- }
-}
diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs
deleted file mode 100644
index 1ea95beb78dbf8637d9af1edb668d51411a9ac33..0000000000000000000000000000000000000000
--- a/rust/qemu-api/src/device_class.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use std::sync::OnceLock;
-
-use crate::bindings::Property;
-
-#[macro_export]
-macro_rules! device_class_init {
- ($func:ident, props => $props:ident, realize_fn => $realize_fn:expr, legacy_reset_fn => $legacy_reset_fn:expr, vmsd => $vmsd:ident$(,)*) => {
- #[no_mangle]
- pub unsafe extern "C" fn $func(
- klass: *mut $crate::bindings::ObjectClass,
- _: *mut ::core::ffi::c_void,
- ) {
- let mut dc =
- ::core::ptr::NonNull::new(klass.cast::<$crate::bindings::DeviceClass>()).unwrap();
- dc.as_mut().realize = $realize_fn;
- dc.as_mut().vmsd = &$vmsd;
- $crate::bindings::device_class_set_legacy_reset(dc.as_mut(), $legacy_reset_fn);
- $crate::bindings::device_class_set_props(dc.as_mut(), $props.as_mut_ptr());
- }
- };
-}
-
-#[macro_export]
-macro_rules! define_property {
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
- $crate::bindings::Property {
- name: {
- #[used]
- static _TEMP: &::core::ffi::CStr = $name;
- _TEMP.as_ptr()
- },
- info: $prop,
- offset: ::core::mem::offset_of!($state, $field)
- .try_into()
- .expect("Could not fit offset value to type"),
- bitnr: 0,
- bitmask: 0,
- set_default: true,
- defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval.into() },
- arrayoffset: 0,
- arrayinfo: ::core::ptr::null(),
- arrayfieldsize: 0,
- link_type: ::core::ptr::null(),
- }
- };
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr$(,)*) => {
- $crate::bindings::Property {
- name: {
- #[used]
- static _TEMP: &::core::ffi::CStr = $name;
- _TEMP.as_ptr()
- },
- info: $prop,
- offset: ::core::mem::offset_of!($state, $field)
- .try_into()
- .expect("Could not fit offset value to type"),
- bitnr: 0,
- bitmask: 0,
- set_default: false,
- defval: $crate::bindings::Property__bindgen_ty_1 { i: 0 },
- arrayoffset: 0,
- arrayinfo: ::core::ptr::null(),
- arrayfieldsize: 0,
- link_type: ::core::ptr::null(),
- }
- };
-}
-
-#[repr(C)]
-pub struct Properties<const N: usize>(pub OnceLock<[Property; N]>, pub fn() -> [Property; N]);
-
-impl<const N: usize> Properties<N> {
- pub fn as_mut_ptr(&mut self) -> *mut Property {
- _ = self.0.get_or_init(self.1);
- self.0.get_mut().unwrap().as_mut_ptr()
- }
-}
-
-#[macro_export]
-macro_rules! declare_properties {
- ($ident:ident, $($prop:expr),*$(,)*) => {
-
- const fn _calc_prop_len() -> usize {
- let mut len = 1;
- $({
- _ = stringify!($prop);
- len += 1;
- })*
- len
- }
- const PROP_LEN: usize = _calc_prop_len();
-
- fn _make_properties() -> [$crate::bindings::Property; PROP_LEN] {
- [
- $($prop),*,
- unsafe { ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init() },
- ]
- }
-
- #[no_mangle]
- pub static mut $ident: $crate::device_class::Properties<PROP_LEN> = $crate::device_class::Properties(::std::sync::OnceLock::new(), _make_properties);
- };
-}
-
-#[macro_export]
-macro_rules! vm_state_description {
- ($(#[$outer:meta])*
- $name:ident,
- $(name: $vname:expr,)*
- $(unmigratable: $um_val:expr,)*
- ) => {
- #[used]
- $(#[$outer])*
- pub static $name: $crate::bindings::VMStateDescription = $crate::bindings::VMStateDescription {
- $(name: {
- #[used]
- static VMSTATE_NAME: &::core::ffi::CStr = $vname;
- $vname.as_ptr()
- },)*
- unmigratable: true,
- ..unsafe { ::core::mem::MaybeUninit::<$crate::bindings::VMStateDescription>::zeroed().assume_init() }
- };
- }
-}
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index e72fb4b4bb13b0982f828b6ec1cfe848c3e6bdf0..b94adc15288cdc62de7679988f549ebd80f895d7 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -27,11 +27,7 @@ unsafe impl Sync for bindings::Property {}
unsafe impl Sync for bindings::TypeInfo {}
unsafe impl Sync for bindings::VMStateDescription {}
-pub mod definitions;
-pub mod device_class;
-
-#[cfg(test)]
-mod tests;
+pub mod objects;
use std::alloc::{GlobalAlloc, Layout};
diff --git a/rust/qemu-api/src/objects.rs b/rust/qemu-api/src/objects.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5c6762023ed6914f9c6b7dd16a5e07f778c2d4fa
--- /dev/null
+++ b/rust/qemu-api/src/objects.rs
@@ -0,0 +1,90 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Implementation traits for QEMU objects, devices.
+
+use ::core::ffi::{c_int, c_void, CStr};
+
+use crate::bindings::{DeviceState, Error, MigrationPriority, Object, ObjectClass, TypeInfo};
+
+/// Trait a type must implement to be registered with QEMU.
+pub trait ObjectImpl {
+ type Class: ClassImpl;
+ const TYPE_NAME: &'static CStr;
+ const PARENT_TYPE_NAME: Option<&'static CStr>;
+ const ABSTRACT: bool;
+
+ unsafe fn instance_init(&mut self) {}
+ fn instance_post_init(&mut self) {}
+ fn instance_finalize(&mut self) {}
+}
+
+/// The `extern`/`unsafe` analogue of [`ObjectImpl`]; it is used internally by `#[derive(Object)]`
+/// and should not be implemented manually.
+pub unsafe trait ObjectImplUnsafe {
+ const TYPE_INFO: TypeInfo;
+
+ const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
+ const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
+ const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
+}
+
+/// Methods for QOM class types.
+pub trait ClassImpl {
+ type Object: ObjectImpl;
+
+ unsafe fn class_init(&mut self, _data: *mut core::ffi::c_void) {}
+ unsafe fn class_base_init(&mut self, _data: *mut core::ffi::c_void) {}
+}
+
+/// The `extern`/`unsafe` analogue of [`ClassImpl`]; it is used internally by `#[derive(Object)]`
+/// and should not be implemented manually.
+pub unsafe trait ClassImplUnsafe {
+ const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
+ const CLASS_BASE_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
+ >;
+}
+
+/// Implementation methods for device types.
+pub trait DeviceImpl: ObjectImpl {
+ fn realize(&mut self) {}
+ fn reset(&mut self) {}
+}
+
+/// The `extern`/`unsafe` analogue of [`DeviceImpl`]; it is used internally by `#[derive(Device)]`
+/// and should not be implemented manually.
+pub unsafe trait DeviceImplUnsafe {
+ const REALIZE: Option<unsafe extern "C" fn(dev: *mut DeviceState, _errp: *mut *mut Error)>;
+ const RESET: Option<unsafe extern "C" fn(dev: *mut DeviceState)>;
+}
+
+/// Constant metadata and implementation methods for types with device migration state.
+pub trait Migrateable: DeviceImplUnsafe {
+ const NAME: Option<&'static CStr> = None;
+ const UNMIGRATABLE: bool = true;
+ const EARLY_SETUP: bool = false;
+ const VERSION_ID: c_int = 1;
+ const MINIMUM_VERSION_ID: c_int = 1;
+ const PRIORITY: MigrationPriority = MigrationPriority::MIG_PRI_DEFAULT;
+
+ unsafe fn pre_load(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
+ 0
+ }
+ unsafe fn pre_save(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn post_save(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn needed(&mut self) -> bool {
+ false
+ }
+ unsafe fn dev_unplug_pending(&mut self) -> bool {
+ false
+ }
+}
diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
deleted file mode 100644
index df54edbd4e27e7d2aafc243355d1826d52497c21..0000000000000000000000000000000000000000
--- a/rust/qemu-api/src/tests.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use crate::{
- bindings::*, declare_properties, define_property, device_class_init, vm_state_description,
-};
-
-#[test]
-fn test_device_decl_macros() {
- // Test that macros can compile.
- vm_state_description! {
- VMSTATE,
- name: c"name",
- unmigratable: true,
- }
-
- #[repr(C)]
- pub struct DummyState {
- pub char_backend: CharBackend,
- pub migrate_clock: bool,
- }
-
- declare_properties! {
- DUMMY_PROPERTIES,
- define_property!(
- c"chardev",
- DummyState,
- char_backend,
- unsafe { &qdev_prop_chr },
- CharBackend
- ),
- define_property!(
- c"migrate-clk",
- DummyState,
- migrate_clock,
- unsafe { &qdev_prop_bool },
- bool
- ),
- }
-
- device_class_init! {
- dummy_class_init,
- props => DUMMY_PROPERTIES,
- realize_fn => None,
- reset_fn => None,
- vmsd => VMSTATE,
- }
-}
diff --git a/subprojects/packagefiles/syn-2-rs/meson.build b/subprojects/packagefiles/syn-2-rs/meson.build
index a53335f3092e06723039513a1bf5a0d35b4afcd7..9f56ce1c24d0ff86e9b0146b0f82c37ac868fab7 100644
--- a/subprojects/packagefiles/syn-2-rs/meson.build
+++ b/subprojects/packagefiles/syn-2-rs/meson.build
@@ -24,6 +24,7 @@ _syn_rs = static_library(
'--cfg', 'feature="printing"',
'--cfg', 'feature="clone-impls"',
'--cfg', 'feature="proc-macro"',
+ '--cfg', 'feature="extra-traits"',
],
dependencies: [
quote_dep,
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
@ 2024-10-24 15:13 ` Alex Bennée
2024-10-24 17:06 ` Manos Pitsidianakis
2024-10-25 12:01 ` Paolo Bonzini
2024-10-27 20:58 ` Paolo Bonzini
2 siblings, 1 reply; 25+ messages in thread
From: Alex Bennée @ 2024-10-24 15:13 UTC (permalink / raw)
To: Manos Pitsidianakis
Cc: qemu-devel, Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé, Thomas Huth,
Junjie Mao, Junjie Mao, Zhao Liu, Kevin Wolf, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Manos Pitsidianakis <manos.pitsidianakis@linaro.org> writes:
> Add a new derive procedural macro to declare device models. Add
> corresponding DeviceImpl trait after already existing ObjectImpl trait.
> At the same time, add instance_init, instance_post_init,
> instance_finalize methods to the ObjectImpl trait and call them from the
> ObjectImplUnsafe trait, which is generated by the procedural macro.
>
> This allows all the boilerplate device model registration to be handled
> by macros, and all pertinent details to be declared through proc macro
> attributes or trait associated constants and methods.
>
> The device class can now be generated automatically and the name can be
> optionally overridden:
>
> ------------------------ >8 ------------------------
> #[repr(C)]
> #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> #[device(class_name_override = PL011Class)]
> /// PL011 Device Model in QEMU
> pub struct PL011State {
> ------------------------ >8 ------------------------
>
> Properties are now marked as field attributes:
>
> ------------------------ >8 ------------------------
> #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
> pub char_backend: CharBackend,
> ------------------------ >8 ------------------------
>
> Object methods (instance_init, etc) methods are now trait methods:
>
> ------------------------ >8 ------------------------
> /// Trait a type must implement to be registered with QEMU.
> pub trait ObjectImpl {
> type Class: ClassImpl;
> const TYPE_NAME: &'static CStr;
> const PARENT_TYPE_NAME: Option<&'static CStr>;
> const ABSTRACT: bool;
>
> unsafe fn instance_init(&mut self) {}
> fn instance_post_init(&mut self) {}
> fn instance_finalize(&mut self) {}
> }
> ------------------------ >8 ------------------------
>
> Device methods (realize/reset etc) are now safe idiomatic trait methods:
>
> ------------------------ >8 ------------------------
> /// Implementation methods for device types.
> pub trait DeviceImpl: ObjectImpl {
> fn realize(&mut self) {}
> fn reset(&mut self) {}
> }
> ------------------------ >8 ------------------------
>
> The derive Device macro is responsible for creating all the extern "C" FFI
> functions that QEMU needs to call these methods.
>
> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> ---
> rust/hw/char/pl011/src/device.rs | 124 +++-----
> rust/hw/char/pl011/src/device_class.rs | 70 -----
> rust/hw/char/pl011/src/lib.rs | 1 -
> rust/qemu-api-macros/src/device.rs | 433 ++++++++++++++++++++++++++
> rust/qemu-api-macros/src/lib.rs | 45 +--
> rust/qemu-api-macros/src/object.rs | 107 +++++++
> rust/qemu-api-macros/src/symbols.rs | 55 ++++
> rust/qemu-api-macros/src/utilities.rs | 152 +++++++++
> rust/qemu-api/meson.build | 3 +-
> rust/qemu-api/src/definitions.rs | 97 ------
> rust/qemu-api/src/device_class.rs | 128 --------
> rust/qemu-api/src/lib.rs | 6 +-
> rust/qemu-api/src/objects.rs | 90 ++++++
> rust/qemu-api/src/tests.rs | 49 ---
> subprojects/packagefiles/syn-2-rs/meson.build | 1 +
> 15 files changed, 902 insertions(+), 459 deletions(-)
My initial reaction is these patches are getting quite large because we
are doing several things at once. Is it possible to split them so:
- we bring in one macro at a time (with unit tests)
- we then convert pl011 to the new idiom
?
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-24 15:13 ` Alex Bennée
@ 2024-10-24 17:06 ` Manos Pitsidianakis
0 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 17:06 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé, Thomas Huth,
Junjie Mao, Junjie Mao, Zhao Liu, Kevin Wolf, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
On Thu, 24 Oct 2024 at 18:14, Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Manos Pitsidianakis <manos.pitsidianakis@linaro.org> writes:
>
> > Add a new derive procedural macro to declare device models. Add
> > corresponding DeviceImpl trait after already existing ObjectImpl trait.
> > At the same time, add instance_init, instance_post_init,
> > instance_finalize methods to the ObjectImpl trait and call them from the
> > ObjectImplUnsafe trait, which is generated by the procedural macro.
> >
> > This allows all the boilerplate device model registration to be handled
> > by macros, and all pertinent details to be declared through proc macro
> > attributes or trait associated constants and methods.
> >
> > The device class can now be generated automatically and the name can be
> > optionally overridden:
> >
> > ------------------------ >8 ------------------------
> > #[repr(C)]
> > #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> > #[device(class_name_override = PL011Class)]
> > /// PL011 Device Model in QEMU
> > pub struct PL011State {
> > ------------------------ >8 ------------------------
> >
> > Properties are now marked as field attributes:
> >
> > ------------------------ >8 ------------------------
> > #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
> > pub char_backend: CharBackend,
> > ------------------------ >8 ------------------------
> >
> > Object methods (instance_init, etc) methods are now trait methods:
> >
> > ------------------------ >8 ------------------------
> > /// Trait a type must implement to be registered with QEMU.
> > pub trait ObjectImpl {
> > type Class: ClassImpl;
> > const TYPE_NAME: &'static CStr;
> > const PARENT_TYPE_NAME: Option<&'static CStr>;
> > const ABSTRACT: bool;
> >
> > unsafe fn instance_init(&mut self) {}
> > fn instance_post_init(&mut self) {}
> > fn instance_finalize(&mut self) {}
> > }
> > ------------------------ >8 ------------------------
> >
> > Device methods (realize/reset etc) are now safe idiomatic trait methods:
> >
> > ------------------------ >8 ------------------------
> > /// Implementation methods for device types.
> > pub trait DeviceImpl: ObjectImpl {
> > fn realize(&mut self) {}
> > fn reset(&mut self) {}
> > }
> > ------------------------ >8 ------------------------
> >
> > The derive Device macro is responsible for creating all the extern "C" FFI
> > functions that QEMU needs to call these methods.
> >
> > Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> > ---
> > rust/hw/char/pl011/src/device.rs | 124 +++-----
> > rust/hw/char/pl011/src/device_class.rs | 70 -----
> > rust/hw/char/pl011/src/lib.rs | 1 -
> > rust/qemu-api-macros/src/device.rs | 433 ++++++++++++++++++++++++++
> > rust/qemu-api-macros/src/lib.rs | 45 +--
> > rust/qemu-api-macros/src/object.rs | 107 +++++++
> > rust/qemu-api-macros/src/symbols.rs | 55 ++++
> > rust/qemu-api-macros/src/utilities.rs | 152 +++++++++
> > rust/qemu-api/meson.build | 3 +-
> > rust/qemu-api/src/definitions.rs | 97 ------
> > rust/qemu-api/src/device_class.rs | 128 --------
> > rust/qemu-api/src/lib.rs | 6 +-
> > rust/qemu-api/src/objects.rs | 90 ++++++
> > rust/qemu-api/src/tests.rs | 49 ---
> > subprojects/packagefiles/syn-2-rs/meson.build | 1 +
> > 15 files changed, 902 insertions(+), 459 deletions(-)
>
> My initial reaction is these patches are getting quite large because we
> are doing several things at once. Is it possible to split them so:
>
> - we bring in one macro at a time (with unit tests)
> - we then convert pl011 to the new idiom
Sure, I think it's possible to split them on some level. (Make changes
on the existing Object derive macro and then introduce the Device
macro) We can't unfortunately do changes without pl011 not being
updated though, since it uses the code.
As for unit tests, the only way to test proc macros is to match
generated code output to expected output. Otherwise they are
indirectly tested by the device not breaking other qemu tests. What do
you think should be our unit testing approach for this instance?
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
2024-10-24 15:13 ` Alex Bennée
@ 2024-10-25 12:01 ` Paolo Bonzini
2024-10-25 14:04 ` Manos Pitsidianakis
2024-10-27 20:58 ` Paolo Bonzini
2 siblings, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-25 12:01 UTC (permalink / raw)
To: Manos Pitsidianakis, qemu-devel
Cc: Peter Maydell, Marc-André Lureau, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Alex Bennée, Thomas Huth,
Junjie Mao, Junjie Mao, Zhao Liu, Kevin Wolf, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
On 10/24/24 16:03, Manos Pitsidianakis wrote:
> Add a new derive procedural macro to declare device models. Add
> corresponding DeviceImpl trait after already existing ObjectImpl trait.
> At the same time, add instance_init, instance_post_init,
> instance_finalize methods to the ObjectImpl trait and call them from the
> ObjectImplUnsafe trait, which is generated by the procedural macro.
>
> This allows all the boilerplate device model registration to be handled
> by macros, and all pertinent details to be declared through proc macro
> attributes or trait associated constants and methods.
> The device class can now be generated automatically and the name can be
> optionally overridden:
>
> ------------------------ >8 ------------------------
> #[repr(C)]
> #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> #[device(class_name_override = PL011Class)]
> /// PL011 Device Model in QEMU
> pub struct PL011State {
> ------------------------ >8 ------------------------
>
> Properties are now marked as field attributes:
>
> ------------------------ >8 ------------------------
> #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
> pub char_backend: CharBackend,
> ------------------------ >8 ------------------------
Let's look at this from the point of view of separating the code into
multiple patches.
This part is probably the easiest to extract. We can start the device
impl trait with just the properties:
pub trait DeviceImpl {
const PROPERTIES: &[Property] = &[];
}
and then the device_class_init! macro can use <$type as
DeviceImpl>::PROPERTIES instead of $props.
qemu_api::device_class_init! {
- pl011_class_init,
- props => PL011_PROPERTIES,
+ PL011State, pl011_class_init,
realize_fn => Some(pl011_realize),
legacy_reset_fn => Some(pl011_reset),
vmsd => VMSTATE_PL011,
}
> Object methods (instance_init, etc) methods are now trait methods:
>
> ------------------------ >8 ------------------------
> /// Trait a type must implement to be registered with QEMU.
> pub trait ObjectImpl {
> type Class: ClassImpl;
> const TYPE_NAME: &'static CStr;
> const PARENT_TYPE_NAME: Option<&'static CStr>;
> const ABSTRACT: bool;
>
> unsafe fn instance_init(&mut self) {}
> fn instance_post_init(&mut self) {}
> fn instance_finalize(&mut self) {}
> }
> ------------------------ >8 ------------------------
Some comments here:
- instance_init should take either "instance: *mut self" or even better
"instance: &mut MaybeUninit<self>".
- for all Rust objects the implementation of instance_finalize() should
just be ptr::drop_in_place(obj), so this need not be included in the trait.
TYPE_NAME/PARENT_TYPE_NAME/ABSTRACT could be passed to the
#[derive(Object)] macro, for example:
#[derive(Object)]
// abstract can be optional
#[object(type_name="pl011", parent_type=DeviceState, abstract=false,
class_type=PL011Class)]
while the "fn instance_init()" remains as a trait implementation in
pl011/src/device.rs.
Because a trait can be implemented only once, this suggests separating
the trait(s) in two: one for the constants that can be generated from
the macro, one for the functions that form the vtable. This is true for
both objects and devices:
pub trait ObjectInfo {
type Class: ClassImpl;
const TYPE_NAME: &'static CStr;
const PARENT_TYPE_NAME: Option<&'static CStr>;
const ABSTRACT: bool;
}
pub trait ObjectImpl: ObjectInfo {
unsafe fn instance_init(instance: &mut MaybeUninit<self>) {}
}
and likewise:
> Device methods (realize/reset etc) are now safe idiomatic trait methods:
>
> ------------------------ >8 ------------------------
> /// Implementation methods for device types.
> pub trait DeviceImpl: ObjectImpl {
> fn realize(&mut self) {}
> fn reset(&mut self) {}
> }
> ------------------------ >8 ------------------------
pub trait DeviceInfo {
const PROPERTIES: &[Property] = &[];
}
pub trait DeviceImpl: ObjectImpl + DeviceInfo {
fn realize(&mut self) {}
fn reset(&mut self) {}
const VMSTATE: Option<VMStateDescription> = None;
}
(Generally, don't read too much in the code - the syntax might have
issues but you get the idea).
Anyhow, going forward to the property attribute:
> + #[property(name = c"migrate-clk", qdev_prop = qdev_prop_bool)]
There are two issues here:
First, the default is true, so 1) this has to be fixed in QEMU (will
do), 2) it is important to support it in #[property()].
Second, this provides a false sense of safety, because I could specify
qdev_prop_chr here. Instead, the qdev_prop type should be derived by
the field type.
Third, since we are at it there's no need to use c"" in the attribute.
The c_str!() macro that I am adding for backwards compatibility to old
versions of Rust might actually come in handy here.
The part where I have most comments, and some ideas of how to make your
work a little more maintainable, is the implementation of class_init
(and all that depends on it).
Let's start with these generated functions:
> + pub unsafe extern "C" fn #realize_fn(
> + dev: *mut ::qemu_api::bindings::DeviceState,
> + errp: *mut *mut ::qemu_api::bindings::Error,
> + ) {
> + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> + unsafe {
> + ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
> + }
> + }
> +
> + #[no_mangle]
> + pub unsafe extern "C" fn #reset_fn(
> + dev: *mut ::qemu_api::bindings::DeviceState,
> + ) {
> + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> + unsafe {
> + ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
> + }
> + }
This can be handled entirely in Rust code, outside the macro. If you add:
unsafe extern "C" fn realize_fn_unsafe<T: DeviceImpl>(
dev: *mut DeviceState,
errp: *mut *mut Error,
) {
let mut instance = NonNull::new(dev.cast::<T>()).
expect("Expected dev to be a non-null pointer");
unsafe {
::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
}
}
unsafe extern "C" fn reset_fn_unsafe<T: DeviceImpl>(
dev: *mut ::qemu_api::bindings::DeviceState,
) {
let mut instance = NonNull::new(dev.cast::<T>()).
expect("Expected dev to be a non-null pointer");
unsafe {
::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
}
}
then the functions can be used directly instead of #realize_fn and
#reset_fn with a blanket implementation of DeviceImplUnsafe:
unsafe impl DeviceImplUnsafe for T: DeviceImpl {
const REALIZE: ... = Some(realize_fn_unsafe::<T>);
const RESET: ... = Some(realize_fn_unsafe::<T>);
}
Going on to the implementation of the safe functions:
> +impl DeviceImpl for PL011State {
> + fn realize(&mut self) {
These are not quite traits. First, you can implement only some of the
functions. Second, if you don't implement them they are not overwritten
by the class_init method.
So this points to a different implementation as an attribute macro,
which is able to rewrite everything in the body of the impl. For example:
#[qom_class_init]
impl DeviceImpl for PL011State {
fn realize(&mut self) { ... }
fn reset(&mut self) { ... }
const VMSTATE: ... = {}
const CATEGORY: ... = {}
}
can be transformed into:
#[qom_class_init]
impl DeviceImpl for PL011State {
// fns are wrapped and transformed into consts
const REALIZE: Option<fn(&mut self)> = {
fn realize(&mut self) { ... }
Some(realize)
};
const RESET: Option<fn(&mut self)> = {
fn reset(&mut self) { ... }
Some(reset)
};
// while associated consts (and perhaps types?) remain as is
const VMSTATE: ... = {}
const CATEGORY: ... = {}
}
The above blanket implementation of DeviceImplUnsafe is easily adjusted
to support non-overridden methods:
unsafe impl DeviceImplUnsafe for T: DeviceImpl {
const REALIZE: ... = <T as DeviceImpl>::REALIZE::map(
|| realize_fn_unsafe::<T>);
const RESET: ... = <T as DeviceImpl>::RESET::map(
|| realize_fn_unsafe::<T>);
}
You can also keep out of the macro the class_init method itself. Here
I'm adding it to DeviceImplUnsafe:
pub trait DeviceImplUnsafe {
unsafe fn class_init(klass: *mut ObjectClass, data: *mut c_void) {
let mut dc = NonNull::new(klass.cast::<DeviceClass>()).unwrap();
unsafe {
dc.as_mut().realize = Self::REALIZE;
bindings::device_class_set_legacy_reset(
dc.as_mut(), Self::RESET);
device_class_set_props(dc.as_mut(),
<Self as DeviceInfo>::PROPS);
if let Some(vmsd) = <Self as DeviceInfo>::VMSTATE {
dc.as_mut().vmsd = vmsd;
}
if let Some(cat) = <Self as DeviceInfo>::CATEGORY {
dc.as_mut().category = cat;
}
// maybe in the future for unparent
// <Self as ObjectImplUnsafe>::class_init(klass, data)
}
}
}
And with all this machinery in place the device_class_init! macro
becomes simply
device_class_init!(PL011State);
(because the arguments have moved to DeviceInfo or DeviceImpl).
Why is this important? Because the only code transformation is the
generation of properties and vtables, and the bindings can be developed
almost entirely in qemu_api instead of qemu_api_macros. This has
several advantages:
1) it's much easier: simpler error messages, no macro indirection, no
super long global crate paths
2) it allows simplifying the pl011 code piecewise, even before
introducing procedural macro code
3) it's easier to add comments
It also becomes much easier to separate the work in separate patches, or
even separate series. Replacing the arguments to device_class_init!()
with DeviceImpl + DeviceImplUnsafe can be introduced first: posted,
reviewed, merged. All the remaining tasks are pretty much independent:
1) splitting out ObjectInfo and introducing #[object] to automate it
(i.e. extending derive(Object))
2) splitting out DeviceInfo and introducing #[property] to automate it
(i.e. derive(Device))
3) the #[qom_class_init] macro
A final word: I absolutely don't want you to think that your work is of
no value. It's totally okay to throw away the first version of
something. You showed that it _is_ possible to have idiomatic code with
the help of procedural macros. Even if the implementation can be
improved, the _idea_ remains yours.
Paolo
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-25 12:01 ` Paolo Bonzini
@ 2024-10-25 14:04 ` Manos Pitsidianakis
2024-10-25 15:22 ` Paolo Bonzini
0 siblings, 1 reply; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-25 14:04 UTC (permalink / raw)
To: Paolo Bonzini
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Richard Henderson, Gustavo Romero, Pierrick Bouvier
On Fri, 25 Oct 2024 at 15:01, Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> (Generally, don't read too much in the code - the syntax might have
> issues but you get the idea).
>
>
> Anyhow, going forward to the property attribute:
>
> > + #[property(name = c"migrate-clk", qdev_prop = qdev_prop_bool)]
>
> There are two issues here:
>
> First, the default is true, so 1) this has to be fixed in QEMU (will
> do), 2) it is important to support it in #[property()].
TODO, it was not ignored just planned as next
>
> Second, this provides a false sense of safety, because I could specify
> qdev_prop_chr here. Instead, the qdev_prop type should be derived by
> the field type.
TODO, if you recall we had that discussion about external statics,
that was what I was looking into back then.
> Third, since we are at it there's no need to use c"" in the attribute.
> The c_str!() macro that I am adding for backwards compatibility to old
> versions of Rust might actually come in handy here.
TODO, you can you use both LitStr and LitCStr in the macro
>
> The part where I have most comments, and some ideas of how to make your
> work a little more maintainable, is the implementation of class_init
> (and all that depends on it).
>
> Let's start with these generated functions:
>
> > + pub unsafe extern "C" fn #realize_fn(
> > + dev: *mut ::qemu_api::bindings::DeviceState,
> > + errp: *mut *mut ::qemu_api::bindings::Error,
> > + ) {
> > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > + unsafe {
> > + ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
> > + }
> > + }
> > +
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #reset_fn(
> > + dev: *mut ::qemu_api::bindings::DeviceState,
> > + ) {
> > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > + unsafe {
> > + ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
> > + }
> > + }
>
> This can be handled entirely in Rust code, outside the macro. If you add:
Why? I don't understand what this solves. These are *just* trampoline
functions to call the Rust-abi code.
>
> unsafe extern "C" fn realize_fn_unsafe<T: DeviceImpl>(
> dev: *mut DeviceState,
> errp: *mut *mut Error,
> ) {
> let mut instance = NonNull::new(dev.cast::<T>()).
> expect("Expected dev to be a non-null pointer");
> unsafe {
> ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
> }
> }
>
> unsafe extern "C" fn reset_fn_unsafe<T: DeviceImpl>(
> dev: *mut ::qemu_api::bindings::DeviceState,
> ) {
> let mut instance = NonNull::new(dev.cast::<T>()).
> expect("Expected dev to be a non-null pointer");
> unsafe {
> ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
> }
> }
>
> then the functions can be used directly instead of #realize_fn and
> #reset_fn with a blanket implementation of DeviceImplUnsafe:
>
So just rename them and put a generic argument. Still not seeing any gain here.
>
> unsafe impl DeviceImplUnsafe for T: DeviceImpl {
> const REALIZE: ... = Some(realize_fn_unsafe::<T>);
> const RESET: ... = Some(realize_fn_unsafe::<T>);
> }
>
> Going on to the implementation of the safe functions:
>
> > +impl DeviceImpl for PL011State {
> > + fn realize(&mut self) {
>
> These are not quite traits. First, you can implement only some of the
> functions.
This is called "default implementations" in Rust
> Second, if you don't implement them they are not overwritten
> by the class_init method.
WYM overwritten? That we hook up the empty stub instead of a NULL
function pointer?
> So this points to a different implementation as an attribute macro,
> which is able to rewrite everything in the body of the impl. For example:
>
> #[qom_class_init]
> impl DeviceImpl for PL011State {
> fn realize(&mut self) { ... }
> fn reset(&mut self) { ... }
>
> const VMSTATE: ... = {}
> const CATEGORY: ... = {}
> }
>
> can be transformed into:
>
> #[qom_class_init]
> impl DeviceImpl for PL011State {
> // fns are wrapped and transformed into consts
> const REALIZE: Option<fn(&mut self)> = {
> fn realize(&mut self) { ... }
> Some(realize)
> };
> const RESET: Option<fn(&mut self)> = {
> fn reset(&mut self) { ... }
> Some(reset)
> };
>
> // while associated consts (and perhaps types?) remain as is
> const VMSTATE: ... = {}
> const CATEGORY: ... = {}
> }
>
> The above blanket implementation of DeviceImplUnsafe is easily adjusted
> to support non-overridden methods:
>
> unsafe impl DeviceImplUnsafe for T: DeviceImpl {
> const REALIZE: ... = <T as DeviceImpl>::REALIZE::map(
> || realize_fn_unsafe::<T>);
> const RESET: ... = <T as DeviceImpl>::RESET::map(
> || realize_fn_unsafe::<T>);
> }
>
>
> You can also keep out of the macro the class_init method itself. Here
> I'm adding it to DeviceImplUnsafe:
>
> pub trait DeviceImplUnsafe {
> unsafe fn class_init(klass: *mut ObjectClass, data: *mut c_void) {
> let mut dc = NonNull::new(klass.cast::<DeviceClass>()).unwrap();
> unsafe {
> dc.as_mut().realize = Self::REALIZE;
> bindings::device_class_set_legacy_reset(
> dc.as_mut(), Self::RESET);
> device_class_set_props(dc.as_mut(),
> <Self as DeviceInfo>::PROPS);
> if let Some(vmsd) = <Self as DeviceInfo>::VMSTATE {
> dc.as_mut().vmsd = vmsd;
> }
> if let Some(cat) = <Self as DeviceInfo>::CATEGORY {
> dc.as_mut().category = cat;
> }
>
> // maybe in the future for unparent
> // <Self as ObjectImplUnsafe>::class_init(klass, data)
> }
> }
> }
>
> And with all this machinery in place the device_class_init! macro
> becomes simply
>
> device_class_init!(PL011State);
>
> (because the arguments have moved to DeviceInfo or DeviceImpl).
>
>
> Why is this important? Because the only code transformation is the
> generation of properties and vtables, and the bindings can be developed
> almost entirely in qemu_api instead of qemu_api_macros. This has
> several advantages:
>
> 1) it's much easier: simpler error messages, no macro indirection, no
> super long global crate paths
Hard no, sorry. Error messages can definitely be generated from proc
macros. Long crate paths; that's just a matter of using imports or
changing names.
>
> 2) it allows simplifying the pl011 code piecewise, even before
> introducing procedural macro code
Not sure how that is relevant can you explain?
>
> 3) it's easier to add comments
>
>
> It also becomes much easier to separate the work in separate patches, or
> even separate series. Replacing the arguments to device_class_init!()
> with DeviceImpl + DeviceImplUnsafe can be introduced first: posted,
> reviewed, merged. All the remaining tasks are pretty much independent:
>
> 1) splitting out ObjectInfo and introducing #[object] to automate it
> (i.e. extending derive(Object))
>
> 2) splitting out DeviceInfo and introducing #[property] to automate it
> (i.e. derive(Device))
>
> 3) the #[qom_class_init] macro
I disagree with this approach at the moment. This patch is in an
acceptable state albeit a bit longish while all these suggestions
would merely cause more running around making changes for no real
gain while also delaying review and merging.
>
> A final word: I absolutely don't want you to think that your work is of
> no value. It's totally okay to throw away the first version of
> something. You showed that it _is_ possible to have idiomatic code with
> the help of procedural macros. Even if the implementation can be
> improved, the _idea_ remains yours.
I don't know what this is in reference to :P What work and throwing
away are you talking about?
> Paolo
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-25 14:04 ` Manos Pitsidianakis
@ 2024-10-25 15:22 ` Paolo Bonzini
2024-10-25 16:22 ` Manos Pitsidianakis
0 siblings, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-25 15:22 UTC (permalink / raw)
To: Manos Pitsidianakis
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Richard Henderson, Gustavo Romero, Pierrick Bouvier
[snipping to concentrate on the QOM code generation]
On Fri, Oct 25, 2024 at 4:05 PM Manos Pitsidianakis
<manos.pitsidianakis@linaro.org> wrote:
> > The part where I have most comments, and some ideas of how to make your
> > work a little more maintainable, is the implementation of class_init
> > (and all that depends on it).
> >
> > Let's start with these generated functions:
> >
> > > + pub unsafe extern "C" fn #realize_fn(
> > > + dev: *mut ::qemu_api::bindings::DeviceState,
> > > + errp: *mut *mut ::qemu_api::bindings::Error,
> > > + ) {
> > > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > > + unsafe {
> > > + DeviceImpl::realize(instance.as_mut());
> > > + }
> > > + }
> > > +
> > > + #[no_mangle]
> > > + pub unsafe extern "C" fn #reset_fn(
> > > + dev: *mut ::qemu_api::bindings::DeviceState,
> > > + ) {
> > > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > > + unsafe {
> > > + DeviceImpl::reset(instance.as_mut());
> > > + }
> > > + }
> >
> > This can be handled entirely in Rust code, outside the macro. If you add:
>
> Why? I don't understand what this solves. These are *just* trampoline
> functions to call the Rust-abi code.
Yes, and there is no need to place them in the procedural macros.
Generally it's a good rule to do as little as possible in procedural
macros, and move as much code as possible outside; this often means to
generic functions or a declarative macros. For some examples you can
look at Linux; in rust/macros/zeroable.rs for example the only
"quote!" is
quote! {
::kernel::__derive_zeroable!(
parse_input:
@sig(#(#rest)*),
@impl_generics(#(#new_impl_generics)*),
@ty_generics(#(#ty_generics)*),
@body(#last),
);
}
For more information, see https://www.youtube.com/watch?v=DMLBBZBlKis
> > unsafe extern "C" fn realize_fn_unsafe<T: DeviceImpl>(...) {}
> > unsafe extern "C" fn reset_fn_unsafe<T: DeviceImpl>(...) {}
>
> So just rename them and put a generic argument. Still not seeing any gain here.
The gain is that you have a much shorter macro implementation, a
shorter expansion which makes it easier to debug the macro, and an
implementation of realize_fn_unsafe<>() and reset_fn_unsafe<>() that
is isolated.
> > Going on to the implementation of the safe functions:
> >
> > > +impl DeviceImpl for PL011State {
> > > + fn realize(&mut self) {
> >
> > These are not quite traits. First, you can implement only some of the
> > functions.
>
> This is called "default implementations" in Rust
Sort of. Yes, this is related to default implementations, but the
default implementation of a QOM method is not "call whatever is
declared in the trait". It's "call whatever is defined by the
superclass", which is represented by None.
> > Second, if you don't implement them they are not overwritten
> > by the class_init method.
>
> WYM overwritten? That we hook up the empty stub instead of a NULL
> function pointer?
Not really the NULL function pointer: the value left by the
superclass. In other words, the class_init function should not have
// realize is a function in a trait
dc.as_mut().realize = Self::realize;
but rather
// realize is a constant Option<fn(...)> in a trait
if let Some(realize_fn) = Self::REALIZE {
dc.as_mut().realize = Self::realize_fn;
}
> > Why is this important? Because the only code transformation is the
> > generation of properties and vtables, and the bindings can be developed
> > almost entirely in qemu_api instead of qemu_api_macros. This has
> > several advantages:
> >
> > 1) it's much easier: simpler error messages, no macro indirection, no
> > super long global crate paths
>
> Hard no, sorry. Error messages can definitely be generated from proc
> macros. Long crate paths; that's just a matter of using imports or
> changing names.
I don't mean error messages from proc macros. I mean that debugging
macros that are this complex (does not matter if procedural or
declarative) is hell. If you move code outside the macro, to generic
functions and blanket trait implementations, error messages from the
compiler are simpler.
One additional benefit is that the code is compiled _always_, not just
if the macro hits a specific code path. It simplifies the testing
noticeably.
Finally, without -Zmacro-backtrace (nightly only) debugging procedural
macros is a tedious matter of using "cargo expand". With
-Zmacro-backtrace it's better but we don't want developers to install
nightly to develop QEMU. Note that this code is not write-once; it
will be extended for example as soon as we have a device that can fail
realization.
> Long crate paths; that's just a matter of using imports or
> changing names.
Only to some extent. See how many ::core::ffi:: or ::core::mem:: are
there in the current macros.
> > 2) it allows simplifying the pl011 code piecewise, even before
> > introducing procedural macro code
>
> Not sure how that is relevant can you explain?
Alex asked for a way to validate the expansion of the macro. If the
procedural macro is simple and the code is outside, then reviewers can
easily compare the pl011 code and the qemu-api-macros code, and see
that they expand to the same thing.
Try to put yourself into the mindset of the reviewer. If I see a patch like
+#[qom_class_init]
impl DeviceImpl for PL011State {
- const REALIZE: Option<fn(&mut self)> = {
fn realize(&mut self) { ... }
- Some(realize)
- };
- const RESET: Option<fn(&mut self)> = {
fn reset(&mut self) { ... }
- Some(reset)
- };
const VMSTATE: ... = ...;
const CATEGORY: ... = ...;
}
... then it's quite obvious what to expect from the expansion of the
#[qom_class_init] attribute macro.
> > It also becomes much easier to separate the work in separate patches, or
> > even separate series. Replacing the arguments to device_class_init!()
> > with DeviceImpl + DeviceImplUnsafe can be introduced first: posted,
> > reviewed, merged. All the remaining tasks are pretty much independent:
> >
> > 1) splitting out ObjectInfo and introducing #[object] to automate it
> > (i.e. extending derive(Object))
> >
> > 2) splitting out DeviceInfo and introducing #[property] to automate it
> > (i.e. derive(Device))
> >
> > 3) the #[qom_class_init] macro
>
> I disagree with this approach at the moment. This patch is in an
> acceptable state albeit a bit longish while all these suggestions
> would merely cause more running around making changes for no real
> gain while also delaying review and merging.
I will be very clear: no, this patch is very far from an acceptable
state. It's a wonderful prototype, just like hw/char/pl011, but at
this point we're not accepting prototype-quality Rust code anymore:
we're turning the existing prototype, which has a manageable but still
large size, into the real thing.
To sum up the basic points underlying the suggestions, my review comments are:
- the syntax of #[device(...)] is arbitrary. You are keeping things
that obviously refer to the QOM object (type name, parent type name)
far from #[derive(Object)]. You are putting in #[device()] things that
apply to the QOM object class (e.g. class_name_override) rather than
the QOM device class. Finally, you are putting in #[device()] the
vmstate, which is not self-contained (due to subsections).
- the split in modules needs more work. For example, why is
Migrateable under qemu_api::objects instead of qemu_api::device_class?
- the part that would require the fewest changes is probably
#[property], but we need to be careful about not introducing "fake
safety".
- to simplify splitting the patches, start by moving code out of the
existing declarative macros and into generic code or declarative
macros
- to simply reviewing the patches, only use the procedural macros as a
syntactic sugar, and do as little code generation as possible in the
procedural macros themselves
- splitting the ObjectImpl/DeviceImpl traits in two (one for code
generated by derive-macros; one for stuff that is definde outside the
struct declaration) could be a way to work incrementally, adding more
and more parts of the class definition to the procedural macro but
without doing everything at once
- it must be possible to specify non-overridden DeviceClass or
ObjectClass functions
The suggestions are just a way to achieve these objectives. In
particular, the suggestions are about how to achieve the bullets in
the above list starting from the third.
Also, this series *must* come after the other cleanups and CI
integration that have been posted, for several reasons.
As to the cleanups, right now we *already* have in the tree code that
is duplicate, dead or untested. Removing that, and ensuring that it
does not regress, is a priority over everything else.
As to CI integration, first, changes like this one must be fully
bisectable, which requires having working CI before. Second, if we
want to advertise 9.2 as supporting --enable-rust (even if
experimental), we need a way to run automated tests on at least _some_
of the platforms that we support.
So my plan right now is to post a pull request for part 1 next week,
and to include part 2 (including your work on migration and Luminary,
but without the procedural macro) in -rc1. This work is for 10.0.
> > A final word: I absolutely don't want you to think that your work is of
> > no value. It's totally okay to throw away the first version of
> > something. You showed that it _is_ possible to have idiomatic code with
> > the help of procedural macros. Even if the implementation can be
> > improved, the _idea_ remains yours.
>
> I don't know what this is in reference to :P What work and throwing
> away are you talking about?
I was talking in general. When working in Rust I often found that the
first thing I came up with was crap, and the main value of it was in
learning. Sometimes I came up with the second version on my own,
sometimes it was pointed out during review. But even if it's pointed
out during review, the reviewer doesn't in any way think bad of you or
your work.
Paolo
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-25 15:22 ` Paolo Bonzini
@ 2024-10-25 16:22 ` Manos Pitsidianakis
0 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-25 16:22 UTC (permalink / raw)
To: Paolo Bonzini
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Richard Henderson, Gustavo Romero, Pierrick Bouvier
I disagree with your points, Paolo. A patch review should not be "Fear
Uncertainty and Doubt" but point out actual problems with the code.
Please do not proceed with this conversation since it's not
productive.
If this series misses softfreeze because of code problems or by people
delaying the review/merge, we can figure out what to do when we get to
it.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
2024-10-24 15:13 ` Alex Bennée
2024-10-25 12:01 ` Paolo Bonzini
@ 2024-10-27 20:58 ` Paolo Bonzini
2024-10-27 22:39 ` Manos Pitsidianakis
2 siblings, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-27 20:58 UTC (permalink / raw)
To: Manos Pitsidianakis, qemu-devel
Cc: Peter Maydell, Marc-André Lureau, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Alex Bennée, Thomas Huth,
Junjie Mao, Junjie Mao, Zhao Liu, Kevin Wolf, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Hello,
here is my second attempt to review this, this time placing the remarks
as close as possible to the code that is affected. However, the meat is
the same as in my previous replies to the 03/11 thread.
I hope this shows that I have practical concerns about the patch and
it's not just FUD that it's not acceptable.
On 10/24/24 16:03, Manos Pitsidianakis wrote:
> Add a new derive procedural macro to declare device models. Add
> corresponding DeviceImpl trait after already existing ObjectImpl trait.
> At the same time, add instance_init, instance_post_init,
> instance_finalize methods to the ObjectImpl trait and call them from the
> ObjectImplUnsafe trait, which is generated by the procedural macro.
>
> This allows all the boilerplate device model registration to be handled
> by macros, and all pertinent details to be declared through proc macro
> attributes or trait associated constants and methods.
>
> The device class can now be generated automatically and the name can be
> optionally overridden:
>
> ------------------------ >8 ------------------------
> #[repr(C)]
> #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> #[device(class_name_override = PL011Class)]
> /// PL011 Device Model in QEMU
> pub struct PL011State {
The first design issue is already visible here in this example. I could
place the same comment when the code appears in rust/hw/char/pl011, but
it's easier to do it here.
You have two derive macros, Object and Device. Object is derived by all
objects (even if right now we have only devices), Device is derived by
devices only.
The class name is a property of any object, not just devices. It should
not be part of the #[device()] attribute. #[derive(Device)] and
#[device()] instead should take care of properties and categories (and
possibly vmstate, but I'm not sure about that and there's already enough
to say about this patch).
You also have no documentation, which means that users will have no idea
of what are the other sub-attributes of #[device()], including the
difference between class_name and class_name_override, or how categories
are defined.
Even if we don't have support for rustdoc yet in tree, we should have
all the needed documentation as soon as the API moves from "ad hoc usage
of C symbols" to idiomatic.
> Object methods (instance_init, etc) methods are now trait methods:
>
> ------------------------ >8 ------------------------
> /// Trait a type must implement to be registered with QEMU.
> pub trait ObjectImpl {
> type Class: ClassImpl;
> const TYPE_NAME: &'static CStr;
> const PARENT_TYPE_NAME: Option<&'static CStr>;
> const ABSTRACT: bool;
Class, TYPE_NAME, PARENT_TYPE_NAME, ABSTRACT should be defined via
#[object()].
But actually, there is already room for defining a separate trait:
/// # Safety
///
/// - the first field of the struct must be of `Object` type,
/// or derived from it
///
/// - `TYPE` must match the type name used in the `TypeInfo` (no matter
/// if it is defined in C or Rust).
///
/// - the struct must be `#[repr(C)]`
pub unsafe trait ObjectType {
type Class: ClassImpl;
const TYPE_NAME: &'static CStr;
}
... because you can implement it even for classes that are defined in C
code. Then #[derive(Object)] can find the TYPE_NAME directly from the
first field of the struct, i.e.
parent_obj: SysBusDevice;
becomes
const PARENT_TYPE_NAME: Option<&'static CStr> =
Some(<SysBusDevice as TypeImpl>::TYPE_NAME);
while #[object()] would be just
#[object(class_type = PL011Class, type_name = "pl011")]
Accessing the type of the first field is easy using the get_fields()
function that Junjie added at
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/
This shows another reason why I prefer to get CI to work first. Having
to do simple, but still non-trivial work, often provides code that can
be reused in more complex setups.
> unsafe fn instance_init(&mut self) {}
> fn instance_post_init(&mut self) {}
> fn instance_finalize(&mut self) {}
> }
In the trait, having a default implementation that is empty works
(unlike for realize/reset, as we'll see later). So this is a bit
simpler. However, instance_finalize should have a non-empty default
implementation:
std::ptr::drop_in_place(self);
which should be okay for most devices.
Alternatively, leave out instance_post_init() and instance_finalize()
until we need them, and put the drop_in_place() call directly in the
unsafe function that goes in the TypeInfo.
> ------------------------ >8 ------------------------
>
> Device methods (realize/reset etc) are now safe idiomatic trait methods:
>
> ------------------------ >8 ------------------------
> /// Implementation methods for device types.
> pub trait DeviceImpl: ObjectImpl {
> fn realize(&mut self) {}
> fn reset(&mut self) {}
> }
> ------------------------ >8 ------------------------
This is an incorrect definition of the trait. The default definition of
device methods is not "empty", it's "just reuse the superclass
implementation". In particular, this means that PL011LuminaryState
right now cannot use #[derive(Device)].
> The derive Device macro is responsible for creating all the extern "C" FFI
> functions that QEMU needs to call these methods.
This is unnecessary. It is perfectly possible to write the extern "C"
functions (class_init, realize, reset) just once as either type-generic
functions, or functions in a trait. More on this later.
> diff --git a/rust/qemu-api/src/objects.rs b/rust/qemu-api/src/objects.rs
> new file mode 100644
> index 0000000000000000000000000000000000000000..5c6762023ed6914f9c6b7dd16a5e07f778c2d4fa
> --- /dev/null
> +++ b/rust/qemu-api/src/objects.rs
> @@ -0,0 +1,90 @@
> +// Copyright 2024, Linaro Limited
> +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +//! Implementation traits for QEMU objects, devices.
> +
> +use ::core::ffi::{c_int, c_void, CStr};
> +
> +use crate::bindings::{DeviceState, Error, MigrationPriority, Object, ObjectClass, TypeInfo};
> +
> +/// Trait a type must implement to be registered with QEMU.
> +pub trait ObjectImpl {
> + type Class: ClassImpl;
> + const TYPE_NAME: &'static CStr;
> + const PARENT_TYPE_NAME: Option<&'static CStr>;
> + const ABSTRACT: bool;
These consts should entirely be derived from the #[object()] attribute.
You can facilitate the split by having two traits, one for things
derived from the attribute (the above four), and one for the vtable.
> + unsafe fn instance_init(&mut self) {}
> + fn instance_post_init(&mut self) {}
> + fn instance_finalize(&mut self) {}
> +}
See above remark on the default implementation of instance_finalize.
> +/// The `extern`/`unsafe` analogue of [`ObjectImpl`]; it is used internally by `#[derive(Object)]`
> +/// and should not be implemented manually.
> +pub unsafe trait ObjectImplUnsafe {
> + const TYPE_INFO: TypeInfo;
> +
> + const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
> + const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
> + const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
> +}
> +
This trait is not needed at all, because it really has juts one
implementation. The fact that there is just one implementation is
hidden by the fact that you are generating the code instead of relying
on the type system.
All you need is a single function, which will be called via the
module_init mechanism:
fn rust_type_register<T: ObjectImpl>() {
let TypeInfo ti = ...;
unsafe { type_register(&ti); }
}
>
> +/// Methods for QOM class types.
> +pub trait ClassImpl {
> + type Object: ObjectImpl;
> +
> + unsafe fn class_init(&mut self, _data: *mut core::ffi::c_void) {}
> + unsafe fn class_base_init(&mut self, _data: *mut core::ffi::c_void) {}
> +}
> +
This trait (or more precisely class_init and class_base_init) is not
needed. class_base_init is only needed in very special cases, we can
just decide they won't be available in Rust for now and possible for ever.
As to class_init device XYZ would only need a non-empty class_init
method if we added support for the _data argument. But then we would
need a way to provide the type of _data, and to cast _data to the
appropriate type; we would also need a way to provide a mapping from
multiple data objects to multiple type names, which is hard to do
because right now each Rust struct has a single type name associated.
So, let's just keep only the auto-generated class_init for simplicity.
If we can just decide that, if device XYZ has superclass FooDevice, it
implements FooDeviceImpl and class_init is provided by the FooDevice
bindings.
I can't really say if the "type Object" part is needed. I couldn't
offhand find anything that uses it, but I may have missed it. If so, it
can be in ClassImplUnsafe.
> +/// The `extern`/`unsafe` analogue of [`ClassImpl`]; it is used internally by `#[derive(Object)]`
> +/// and should not be implemented manually.
> +pub unsafe trait ClassImplUnsafe {
> + const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
> + const CLASS_BASE_INIT: Option<
> + unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
> + >;
> +}
Again, no need to have CLASS_BASE_INIT here.
> +/// Implementation methods for device types.
> +pub trait DeviceImpl: ObjectImpl {
> + fn realize(&mut self) {}
> + fn reset(&mut self) {}
> +}
These unfortunately cannot be functions. Doing so forces the class_init
method to assign both dc->reset and dc->realize for _all_ classes,
whereas for example PL011LuminaryClass would not override either.
Therefore, the definition must be
pub trait DeviceImpl: ObjectImpl {
const REALIZE: Option<fn realize(&mut self)> = None;
const RESET: Option<fn realize(&mut self)> = None;
}
Yes, it's uglier, but we cannot escape the fact that we're implementing
something that Rust doesn't have natively (inheritance). :( So we
cannot use language features meant for a completely different kind of
polymorphism.
> +/// The `extern`/`unsafe` analogue of [`DeviceImpl`]; it is used internally by `#[derive(Device)]`
> +/// and should not be implemented manually.
> +pub unsafe trait DeviceImplUnsafe {
> + const REALIZE: Option<unsafe extern "C" fn(dev: *mut DeviceState, _errp: *mut *mut Error)>;
> + const RESET: Option<unsafe extern "C" fn(dev: *mut DeviceState)>;
> +}
This trait is also unnecessary, because all that you need is a single
function:
fn rust_device_class_init<T: DeviceImpl>(
klass: *mut ObjectClass, _data: *mut c_void)
defined outside the procedural macro. #[derive(Device)] can define
ClassImplUnsafe to point CLASS_INIT to rust_device_class_init.
(Later, rust_device_class_init() can be moved into a trait so that it's
possible to define other classes of devices, for example PCI devices.
Note that such an extension would be much easier, than if it was
_required_ to touch the procedural macro).
>
> +/// Constant metadata and implementation methods for types with device migration state.
> +pub trait Migrateable: DeviceImplUnsafe {
> + const NAME: Option<&'static CStr> = None;
> + const UNMIGRATABLE: bool = true;
> + const EARLY_SETUP: bool = false;
> + const VERSION_ID: c_int = 1;
> + const MINIMUM_VERSION_ID: c_int = 1;
> + const PRIORITY: MigrationPriority = MigrationPriority::MIG_PRI_DEFAULT;
> +
> + unsafe fn pre_load(&mut self) -> c_int {
> + 0
> + }
> + unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
> + 0
> + }
> + unsafe fn pre_save(&mut self) -> c_int {
> + 0
> + }
> + unsafe fn post_save(&mut self) -> c_int {
> + 0
> + }
> + unsafe fn needed(&mut self) -> bool {
> + false
> + }
> + unsafe fn dev_unplug_pending(&mut self) -> bool {
> + false
> + }
> +}
Premature. No need to add this trait until you add support for migration.
> diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
> deleted file mode 100644
> index df54edbd4e27e7d2aafc243355d1826d52497c21..0000000000000000000000000000000000000000
> --- a/rust/qemu-api/src/tests.rs
> +++ /dev/null
> @@ -1,49 +0,0 @@
Nope. Fix the test, don't remove it.
> -#[derive(Debug, qemu_api_macros::Object)]
> +#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> +#[device(class_name_override = PL011Class)]
> /// PL011 Device Model in QEMU
> pub struct PL011State {
> pub parent_obj: SysBusDevice,
> @@ -51,6 +52,7 @@ pub struct PL011State {
> pub read_count: usize,
> pub read_trigger: usize,
> #[doc(alias = "chr")]
> + #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
(See earlier comments on accepting only a LitStr and deriving qdev_prop
from the type).
> +impl DeviceImpl for PL011State {
> + fn realize(&mut self) {
> + ...
> + }
> +
> + fn reset(&mut self) {
> + ...
> + }
This extractions of code into DeviceImpl is good. However, as I said
above, I'm not sure about the trait itself. I'll remark later when I
encounter the definition.
> +impl qemu_api::objects::Migrateable for PL011State {}
Premature.
Before moving on to the procedural macro code, my proposal to split the
patches is:
1) introduce the trait ObjectType, define it for Object, DeviceState and
SysBusDevice.
2) introduce the traits ObjectImpl, DeviceImpl and ClassImplUnsafe.
Define the first two for PL011State.
3) add to common code the wrappers that call into DeviceImpl, removing
them from PL011State
4) introduce the functions rust_type_register and rust_device_class_init
that use the traits.
5) remove most arguments of device_class_init!(), using the
infrastructure introduced in the previous two steps
6) split ObjectImpl into a part that will be covered by #[object()],
let's call it ObjectInfo
7) add implementation of #[object()], replace PL011State's
implementation of ObjectInfo with #[object()]
8) split DeviceImpl into a part that will be covered by #[device()]
(properties and categories), let's call it DeviceInfo
9) add #[derive(Device) and implementation of #[device()], replace
PL011State's implementation of DeviceInfo with #[device()]
Where 1-5 should be submitted as a separate series, one that does not
touch procedural macros *at all* and just generalizes the pl011 code
that defines QOM types.
Anyhow, I'll continue reviewing the procedural macro code.
> +#[derive(Debug, Default)]
> +struct DeriveContainer {
> + category: Option<syn::Path>,
> + class_name: Option<syn::Ident>,
> + class_name_override: Option<syn::Ident>,
> +}
Rename to DeviceAttribute.
> +impl Parse for DeriveContainer {
> + fn parse(input: ParseStream) -> Result<Self> {
syn::Result represents a parse error, not an error in the allowed syntax
of the attribute. Below, you're using panic! and unwrap(), but probably
instead of syn::Result we need to have something like
pub enum Error {
CompileError(syn::Span, String),
ParseError(syn::Error)
}
which extends the CompileError enum of
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/
and is amenable to use with "?". In particular, note the idiom used by
the root derive_offsets() functions:
let input = parse_macro_input!(input as DeriveInput);
let expanded = derive_offsets_or_error(input).
unwrap_or_else(Into::into);
TokenStream::from(expanded)
which works via an "impl From<CompileError> for proc_macro2::TokenStream".
I believe that most of the benefit of this series (basically, all except
the #[property] attribute) can be obtained without the procedural macro.
Therefore, once we do add the procedural macro, we should not have it
panic on errors.
> + let _: syn::Token![#] = input.parse()?;
> + let bracketed;
> + _ = syn::bracketed!(bracketed in input);
> + assert_eq!(DEVICE, bracketed.parse::<syn::Ident>()?);
> + let mut retval = Self {
> + category: None,
> + class_name: None,
> + class_name_override: None,
> + };
> + let content;
> + _ = syn::parenthesized!(content in bracketed);
> + while !content.is_empty() {
> + let value: syn::Ident = content.parse()?;
> + if value == CLASS_NAME {
> + let _: syn::Token![=] = content.parse()?;
> + if retval.class_name.is_some() {
> + panic!("{} can only be used at most once", CLASS_NAME);
> + }
No panic!, instead we need to return a compile_error!() TokenStream, or
as above a Result<> that can be converted to compile_error!() up in the
chain.
> + retval.class_name = Some(content.parse()?);
Please make this function a separate trait in utilities:
trait AttributeParsing {
const NAME: Symbol;
fn set(&mut self, key: &syn::Ident, input: &ParseStream) -> Result<()>;
fn parse(input: ParseStream) -> Result<Self> { ... }
}
Then the "if" can move to the struct-specific implementation of
AttributeParsing::set, while the rest can move to the default
implementation of AttributeParsing::parse.
#[property()] and #[device()] (and also the proposed #[object()]) can
then share the implementation of AttributeParsing::parse.
> + } else if value == CLASS_NAME_OVERRIDE {
> + let _: syn::Token![=] = content.parse()?;
> + if retval.class_name_override.is_some() {
> + panic!("{} can only be used at most once", CLASS_NAME_OVERRIDE);
> + }> + retval.class_name_override = Some(content.parse()?);
> + } else if value == CATEGORY {
> + let _: syn::Token![=] = content.parse()?;
> + if retval.category.is_some() {
> + panic!("{} can only be used at most once", CATEGORY);
> + }
> + let lit: syn::LitStr = content.parse()?;
> + let path: syn::Path = lit.parse()?;
Do I understand that this would be
category = "foo::bar::Baz"
? If so, why the extra quotes? There can actually be more than one
category, so at least add a TODO here.
> +#[derive(Debug)]
> +struct QdevProperty {
> + name: Option<syn::LitCStr>,
Just LitStr. Convert it to CString in the macro. You can reuse the
c_str!() macro that I'm adding in the series to fix CI and support old
rustc, i.e. quote! { ::qemu_api::c_str!(#name) } or something like that.
> + qdev_prop: Option<syn::Path>,
> +}
> +
> +impl Parse for QdevProperty {
> + fn parse(input: ParseStream) -> Result<Self> {
> + let _: syn::Token![#] = input.parse()?;
> + let bracketed;
> + _ = syn::bracketed!(bracketed in input);
> + assert_eq!(PROPERTY, bracketed.parse::<syn::Ident>()?);
> + let mut retval = Self {
> + name: None,
> + qdev_prop: None,
> + };
> + let content;
> + _ = syn::parenthesized!(content in bracketed);
> + while !content.is_empty() {
> + let value: syn::Ident = content.parse()?;
> + if value == NAME {
> + let _: syn::Token![=] = content.parse()?;
> + if retval.name.is_some() {
> + panic!("{} can only be used at most once", NAME);
> + }
> + retval.name = Some(content.parse()?);
> + } else if value == QDEV_PROP {
> + let _: syn::Token![=] = content.parse()?;
> + if retval.qdev_prop.is_some() {
> + panic!("{} can only be used at most once", QDEV_PROP);
> + }
> + retval.qdev_prop = Some(content.parse()?);
> + } else {
> + panic!("unrecognized token `{}`", value);
> + }
> +
> + if !content.is_empty() {
> + let _: syn::Token![,] = content.parse()?;
> + }
> + }
> + Ok(retval)
See above with respect to the duplicated code with #[device()].
> + let derive_container: DeriveContainer = input
> + .attrs
> + .iter()
> + .find(|a| a.path() == DEVICE)
> + .map(|a| syn::parse(a.to_token_stream().into()).expect("could not parse device attr"))
> + .unwrap_or_default();
> + let (qdev_properties_static, qdev_properties_expanded) = make_qdev_properties(&input);
Please put functions before their callers.
> + let realize_fn = format_ident!("__{}_realize_generated", name);
> + let reset_fn = format_ident!("__{}_reset_generated", name);
> +
> + let expanded = quote! {
> + unsafe impl ::qemu_api::objects::DeviceImplUnsafe for #name {
> + const REALIZE: ::core::option::Option<
> + unsafe extern "C" fn(
> + dev: *mut ::qemu_api::bindings::DeviceState,
> + errp: *mut *mut ::qemu_api::bindings::Error,
> + ),
> + > = Some(#realize_fn);
> + const RESET: ::core::option::Option<
> + unsafe extern "C" fn(dev: *mut ::qemu_api::bindings::DeviceState),
> + > = Some(#reset_fn);
> + }
> +
> + #[no_mangle]
Not needed.
> + pub unsafe extern "C" fn #realize_fn(
> + dev: *mut ::qemu_api::bindings::DeviceState,
> + errp: *mut *mut ::qemu_api::bindings::Error,
> + ) {
> + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> + unsafe {
> + ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
> + }
> + }
> +
> + #[no_mangle]
Not needed.
> + pub unsafe extern "C" fn #reset_fn(
> + dev: *mut ::qemu_api::bindings::DeviceState,
> + ) {
> + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> + unsafe {
> + ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
> + }
> + }
All this code depends on nothing but #name. This is not the C
preprocessor; the way to do it in Rust is monomorphization as described
above.
> +fn gen_device_class(
> + derive_container: DeriveContainer,
> + qdev_properties_static: syn::Ident,
> + name: &syn::Ident,
> +) -> proc_macro2::TokenStream {
> + let (class_name, class_def) = match (
> + derive_container.class_name_override,
> + derive_container.class_name,
> + ) {
> + (Some(class_name), _) => {
> + let class_expanded = quote! {
> + #[repr(C)]
> + pub struct #class_name {
> + _inner: [u8; 0],
> + }
> + };
> + (class_name, class_expanded)
> + }
> + (None, Some(class_name)) => (class_name, quote! {}),
> + (None, None) => {
> + let class_name = format_ident!("{}Class", name);
> + let class_expanded = quote! {
> + #[repr(C)]
> + pub struct #class_name {
> + _inner: [u8; 0],
> + }
This should have a DeviceClass member, it should not be a dummy 0-byte type.
Also, this should be generated by #[derive(Object)].
> + };
> + (class_name, class_expanded)
> + }
> + };
> + let class_init_fn = format_ident!("__{}_class_init_generated", class_name);
> + let class_base_init_fn = format_ident!("__{}_class_base_init_generated", class_name);
> +
> + let (vmsd, vmsd_impl) = {
> + let (i, vmsd) = make_vmstate(name);
> + (quote! { &#i }, vmsd)
> + };
> + let category = if let Some(category) = derive_container.category {
> + quote! {
> + const BITS_PER_LONG: u32 = ::core::ffi::c_ulong::BITS;
> + let _: ::qemu_api::bindings::DeviceCategory = #category;
> + let nr: ::core::ffi::c_ulong = #category as _;
> + let mask = 1 << (nr as u32 % BITS_PER_LONG);
> + let p = ::core::ptr::addr_of_mut!(dc.as_mut().categories).offset((nr as u32 / BITS_PER_LONG) as isize);
> + let p: *mut ::core::ffi::c_ulong = p.cast();
> + let categories = p.read_unaligned();
> + p.write_unaligned(categories | mask);
What's wrong with
const BITS_PER_ELEMENT: u32 =
::core::mem::sizeof(dc.categories) /
dc.categories.len() * 8;
dc.categories[((nr as u32) / BITS_PER_ELEMENT) as usize]
|= 1 << ((nr as u32) % BITS_PER_ELEMENT);
?
> + #[no_mangle]
> + pub unsafe extern "C" fn #class_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
> + {
> + {
> + let mut dc =
> + ::core::ptr::NonNull::new(klass.cast::<::qemu_api::bindings::DeviceClass>()).unwrap();
And then "let mut dc = dc.as_mut()". Just write the conversion once.
> + unsafe {
> + dc.as_mut().realize =
> + <#name as ::qemu_api::objects::DeviceImplUnsafe>::REALIZE;
> + ::qemu_api::bindings::device_class_set_legacy_reset(
> + dc.as_mut(),
> + <#name as ::qemu_api::objects::DeviceImplUnsafe>::RESET
> + );
As written elsewhere, these should be conditional.
> + dc.as_mut().vmsd = #vmsd;
> + #props
> + #category
> + }> + }
All this code should be outside the macro, and should use trait consts
instead of quoting.
> + let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
> + unsafe {
> + ::qemu_api::objects::ClassImpl::class_init(klass.as_mut(), data);
> + }
> + }
> + }
> + #[no_mangle]
> + pub unsafe extern "C" fn #class_base_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
> + {
> + let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
> + unsafe {
> + ::qemu_api::objects::ClassImpl::class_base_init(klass.as_mut(), data);
> + }
> + }
> + }
> +
> + #vmsd_impl
> + }
> +}
> +
> +fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStream) {
Not needed. Just let the user provide a VMStateDescription in
DeviceImpl. I'm not sure if it's possible to make it a const; if not,
it can be a function returning a &'static VMStateDescription.
> + let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
> +
> + let pre_load = format_ident!("__{}_pre_load_generated", name);
> + let post_load = format_ident!("__{}_post_load_generated", name);
> + let pre_save = format_ident!("__{}_pre_save_generated", name);
> + let post_save = format_ident!("__{}_post_save_generated", name);
> + let needed = format_ident!("__{}_needed_generated", name);
> + let dev_unplug_pending = format_ident!("__{}_dev_unplug_pending_generated", name);
> +
> + let migrateable_fish = quote! {<#name as ::qemu_api::objects::Migrateable>};
> + let vmstate_description = quote! {
> + #[used]
Attribute not needed.
> + #[allow(non_upper_case_globals)]
> + pub static #vmstate_description_ident: ::qemu_api::bindings::VMStateDescription = ::qemu_api::bindings::VMStateDescription {
> + name: if let Some(name) = #migrateable_fish::NAME {
> + name.as_ptr()
> + } else {
> + <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
> + },
> + unmigratable: #migrateable_fish::UNMIGRATABLE,
> + early_setup: #migrateable_fish::EARLY_SETUP,
> + version_id: #migrateable_fish::VERSION_ID,
> + minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
> + priority: #migrateable_fish::PRIORITY,
> + pre_load: Some(#pre_load),
> + post_load: Some(#post_load),
> + pre_save: Some(#pre_save),
> + post_save: Some(#post_save),
> + needed: Some(#needed),
> + dev_unplug_pending: Some(#dev_unplug_pending),
> + fields: ::core::ptr::null(),
> + subsections: ::core::ptr::null(),
> + };
> +
> + #[no_mangle]
Not needed (other occurrences below).
> + pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
> + }
> + }
> + #[no_mangle]
> + pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void, version_id: core::ffi::c_int) -> ::core::ffi::c_int {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::post_load(instance.as_mut(), version_id)
Again, introducing the Migrateable code and all these thunks is
premature; but in any case, this can only return 0 or -1 so make
Migrateable::post_load() return Result<(), ()>.
> + }
> + }
> + #[no_mangle]
> + pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
> + }
> + }
Likewise.
> + #[no_mangle]
> + pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
> + }
> + }
Likewise.
> + #[no_mangle]
> + pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) -> bool {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::needed(instance.as_mut())
> + }
> + }
> + #[no_mangle]
> + pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::ffi::c_void) -> bool {
> + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> + unsafe {
> + ::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
> + }
> + }
> + };
> +
> + let expanded = quote! {
> + #vmstate_description
> + };
> + (vmstate_description_ident, expanded)
> +}
> diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
> index 59aba592d9ae4c5a4cdfdc6f9b9b08363b8a57e5..7753a853fae72fc87e6dc642cf076c6d0c736345 100644
> --- a/rust/qemu-api-macros/src/lib.rs
> +++ b/rust/qemu-api-macros/src/lib.rs
> @@ -2,42 +2,21 @@
> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> // SPDX-License-Identifier: GPL-2.0-or-later
>
> +#![allow(dead_code)]
Why?
> use proc_macro::TokenStream;
> -use quote::{format_ident, quote};
> -use syn::{parse_macro_input, DeriveInput};
> +
> +mod device;
> +mod object;
> +mod symbols;
> +mod utilities;
>
> #[proc_macro_derive(Object)]
> pub fn derive_object(input: TokenStream) -> TokenStream {
> - let input = parse_macro_input!(input as DeriveInput);
> -
> - let name = input.ident;
> - let module_static = format_ident!("__{}_LOAD_MODULE", name);
> -
> - let expanded = quote! {
> - #[allow(non_upper_case_globals)]
> - #[used]
> - #[cfg_attr(target_os = "linux", link_section = ".ctors")]
> - #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
> - #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
> - pub static #module_static: extern "C" fn() = {
> - extern "C" fn __register() {
> - unsafe {
> - ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
> - }
> - }
> -
> - extern "C" fn __load() {
> - unsafe {
> - ::qemu_api::bindings::register_module_init(
> - Some(__register),
> - ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
> - );
> - }
> - }
> -
> - __load
> - };
> - };
> + object::derive_object(input)
Moving code to a separate file should be a separate patch from modifying
the expansion of the macro.
> diff --git a/rust/qemu-api-macros/src/symbols.rs b/rust/qemu-api-macros/src/symbols.rs
> new file mode 100644
> index 0000000000000000000000000000000000000000..f73768d228ed2b4d478c18336db56cb11e70f012
> --- /dev/null
> +++ b/rust/qemu-api-macros/src/symbols.rs
> @@ -0,0 +1,55 @@
> +// Copyright 2024, Linaro Limited
> +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +use core::fmt;
> +use syn::{Ident, Path};
> +
> +#[derive(Copy, Clone, Debug)]
> +pub struct Symbol(&'static str);
> +
> +pub const DEVICE: Symbol = Symbol("device");
> +pub const NAME: Symbol = Symbol("name");
> +pub const CATEGORY: Symbol = Symbol("category");
> +pub const CLASS_NAME: Symbol = Symbol("class_name");
> +pub const CLASS_NAME_OVERRIDE: Symbol = Symbol("class_name_override");
> +pub const QDEV_PROP: Symbol = Symbol("qdev_prop");
> +pub const MIGRATEABLE: Symbol = Symbol("migrateable");
> +pub const PROPERTIES: Symbol = Symbol("properties");
> +pub const PROPERTY: Symbol = Symbol("property");
Declare these in device.rs as needed, not here. This avoids "use
symbols::*". It also allows making them not "pub", so that dead ones
are detected by the compiler (e.g. MIGRATEABLE, PROPERTIES).
> +pub fn assert_is_repr_c_struct(input: &DeriveInput, derive_macro: &'static str) {
Nice but a bit overengineered. Unless you think/know that you'll have a
use for Repr elsewhere, try sharing code with Junjie's macro
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/.
Paolo
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-27 20:58 ` Paolo Bonzini
@ 2024-10-27 22:39 ` Manos Pitsidianakis
2024-10-28 7:07 ` Paolo Bonzini
0 siblings, 1 reply; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-27 22:39 UTC (permalink / raw)
To: Paolo Bonzini
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Zhao Liu, Kevin Wolf,
Richard Henderson, Gustavo Romero, Pierrick Bouvier
Thank you for the review comments Paolo. I will address any bits I did
wrong and not much the rest, it's obvious you have a disagreement over
how things are done and that's fine. This series does not attempt to
solve everything at once and arguing again and again over "this Trait
should have been OtherTrait and this thing should have been thing!()"
is not productive. Your review style of relentless disagreement after
disagreement is tiresome and impossible to deal with; it's like a
denial of service for other human beings. I suggest you take a step
back and take a deep breath before reviewing Rust patches again. I
assure you I will make sure to address all your comments either in
code, TODO comments, or patch messages.
In the meantime, take it easy.
On Sun, Oct 27, 2024 at 10:58 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> Hello,
>
> here is my second attempt to review this, this time placing the remarks
> as close as possible to the code that is affected. However, the meat is
> the same as in my previous replies to the 03/11 thread.
>
> I hope this shows that I have practical concerns about the patch and
> it's not just FUD that it's not acceptable.
>
> On 10/24/24 16:03, Manos Pitsidianakis wrote:
> > Add a new derive procedural macro to declare device models. Add
> > corresponding DeviceImpl trait after already existing ObjectImpl trait.
> > At the same time, add instance_init, instance_post_init,
> > instance_finalize methods to the ObjectImpl trait and call them from the
> > ObjectImplUnsafe trait, which is generated by the procedural macro.
> >
> > This allows all the boilerplate device model registration to be handled
> > by macros, and all pertinent details to be declared through proc macro
> > attributes or trait associated constants and methods.
> >
> > The device class can now be generated automatically and the name can be
> > optionally overridden:
> >
> > ------------------------ >8 ------------------------
> > #[repr(C)]
> > #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> > #[device(class_name_override = PL011Class)]
> > /// PL011 Device Model in QEMU
> > pub struct PL011State {
>
> The first design issue is already visible here in this example. I could
> place the same comment when the code appears in rust/hw/char/pl011, but
> it's easier to do it here.
>
> You have two derive macros, Object and Device. Object is derived by all
> objects (even if right now we have only devices), Device is derived by
> devices only.
>
> The class name is a property of any object, not just devices. It should
> not be part of the #[device()] attribute. #[derive(Device)] and
> #[device()] instead should take care of properties and categories (and
> possibly vmstate, but I'm not sure about that and there's already enough
> to say about this patch).
>
>
> You also have no documentation, which means that users will have no idea
> of what are the other sub-attributes of #[device()], including the
> difference between class_name and class_name_override, or how categories
> are defined.
>
> Even if we don't have support for rustdoc yet in tree, we should have
> all the needed documentation as soon as the API moves from "ad hoc usage
> of C symbols" to idiomatic.
>
> > Object methods (instance_init, etc) methods are now trait methods:
> >
> > ------------------------ >8 ------------------------
> > /// Trait a type must implement to be registered with QEMU.
> > pub trait ObjectImpl {
> > type Class: ClassImpl;
> > const TYPE_NAME: &'static CStr;
> > const PARENT_TYPE_NAME: Option<&'static CStr>;
> > const ABSTRACT: bool;
>
>
> Class, TYPE_NAME, PARENT_TYPE_NAME, ABSTRACT should be defined via
> #[object()].
>
> But actually, there is already room for defining a separate trait:
>
> /// # Safety
> ///
> /// - the first field of the struct must be of `Object` type,
> /// or derived from it
> ///
> /// - `TYPE` must match the type name used in the `TypeInfo` (no matter
> /// if it is defined in C or Rust).
> ///
> /// - the struct must be `#[repr(C)]`
> pub unsafe trait ObjectType {
> type Class: ClassImpl;
> const TYPE_NAME: &'static CStr;
> }
>
> ... because you can implement it even for classes that are defined in C
> code. Then #[derive(Object)] can find the TYPE_NAME directly from the
> first field of the struct, i.e.
>
> parent_obj: SysBusDevice;
>
> becomes
>
> const PARENT_TYPE_NAME: Option<&'static CStr> =
> Some(<SysBusDevice as TypeImpl>::TYPE_NAME);
>
> while #[object()] would be just
>
> #[object(class_type = PL011Class, type_name = "pl011")]
>
> Accessing the type of the first field is easy using the get_fields()
> function that Junjie added at
> https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/
>
> This shows another reason why I prefer to get CI to work first. Having
> to do simple, but still non-trivial work, often provides code that can
> be reused in more complex setups.
>
> > unsafe fn instance_init(&mut self) {}
> > fn instance_post_init(&mut self) {}
> > fn instance_finalize(&mut self) {}
> > }
>
> In the trait, having a default implementation that is empty works
> (unlike for realize/reset, as we'll see later). So this is a bit
> simpler. However, instance_finalize should have a non-empty default
> implementation:
>
> std::ptr::drop_in_place(self);
>
> which should be okay for most devices.
>
> Alternatively, leave out instance_post_init() and instance_finalize()
> until we need them, and put the drop_in_place() call directly in the
> unsafe function that goes in the TypeInfo.
>
> > ------------------------ >8 ------------------------
> >
> > Device methods (realize/reset etc) are now safe idiomatic trait methods:
> >
> > ------------------------ >8 ------------------------
> > /// Implementation methods for device types.
> > pub trait DeviceImpl: ObjectImpl {
> > fn realize(&mut self) {}
> > fn reset(&mut self) {}
> > }
> > ------------------------ >8 ------------------------
>
> This is an incorrect definition of the trait. The default definition of
> device methods is not "empty", it's "just reuse the superclass
> implementation". In particular, this means that PL011LuminaryState
> right now cannot use #[derive(Device)].
>
> > The derive Device macro is responsible for creating all the extern "C" FFI
> > functions that QEMU needs to call these methods.
>
> This is unnecessary. It is perfectly possible to write the extern "C"
> functions (class_init, realize, reset) just once as either type-generic
> functions, or functions in a trait. More on this later.
>
> > diff --git a/rust/qemu-api/src/objects.rs b/rust/qemu-api/src/objects.rs
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..5c6762023ed6914f9c6b7dd16a5e07f778c2d4fa
> > --- /dev/null
> > +++ b/rust/qemu-api/src/objects.rs
> > @@ -0,0 +1,90 @@
> > +// Copyright 2024, Linaro Limited
> > +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +//! Implementation traits for QEMU objects, devices.
> > +
> > +use ::core::ffi::{c_int, c_void, CStr};
> > +
> > +use crate::bindings::{DeviceState, Error, MigrationPriority, Object, ObjectClass, TypeInfo};
> > +
> > +/// Trait a type must implement to be registered with QEMU.
> > +pub trait ObjectImpl {
> > + type Class: ClassImpl;
> > + const TYPE_NAME: &'static CStr;
> > + const PARENT_TYPE_NAME: Option<&'static CStr>;
> > + const ABSTRACT: bool;
>
> These consts should entirely be derived from the #[object()] attribute.
> You can facilitate the split by having two traits, one for things
> derived from the attribute (the above four), and one for the vtable.
>
> > + unsafe fn instance_init(&mut self) {}
> > + fn instance_post_init(&mut self) {}
> > + fn instance_finalize(&mut self) {}
> > +}
>
> See above remark on the default implementation of instance_finalize.
>
> > +/// The `extern`/`unsafe` analogue of [`ObjectImpl`]; it is used internally by `#[derive(Object)]`
> > +/// and should not be implemented manually.
> > +pub unsafe trait ObjectImplUnsafe {
> > + const TYPE_INFO: TypeInfo;
> > +
> > + const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
> > + const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
> > + const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
> > +}
> > +
>
> This trait is not needed at all, because it really has juts one
> implementation. The fact that there is just one implementation is
> hidden by the fact that you are generating the code instead of relying
> on the type system.
>
> All you need is a single function, which will be called via the
> module_init mechanism:
>
> fn rust_type_register<T: ObjectImpl>() {
> let TypeInfo ti = ...;
> unsafe { type_register(&ti); }
> }
>
> >
> > +/// Methods for QOM class types.
> > +pub trait ClassImpl {
> > + type Object: ObjectImpl;
> > +
> > + unsafe fn class_init(&mut self, _data: *mut core::ffi::c_void) {}
> > + unsafe fn class_base_init(&mut self, _data: *mut core::ffi::c_void) {}
> > +}
> > +
>
> This trait (or more precisely class_init and class_base_init) is not
> needed. class_base_init is only needed in very special cases, we can
> just decide they won't be available in Rust for now and possible for ever.
>
> As to class_init device XYZ would only need a non-empty class_init
> method if we added support for the _data argument. But then we would
> need a way to provide the type of _data, and to cast _data to the
> appropriate type; we would also need a way to provide a mapping from
> multiple data objects to multiple type names, which is hard to do
> because right now each Rust struct has a single type name associated.
>
> So, let's just keep only the auto-generated class_init for simplicity.
> If we can just decide that, if device XYZ has superclass FooDevice, it
> implements FooDeviceImpl and class_init is provided by the FooDevice
> bindings.
>
> I can't really say if the "type Object" part is needed. I couldn't
> offhand find anything that uses it, but I may have missed it. If so, it
> can be in ClassImplUnsafe.
>
> > +/// The `extern`/`unsafe` analogue of [`ClassImpl`]; it is used internally by `#[derive(Object)]`
> > +/// and should not be implemented manually.
> > +pub unsafe trait ClassImplUnsafe {
> > + const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
> > + const CLASS_BASE_INIT: Option<
> > + unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
> > + >;
> > +}
>
> Again, no need to have CLASS_BASE_INIT here.
>
> > +/// Implementation methods for device types.
> > +pub trait DeviceImpl: ObjectImpl {
> > + fn realize(&mut self) {}
> > + fn reset(&mut self) {}
> > +}
>
> These unfortunately cannot be functions. Doing so forces the class_init
> method to assign both dc->reset and dc->realize for _all_ classes,
> whereas for example PL011LuminaryClass would not override either.
>
> Therefore, the definition must be
>
> pub trait DeviceImpl: ObjectImpl {
> const REALIZE: Option<fn realize(&mut self)> = None;
> const RESET: Option<fn realize(&mut self)> = None;
> }
>
> Yes, it's uglier, but we cannot escape the fact that we're implementing
> something that Rust doesn't have natively (inheritance). :( So we
> cannot use language features meant for a completely different kind of
> polymorphism.
>
> > +/// The `extern`/`unsafe` analogue of [`DeviceImpl`]; it is used internally by `#[derive(Device)]`
> > +/// and should not be implemented manually.
> > +pub unsafe trait DeviceImplUnsafe {
> > + const REALIZE: Option<unsafe extern "C" fn(dev: *mut DeviceState, _errp: *mut *mut Error)>;
> > + const RESET: Option<unsafe extern "C" fn(dev: *mut DeviceState)>;
> > +}
>
> This trait is also unnecessary, because all that you need is a single
> function:
>
> fn rust_device_class_init<T: DeviceImpl>(
> klass: *mut ObjectClass, _data: *mut c_void)
>
> defined outside the procedural macro. #[derive(Device)] can define
> ClassImplUnsafe to point CLASS_INIT to rust_device_class_init.
>
> (Later, rust_device_class_init() can be moved into a trait so that it's
> possible to define other classes of devices, for example PCI devices.
> Note that such an extension would be much easier, than if it was
> _required_ to touch the procedural macro).
>
> >
> > +/// Constant metadata and implementation methods for types with device migration state.
> > +pub trait Migrateable: DeviceImplUnsafe {
> > + const NAME: Option<&'static CStr> = None;
> > + const UNMIGRATABLE: bool = true;
> > + const EARLY_SETUP: bool = false;
> > + const VERSION_ID: c_int = 1;
> > + const MINIMUM_VERSION_ID: c_int = 1;
> > + const PRIORITY: MigrationPriority = MigrationPriority::MIG_PRI_DEFAULT;
> > +
> > + unsafe fn pre_load(&mut self) -> c_int {
> > + 0
> > + }
> > + unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
> > + 0
> > + }
> > + unsafe fn pre_save(&mut self) -> c_int {
> > + 0
> > + }
> > + unsafe fn post_save(&mut self) -> c_int {
> > + 0
> > + }
> > + unsafe fn needed(&mut self) -> bool {
> > + false
> > + }
> > + unsafe fn dev_unplug_pending(&mut self) -> bool {
> > + false
> > + }
> > +}
>
> Premature. No need to add this trait until you add support for migration.
>
> > diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
> > deleted file mode 100644
> > index df54edbd4e27e7d2aafc243355d1826d52497c21..0000000000000000000000000000000000000000
> > --- a/rust/qemu-api/src/tests.rs
> > +++ /dev/null
> > @@ -1,49 +0,0 @@
>
> Nope. Fix the test, don't remove it.
>
>
> > -#[derive(Debug, qemu_api_macros::Object)]
> > +#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
> > +#[device(class_name_override = PL011Class)]
> > /// PL011 Device Model in QEMU
> > pub struct PL011State {
> > pub parent_obj: SysBusDevice,
> > @@ -51,6 +52,7 @@ pub struct PL011State {
> > pub read_count: usize,
> > pub read_trigger: usize,
> > #[doc(alias = "chr")]
> > + #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
>
> (See earlier comments on accepting only a LitStr and deriving qdev_prop
> from the type).
>
> > +impl DeviceImpl for PL011State {
> > + fn realize(&mut self) {
> > + ...
> > + }
> > +
> > + fn reset(&mut self) {
> > + ...
> > + }
>
> This extractions of code into DeviceImpl is good. However, as I said
> above, I'm not sure about the trait itself. I'll remark later when I
> encounter the definition.
>
> > +impl qemu_api::objects::Migrateable for PL011State {}
>
> Premature.
>
> Before moving on to the procedural macro code, my proposal to split the
> patches is:
>
> 1) introduce the trait ObjectType, define it for Object, DeviceState and
> SysBusDevice.
>
> 2) introduce the traits ObjectImpl, DeviceImpl and ClassImplUnsafe.
> Define the first two for PL011State.
>
> 3) add to common code the wrappers that call into DeviceImpl, removing
> them from PL011State
>
> 4) introduce the functions rust_type_register and rust_device_class_init
> that use the traits.
>
> 5) remove most arguments of device_class_init!(), using the
> infrastructure introduced in the previous two steps
>
> 6) split ObjectImpl into a part that will be covered by #[object()],
> let's call it ObjectInfo
>
> 7) add implementation of #[object()], replace PL011State's
> implementation of ObjectInfo with #[object()]
>
> 8) split DeviceImpl into a part that will be covered by #[device()]
> (properties and categories), let's call it DeviceInfo
>
> 9) add #[derive(Device) and implementation of #[device()], replace
> PL011State's implementation of DeviceInfo with #[device()]
>
> Where 1-5 should be submitted as a separate series, one that does not
> touch procedural macros *at all* and just generalizes the pl011 code
> that defines QOM types.
>
>
> Anyhow, I'll continue reviewing the procedural macro code.
>
> > +#[derive(Debug, Default)]
> > +struct DeriveContainer {
> > + category: Option<syn::Path>,
> > + class_name: Option<syn::Ident>,
> > + class_name_override: Option<syn::Ident>,
> > +}
>
> Rename to DeviceAttribute.
>
> > +impl Parse for DeriveContainer {
> > + fn parse(input: ParseStream) -> Result<Self> {
>
> syn::Result represents a parse error, not an error in the allowed syntax
> of the attribute. Below, you're using panic! and unwrap(), but probably
> instead of syn::Result we need to have something like
>
> pub enum Error {
> CompileError(syn::Span, String),
> ParseError(syn::Error)
> }
>
> which extends the CompileError enum of
> https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/
> and is amenable to use with "?". In particular, note the idiom used by
> the root derive_offsets() functions:
>
> let input = parse_macro_input!(input as DeriveInput);
> let expanded = derive_offsets_or_error(input).
> unwrap_or_else(Into::into);
>
> TokenStream::from(expanded)
>
> which works via an "impl From<CompileError> for proc_macro2::TokenStream".
>
> I believe that most of the benefit of this series (basically, all except
> the #[property] attribute) can be obtained without the procedural macro.
> Therefore, once we do add the procedural macro, we should not have it
> panic on errors.
>
> > + let _: syn::Token![#] = input.parse()?;
> > + let bracketed;
> > + _ = syn::bracketed!(bracketed in input);
> > + assert_eq!(DEVICE, bracketed.parse::<syn::Ident>()?);
> > + let mut retval = Self {
> > + category: None,
> > + class_name: None,
> > + class_name_override: None,
> > + };
> > + let content;
> > + _ = syn::parenthesized!(content in bracketed);
> > + while !content.is_empty() {
> > + let value: syn::Ident = content.parse()?;
> > + if value == CLASS_NAME {
> > + let _: syn::Token![=] = content.parse()?;
> > + if retval.class_name.is_some() {
> > + panic!("{} can only be used at most once", CLASS_NAME);
> > + }
>
> No panic!, instead we need to return a compile_error!() TokenStream, or
> as above a Result<> that can be converted to compile_error!() up in the
> chain.
>
> > + retval.class_name = Some(content.parse()?);
>
> Please make this function a separate trait in utilities:
>
> trait AttributeParsing {
> const NAME: Symbol;
> fn set(&mut self, key: &syn::Ident, input: &ParseStream) -> Result<()>;
> fn parse(input: ParseStream) -> Result<Self> { ... }
> }
>
> Then the "if" can move to the struct-specific implementation of
> AttributeParsing::set, while the rest can move to the default
> implementation of AttributeParsing::parse.
>
> #[property()] and #[device()] (and also the proposed #[object()]) can
> then share the implementation of AttributeParsing::parse.
>
> > + } else if value == CLASS_NAME_OVERRIDE {
> > + let _: syn::Token![=] = content.parse()?;
> > + if retval.class_name_override.is_some() {
> > + panic!("{} can only be used at most once", CLASS_NAME_OVERRIDE);
> > + }> + retval.class_name_override = Some(content.parse()?);
> > + } else if value == CATEGORY {
> > + let _: syn::Token![=] = content.parse()?;
> > + if retval.category.is_some() {
> > + panic!("{} can only be used at most once", CATEGORY);
> > + }
> > + let lit: syn::LitStr = content.parse()?;
> > + let path: syn::Path = lit.parse()?;
>
> Do I understand that this would be
>
> category = "foo::bar::Baz"
>
> ? If so, why the extra quotes? There can actually be more than one
> category, so at least add a TODO here.
>
> > +#[derive(Debug)]
> > +struct QdevProperty {
> > + name: Option<syn::LitCStr>,
>
> Just LitStr. Convert it to CString in the macro. You can reuse the
> c_str!() macro that I'm adding in the series to fix CI and support old
> rustc, i.e. quote! { ::qemu_api::c_str!(#name) } or something like that.
>
> > + qdev_prop: Option<syn::Path>,
> > +}
> > +
> > +impl Parse for QdevProperty {
> > + fn parse(input: ParseStream) -> Result<Self> {
> > + let _: syn::Token![#] = input.parse()?;
> > + let bracketed;
> > + _ = syn::bracketed!(bracketed in input);
> > + assert_eq!(PROPERTY, bracketed.parse::<syn::Ident>()?);
> > + let mut retval = Self {
> > + name: None,
> > + qdev_prop: None,
> > + };
> > + let content;
> > + _ = syn::parenthesized!(content in bracketed);
> > + while !content.is_empty() {
> > + let value: syn::Ident = content.parse()?;
> > + if value == NAME {
> > + let _: syn::Token![=] = content.parse()?;
> > + if retval.name.is_some() {
> > + panic!("{} can only be used at most once", NAME);
> > + }
> > + retval.name = Some(content.parse()?);
> > + } else if value == QDEV_PROP {
> > + let _: syn::Token![=] = content.parse()?;
> > + if retval.qdev_prop.is_some() {
> > + panic!("{} can only be used at most once", QDEV_PROP);
> > + }
> > + retval.qdev_prop = Some(content.parse()?);
> > + } else {
> > + panic!("unrecognized token `{}`", value);
> > + }
> > +
> > + if !content.is_empty() {
> > + let _: syn::Token![,] = content.parse()?;
> > + }
> > + }
> > + Ok(retval)
>
> See above with respect to the duplicated code with #[device()].
>
> > + let derive_container: DeriveContainer = input
> > + .attrs
> > + .iter()
> > + .find(|a| a.path() == DEVICE)
> > + .map(|a| syn::parse(a.to_token_stream().into()).expect("could not parse device attr"))
> > + .unwrap_or_default();
> > + let (qdev_properties_static, qdev_properties_expanded) = make_qdev_properties(&input);
>
> Please put functions before their callers.
>
> > + let realize_fn = format_ident!("__{}_realize_generated", name);
> > + let reset_fn = format_ident!("__{}_reset_generated", name);
> > +
> > + let expanded = quote! {
> > + unsafe impl ::qemu_api::objects::DeviceImplUnsafe for #name {
> > + const REALIZE: ::core::option::Option<
> > + unsafe extern "C" fn(
> > + dev: *mut ::qemu_api::bindings::DeviceState,
> > + errp: *mut *mut ::qemu_api::bindings::Error,
> > + ),
> > + > = Some(#realize_fn);
> > + const RESET: ::core::option::Option<
> > + unsafe extern "C" fn(dev: *mut ::qemu_api::bindings::DeviceState),
> > + > = Some(#reset_fn);
> > + }
> > +
> > + #[no_mangle]
>
> Not needed.
>
> > + pub unsafe extern "C" fn #realize_fn(
> > + dev: *mut ::qemu_api::bindings::DeviceState,
> > + errp: *mut *mut ::qemu_api::bindings::Error,
> > + ) {
> > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > + unsafe {
> > + ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
> > + }
> > + }
> > +
> > + #[no_mangle]
>
> Not needed.
>
> > + pub unsafe extern "C" fn #reset_fn(
> > + dev: *mut ::qemu_api::bindings::DeviceState,
> > + ) {
> > + let mut instance = NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null pointer of type ", stringify!(#name)));
> > + unsafe {
> > + ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
> > + }
> > + }
>
> All this code depends on nothing but #name. This is not the C
> preprocessor; the way to do it in Rust is monomorphization as described
> above.
>
> > +fn gen_device_class(
> > + derive_container: DeriveContainer,
> > + qdev_properties_static: syn::Ident,
> > + name: &syn::Ident,
> > +) -> proc_macro2::TokenStream {
> > + let (class_name, class_def) = match (
> > + derive_container.class_name_override,
> > + derive_container.class_name,
> > + ) {
> > + (Some(class_name), _) => {
> > + let class_expanded = quote! {
> > + #[repr(C)]
> > + pub struct #class_name {
> > + _inner: [u8; 0],
> > + }
> > + };
> > + (class_name, class_expanded)
> > + }
> > + (None, Some(class_name)) => (class_name, quote! {}),
> > + (None, None) => {
> > + let class_name = format_ident!("{}Class", name);
> > + let class_expanded = quote! {
> > + #[repr(C)]
> > + pub struct #class_name {
> > + _inner: [u8; 0],
> > + }
>
> This should have a DeviceClass member, it should not be a dummy 0-byte type.
>
> Also, this should be generated by #[derive(Object)].
>
> > + };
> > + (class_name, class_expanded)
> > + }
> > + };
> > + let class_init_fn = format_ident!("__{}_class_init_generated", class_name);
> > + let class_base_init_fn = format_ident!("__{}_class_base_init_generated", class_name);
> > +
> > + let (vmsd, vmsd_impl) = {
> > + let (i, vmsd) = make_vmstate(name);
> > + (quote! { &#i }, vmsd)
> > + };
> > + let category = if let Some(category) = derive_container.category {
> > + quote! {
> > + const BITS_PER_LONG: u32 = ::core::ffi::c_ulong::BITS;
> > + let _: ::qemu_api::bindings::DeviceCategory = #category;
> > + let nr: ::core::ffi::c_ulong = #category as _;
> > + let mask = 1 << (nr as u32 % BITS_PER_LONG);
> > + let p = ::core::ptr::addr_of_mut!(dc.as_mut().categories).offset((nr as u32 / BITS_PER_LONG) as isize);
> > + let p: *mut ::core::ffi::c_ulong = p.cast();
> > + let categories = p.read_unaligned();
> > + p.write_unaligned(categories | mask);
>
> What's wrong with
>
> const BITS_PER_ELEMENT: u32 =
> ::core::mem::sizeof(dc.categories) /
> dc.categories.len() * 8;
>
> dc.categories[((nr as u32) / BITS_PER_ELEMENT) as usize]
> |= 1 << ((nr as u32) % BITS_PER_ELEMENT);
>
> ?
>
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #class_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
> > + {
> > + {
> > + let mut dc =
> > + ::core::ptr::NonNull::new(klass.cast::<::qemu_api::bindings::DeviceClass>()).unwrap();
>
> And then "let mut dc = dc.as_mut()". Just write the conversion once.
>
> > + unsafe {
> > + dc.as_mut().realize =
> > + <#name as ::qemu_api::objects::DeviceImplUnsafe>::REALIZE;
> > + ::qemu_api::bindings::device_class_set_legacy_reset(
> > + dc.as_mut(),
> > + <#name as ::qemu_api::objects::DeviceImplUnsafe>::RESET
> > + );
>
> As written elsewhere, these should be conditional.
>
> > + dc.as_mut().vmsd = #vmsd;
> > + #props
> > + #category
> > + }> + }
>
> All this code should be outside the macro, and should use trait consts
> instead of quoting.
>
> > + let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
> > + unsafe {
> > + ::qemu_api::objects::ClassImpl::class_init(klass.as_mut(), data);
> > + }
> > + }
> > + }
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #class_base_init_fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
> > + {
> > + let mut klass = NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a non-null pointer of type ", stringify!(#class_name)));
> > + unsafe {
> > + ::qemu_api::objects::ClassImpl::class_base_init(klass.as_mut(), data);
> > + }
> > + }
> > + }
> > +
> > + #vmsd_impl
> > + }
> > +}
> > +
> > +fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStream) {
>
> Not needed. Just let the user provide a VMStateDescription in
> DeviceImpl. I'm not sure if it's possible to make it a const; if not,
> it can be a function returning a &'static VMStateDescription.
>
> > + let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
> > +
> > + let pre_load = format_ident!("__{}_pre_load_generated", name);
> > + let post_load = format_ident!("__{}_post_load_generated", name);
> > + let pre_save = format_ident!("__{}_pre_save_generated", name);
> > + let post_save = format_ident!("__{}_post_save_generated", name);
> > + let needed = format_ident!("__{}_needed_generated", name);
> > + let dev_unplug_pending = format_ident!("__{}_dev_unplug_pending_generated", name);
> > +
> > + let migrateable_fish = quote! {<#name as ::qemu_api::objects::Migrateable>};
> > + let vmstate_description = quote! {
> > + #[used]
>
> Attribute not needed.
>
> > + #[allow(non_upper_case_globals)]
> > + pub static #vmstate_description_ident: ::qemu_api::bindings::VMStateDescription = ::qemu_api::bindings::VMStateDescription {
> > + name: if let Some(name) = #migrateable_fish::NAME {
> > + name.as_ptr()
> > + } else {
> > + <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
> > + },
> > + unmigratable: #migrateable_fish::UNMIGRATABLE,
> > + early_setup: #migrateable_fish::EARLY_SETUP,
> > + version_id: #migrateable_fish::VERSION_ID,
> > + minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
> > + priority: #migrateable_fish::PRIORITY,
> > + pre_load: Some(#pre_load),
> > + post_load: Some(#post_load),
> > + pre_save: Some(#pre_save),
> > + post_save: Some(#post_save),
> > + needed: Some(#needed),
> > + dev_unplug_pending: Some(#dev_unplug_pending),
> > + fields: ::core::ptr::null(),
> > + subsections: ::core::ptr::null(),
> > + };
> > +
> > + #[no_mangle]
>
> Not needed (other occurrences below).
>
> > + pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
> > + }
> > + }
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void, version_id: core::ffi::c_int) -> ::core::ffi::c_int {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::post_load(instance.as_mut(), version_id)
>
> Again, introducing the Migrateable code and all these thunks is
> premature; but in any case, this can only return 0 or -1 so make
> Migrateable::post_load() return Result<(), ()>.
>
> > + }
> > + }
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
> > + }
> > + }
>
> Likewise.
>
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
> > + }
> > + }
>
> Likewise.
>
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) -> bool {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::needed(instance.as_mut())
> > + }
> > + }
> > + #[no_mangle]
> > + pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::ffi::c_void) -> bool {
> > + let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
> > + unsafe {
> > + ::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
> > + }
> > + }
> > + };
> > +
> > + let expanded = quote! {
> > + #vmstate_description
> > + };
> > + (vmstate_description_ident, expanded)
> > +}
> > diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
> > index 59aba592d9ae4c5a4cdfdc6f9b9b08363b8a57e5..7753a853fae72fc87e6dc642cf076c6d0c736345 100644
> > --- a/rust/qemu-api-macros/src/lib.rs
> > +++ b/rust/qemu-api-macros/src/lib.rs
> > @@ -2,42 +2,21 @@
> > // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> > // SPDX-License-Identifier: GPL-2.0-or-later
> >
> > +#![allow(dead_code)]
>
> Why?
>
> > use proc_macro::TokenStream;
> > -use quote::{format_ident, quote};
> > -use syn::{parse_macro_input, DeriveInput};
> > +
> > +mod device;
> > +mod object;
> > +mod symbols;
> > +mod utilities;
> >
> > #[proc_macro_derive(Object)]
> > pub fn derive_object(input: TokenStream) -> TokenStream {
> > - let input = parse_macro_input!(input as DeriveInput);
> > -
> > - let name = input.ident;
> > - let module_static = format_ident!("__{}_LOAD_MODULE", name);
> > -
> > - let expanded = quote! {
> > - #[allow(non_upper_case_globals)]
> > - #[used]
> > - #[cfg_attr(target_os = "linux", link_section = ".ctors")]
> > - #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
> > - #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
> > - pub static #module_static: extern "C" fn() = {
> > - extern "C" fn __register() {
> > - unsafe {
> > - ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
> > - }
> > - }
> > -
> > - extern "C" fn __load() {
> > - unsafe {
> > - ::qemu_api::bindings::register_module_init(
> > - Some(__register),
> > - ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
> > - );
> > - }
> > - }
> > -
> > - __load
> > - };
> > - };
> > + object::derive_object(input)
>
> Moving code to a separate file should be a separate patch from modifying
> the expansion of the macro.
>
> > diff --git a/rust/qemu-api-macros/src/symbols.rs b/rust/qemu-api-macros/src/symbols.rs
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..f73768d228ed2b4d478c18336db56cb11e70f012
> > --- /dev/null
> > +++ b/rust/qemu-api-macros/src/symbols.rs
> > @@ -0,0 +1,55 @@
> > +// Copyright 2024, Linaro Limited
> > +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +use core::fmt;
> > +use syn::{Ident, Path};
> > +
> > +#[derive(Copy, Clone, Debug)]
> > +pub struct Symbol(&'static str);
> > +
> > +pub const DEVICE: Symbol = Symbol("device");
> > +pub const NAME: Symbol = Symbol("name");
> > +pub const CATEGORY: Symbol = Symbol("category");
> > +pub const CLASS_NAME: Symbol = Symbol("class_name");
> > +pub const CLASS_NAME_OVERRIDE: Symbol = Symbol("class_name_override");
> > +pub const QDEV_PROP: Symbol = Symbol("qdev_prop");
> > +pub const MIGRATEABLE: Symbol = Symbol("migrateable");
> > +pub const PROPERTIES: Symbol = Symbol("properties");
> > +pub const PROPERTY: Symbol = Symbol("property");
>
> Declare these in device.rs as needed, not here. This avoids "use
> symbols::*". It also allows making them not "pub", so that dead ones
> are detected by the compiler (e.g. MIGRATEABLE, PROPERTIES).
>
> > +pub fn assert_is_repr_c_struct(input: &DeriveInput, derive_macro: &'static str) {
>
> Nice but a bit overengineered. Unless you think/know that you'll have a
> use for Repr elsewhere, try sharing code with Junjie's macro
> https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonzini@redhat.com/.
>
> Paolo
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
2024-10-27 22:39 ` Manos Pitsidianakis
@ 2024-10-28 7:07 ` Paolo Bonzini
0 siblings, 0 replies; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-28 7:07 UTC (permalink / raw)
To: Manos Pitsidianakis
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Zhao Liu, Kevin Wolf,
Richard Henderson, Gustavo Romero, Pierrick Bouvier
[-- Attachment #1: Type: text/plain, Size: 2192 bytes --]
Manos,
I understand your desire to make fast progress, at the same time I can't
stress enough the importance of building on a shared design. Each
architectural decision we make now - whether about trait hierarchies, macro
designs, or API patterns - will significantly impact future maintenance and
development work.
The suggestions about how to split the implementation into smaller patches
aim to facilitate smaller and easier discussions about specific aspects,
and they are only examples and in no way the only possible solution.
However, with respect to design choices I think I pointed out major issues
that can't be treated simply as wanting to "do everything at once".
Dismissing design discussions as mere disagreements about style, or
responding confrontationally that you will address "bits that are wrong and
not much else" won't help us move forward. Code reviews, especially for
core APIs, necessarily include discussion of design choices and language
usage.
This isn't the first time our technical discussions have become tense, and
we're talking past each other at this point. My suggestion is that you try
to explain your point of view to a third person, that can help you
understand the patch review process and what is requested of both code
contributors and reviewers.
Thanks,
Paolo
Il dom 27 ott 2024, 23:40 Manos Pitsidianakis <
manos.pitsidianakis@linaro.org> ha scritto:
> Thank you for the review comments Paolo. I will address any bits I did
> wrong and not much the rest, it's obvious you have a disagreement over
> how things are done and that's fine. This series does not attempt to
> solve everything at once and arguing again and again over "this Trait
> should have been OtherTrait and this thing should have been thing!()"
> is not productive. Your review style of relentless disagreement after
> disagreement is tiresome and impossible to deal with; it's like a
> denial of service for other human beings. I suggest you take a step
> back and take a deep breath before reviewing Rust patches again. I
> assure you I will make sure to address all your comments either in
> code, TODO comments, or patch messages.
>
> In the meantime, take it easy.
>
>
[-- Attachment #2: Type: text/html, Size: 2896 bytes --]
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 04/11] rust: add support for migration in device models
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (2 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 05/11] rust/pl011: move CLK_NAME static to function scope Manos Pitsidianakis
` (7 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
This commit adds support for declaring migration state to device models
in Rust. This is done through different but related parts:
- The Device derive macro gains new attributes `vmstate_fields` and
`vmstate_subsections`. This allows the device declaration to include
the vmstate fields directly at the struct definition.
- a new qemu_api module, `vmstate` was added. There a bunch of Rust
macros declared there that are equivalent in spirit to the C macros
declared in include/migration/vmstate.h.
For example the Rust of equivalent of the C macro:
VMSTATE_UINT32(field_name, struct_name)
is:
vmstate_uint32!(field_name, StructName)
This breathtaking development now allows us to not have to define
VMStateDescription ourselves but split the notion of migration to two
parts:
- A Migrateable trait that allows a type to define version_ids, name,
priority, override methods like pre_load, post_load, pre_save etc.
- Define the actual vmstate fields and subsections through the Device
derive macro right there with the struct definition:
------------------------ >8 ------------------------
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
+#[device(
+ class_name_override = PL011Class,
+ vmstate_fields = vmstate_fields!{
+ vmstate_unused!(u32::BITS as u64),
+ vmstate_uint32!(flags, PL011State),
+ vmstate_uint32!(line_control, PL011State),
+ vmstate_uint32!(receive_status_error_clear, PL011State),
+ vmstate_uint32!(control, PL011State),
+ vmstate_uint32!(dmacr, PL011State),
+ vmstate_uint32!(int_enabled, PL011State),
+ vmstate_uint32!(int_level, PL011State),
+ vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH),
+ vmstate_uint32!(ilpr, PL011State),
+ vmstate_uint32!(ibrd, PL011State),
+ vmstate_uint32!(fbrd, PL011State),
+ vmstate_uint32!(ifl, PL011State),
+ vmstate_int32!(read_pos, PL011State),
+ vmstate_int32!(read_count, PL011State),
+ vmstate_int32!(read_trigger, PL011State),
+ },
+ vmstate_subsections = vmstate_subsections!{
+ VMSTATE_PL011_CLOCK
+ }
+)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
------------------------ >8 ------------------------
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 92 +++++++-
rust/qemu-api-macros/src/device.rs | 111 +++-------
rust/qemu-api-macros/src/lib.rs | 1 +
rust/qemu-api-macros/src/symbols.rs | 2 +
rust/qemu-api-macros/src/vmstate.rs | 113 ++++++++++
rust/qemu-api/meson.build | 1 +
rust/qemu-api/src/lib.rs | 3 +
rust/qemu-api/src/vmstate.rs | 403 ++++++++++++++++++++++++++++++++++++
8 files changed, 637 insertions(+), 89 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index c469877b1ca70dd1a02e3a2449c65ad3e57c93ae..57dc37dadef631fbccfa3049a3d8701b4e62b5b3 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -10,6 +10,8 @@
use qemu_api::{
bindings::{self, *},
objects::*,
+ vmstate_clock, vmstate_fields, vmstate_int32, vmstate_subsections, vmstate_uint32,
+ vmstate_uint32_array, vmstate_unused,
};
use crate::{
@@ -20,14 +22,74 @@
static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
+/// Integer Baud Rate Divider, `UARTIBRD`
+const IBRD_MASK: u32 = 0x3f;
+
+/// Fractional Baud Rate Divider, `UARTFBRD`
+const FBRD_MASK: u32 = 0xffff;
+
const DATA_BREAK: u32 = 1 << 10;
/// QEMU sourced constant.
pub const PL011_FIFO_DEPTH: usize = 16_usize;
+#[no_mangle]
+extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_ref().migrate_clock
+ }
+}
+
+qemu_api::vmstate_description! {
+ /// Migration subsection for [`PL011State`] clock.
+ pub static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription {
+ name: c"pl011/clock",
+ unmigratable: false,
+ early_setup: false,
+ version_id: 1,
+ minimum_version_id: 1,
+ priority: MigrationPriority::MIG_PRI_DEFAULT,
+ pre_load: None,
+ post_load: None,
+ pre_save: None,
+ post_save: None,
+ needed: Some(pl011_clock_needed),
+ dev_unplug_pending: None,
+ fields: vmstate_fields!{
+ vmstate_clock!(clock, PL011State),
+ },
+ subsections: ::core::ptr::null(),
+ };
+}
+
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
-#[device(class_name_override = PL011Class)]
+#[device(
+ class_name_override = PL011Class,
+ vmstate_fields = vmstate_fields!{
+ vmstate_unused!(u32::BITS as u64),
+ vmstate_uint32!(flags, PL011State),
+ vmstate_uint32!(line_control, PL011State),
+ vmstate_uint32!(receive_status_error_clear, PL011State),
+ vmstate_uint32!(control, PL011State),
+ vmstate_uint32!(dmacr, PL011State),
+ vmstate_uint32!(int_enabled, PL011State),
+ vmstate_uint32!(int_level, PL011State),
+ vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH),
+ vmstate_uint32!(ilpr, PL011State),
+ vmstate_uint32!(ibrd, PL011State),
+ vmstate_uint32!(fbrd, PL011State),
+ vmstate_uint32!(ifl, PL011State),
+ vmstate_int32!(read_pos, PL011State),
+ vmstate_int32!(read_count, PL011State),
+ vmstate_int32!(read_trigger, PL011State),
+ },
+ vmstate_subsections = vmstate_subsections!{
+ VMSTATE_PL011_CLOCK
+ }
+)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
@@ -165,7 +227,33 @@ fn reset(&mut self) {
}
}
-impl qemu_api::objects::Migrateable for PL011State {}
+impl qemu_api::objects::Migrateable for PL011State {
+ const NAME: Option<&'static CStr> = Some(c"pl011");
+ const UNMIGRATABLE: bool = false;
+ const VERSION_ID: c_int = 2;
+ const MINIMUM_VERSION_ID: c_int = 2;
+
+ unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
+ /* Sanity-check input state */
+ if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() {
+ return -1;
+ }
+
+ if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > 0 {
+ // Older versions of PL011 didn't ensure that the single
+ // character in the FIFO in FIFO-disabled mode is in
+ // element 0 of the array; convert to follow the current
+ // code's assumptions.
+ self.read_fifo[0] = self.read_fifo[self.read_pos];
+ self.read_pos = 0;
+ }
+
+ self.ibrd &= IBRD_MASK;
+ self.fbrd &= FBRD_MASK;
+
+ 0
+ }
+}
#[used]
pub static CLK_NAME: &CStr = c"clk";
diff --git a/rust/qemu-api-macros/src/device.rs b/rust/qemu-api-macros/src/device.rs
index 3b965576a065601cd5c97d5ab6a2501f96d16a61..a666c64087715b9dc0d9ebe33f2b22d965381c64 100644
--- a/rust/qemu-api-macros/src/device.rs
+++ b/rust/qemu-api-macros/src/device.rs
@@ -10,11 +10,13 @@
};
use syn::{parse_macro_input, DeriveInput};
-use crate::{symbols::*, utilities::*};
+use crate::{symbols::*, utilities::*, vmstate};
#[derive(Debug, Default)]
struct DeriveContainer {
category: Option<syn::Path>,
+ vmstate_fields: Option<syn::Expr>,
+ vmstate_subsections: Option<syn::Expr>,
class_name: Option<syn::Ident>,
class_name_override: Option<syn::Ident>,
}
@@ -27,6 +29,8 @@ fn parse(input: ParseStream) -> Result<Self> {
assert_eq!(DEVICE, bracketed.parse::<syn::Ident>()?);
let mut retval = Self {
category: None,
+ vmstate_fields: None,
+ vmstate_subsections: None,
class_name: None,
class_name_override: None,
};
@@ -54,6 +58,20 @@ fn parse(input: ParseStream) -> Result<Self> {
let lit: syn::LitStr = content.parse()?;
let path: syn::Path = lit.parse()?;
retval.category = Some(path);
+ } else if value == VMSTATE_FIELDS {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.vmstate_fields.is_some() {
+ panic!("{} can only be used at most once", VMSTATE_FIELDS);
+ }
+ let expr: syn::Expr = content.parse()?;
+ retval.vmstate_fields = Some(expr);
+ } else if value == VMSTATE_SUBSECTIONS {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.vmstate_subsections.is_some() {
+ panic!("{} can only be used at most once", VMSTATE_SUBSECTIONS);
+ }
+ let expr: syn::Expr = content.parse()?;
+ retval.vmstate_subsections = Some(expr);
} else {
panic!("unrecognized token `{}`", value);
}
@@ -272,7 +290,11 @@ pub struct #class_name {
let class_base_init_fn = format_ident!("__{}_class_base_init_generated", class_name);
let (vmsd, vmsd_impl) = {
- let (i, vmsd) = make_vmstate(name);
+ let (i, vmsd) = vmstate::make_vmstate(
+ name,
+ derive_container.vmstate_fields,
+ derive_container.vmstate_subsections,
+ );
(quote! { &#i }, vmsd)
};
let category = if let Some(category) = derive_container.category {
@@ -346,88 +368,3 @@ unsafe impl ::qemu_api::objects::ClassImplUnsafe for #class_name {
#vmsd_impl
}
}
-
-fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStream) {
- let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
-
- let pre_load = format_ident!("__{}_pre_load_generated", name);
- let post_load = format_ident!("__{}_post_load_generated", name);
- let pre_save = format_ident!("__{}_pre_save_generated", name);
- let post_save = format_ident!("__{}_post_save_generated", name);
- let needed = format_ident!("__{}_needed_generated", name);
- let dev_unplug_pending = format_ident!("__{}_dev_unplug_pending_generated", name);
-
- let migrateable_fish = quote! {<#name as ::qemu_api::objects::Migrateable>};
- let vmstate_description = quote! {
- #[used]
- #[allow(non_upper_case_globals)]
- pub static #vmstate_description_ident: ::qemu_api::bindings::VMStateDescription = ::qemu_api::bindings::VMStateDescription {
- name: if let Some(name) = #migrateable_fish::NAME {
- name.as_ptr()
- } else {
- <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
- },
- unmigratable: #migrateable_fish::UNMIGRATABLE,
- early_setup: #migrateable_fish::EARLY_SETUP,
- version_id: #migrateable_fish::VERSION_ID,
- minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
- priority: #migrateable_fish::PRIORITY,
- pre_load: Some(#pre_load),
- post_load: Some(#post_load),
- pre_save: Some(#pre_save),
- post_save: Some(#post_save),
- needed: Some(#needed),
- dev_unplug_pending: Some(#dev_unplug_pending),
- fields: ::core::ptr::null(),
- subsections: ::core::ptr::null(),
- };
-
- #[no_mangle]
- pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
- }
- }
- #[no_mangle]
- pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void, version_id: core::ffi::c_int) -> ::core::ffi::c_int {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::post_load(instance.as_mut(), version_id)
- }
- }
- #[no_mangle]
- pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
- }
- }
- #[no_mangle]
- pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
- }
- }
- #[no_mangle]
- pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) -> bool {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::needed(instance.as_mut())
- }
- }
- #[no_mangle]
- pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::ffi::c_void) -> bool {
- let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
- unsafe {
- ::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
- }
- }
- };
-
- let expanded = quote! {
- #vmstate_description
- };
- (vmstate_description_ident, expanded)
-}
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index 7753a853fae72fc87e6dc642cf076c6d0c736345..7b5c0c044da879241b05bba75edcb17b498e5d5a 100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -10,6 +10,7 @@
mod object;
mod symbols;
mod utilities;
+mod vmstate;
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
diff --git a/rust/qemu-api-macros/src/symbols.rs b/rust/qemu-api-macros/src/symbols.rs
index f73768d228ed2b4d478c18336db56cb11e70f012..79c242cf069d5de1dd0cd61b2a5c7814564af47e 100644
--- a/rust/qemu-api-macros/src/symbols.rs
+++ b/rust/qemu-api-macros/src/symbols.rs
@@ -15,6 +15,8 @@
pub const CLASS_NAME_OVERRIDE: Symbol = Symbol("class_name_override");
pub const QDEV_PROP: Symbol = Symbol("qdev_prop");
pub const MIGRATEABLE: Symbol = Symbol("migrateable");
+pub const VMSTATE_FIELDS: Symbol = Symbol("vmstate_fields");
+pub const VMSTATE_SUBSECTIONS: Symbol = Symbol("vmstate_subsections");
pub const PROPERTIES: Symbol = Symbol("properties");
pub const PROPERTY: Symbol = Symbol("property");
diff --git a/rust/qemu-api-macros/src/vmstate.rs b/rust/qemu-api-macros/src/vmstate.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2d72bf13b5acc861fac0814d749762ddb76824d5
--- /dev/null
+++ b/rust/qemu-api-macros/src/vmstate.rs
@@ -0,0 +1,113 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use quote::{format_ident, quote};
+
+pub fn make_vmstate(
+ name: &syn::Ident,
+ vmstate_fields: Option<syn::Expr>,
+ vmstate_subsections: Option<syn::Expr>,
+) -> (syn::Ident, proc_macro2::TokenStream) {
+ let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
+
+ let pre_load = format_ident!("__{}_pre_load_generated", name);
+ let post_load = format_ident!("__{}_post_load_generated", name);
+ let pre_save = format_ident!("__{}_pre_save_generated", name);
+ let post_save = format_ident!("__{}_post_save_generated", name);
+ let needed = format_ident!("__{}_needed_generated", name);
+ let dev_unplug_pending = format_ident!("__{}_dev_unplug_pending_generated", name);
+
+ let migrateable_fish = quote! {<#name as ::qemu_api::objects::Migrateable>};
+ let vmstate_fields = if let Some(fields) = vmstate_fields {
+ quote! {
+ #fields
+ }
+ } else {
+ quote! {
+ ::core::ptr::null()
+ }
+ };
+ let vmstate_subsections = if let Some(subsections) = vmstate_subsections {
+ quote! {
+ #subsections
+ }
+ } else {
+ quote! {
+ ::core::ptr::null()
+ }
+ };
+
+ let vmstate_description = quote! {
+ #[used]
+ #[allow(non_upper_case_globals)]
+ pub static #vmstate_description_ident: ::qemu_api::bindings::VMStateDescription = ::qemu_api::bindings::VMStateDescription {
+ name: if let Some(name) = #migrateable_fish::NAME {
+ name.as_ptr()
+ } else {
+ <#name as ::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
+ },
+ unmigratable: #migrateable_fish::UNMIGRATABLE,
+ early_setup: #migrateable_fish::EARLY_SETUP,
+ version_id: #migrateable_fish::VERSION_ID,
+ minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
+ priority: #migrateable_fish::PRIORITY,
+ pre_load: Some(#pre_load),
+ post_load: Some(#post_load),
+ pre_save: Some(#pre_save),
+ post_save: Some(#post_save),
+ needed: Some(#needed),
+ dev_unplug_pending: Some(#dev_unplug_pending),
+ fields: #vmstate_fields,
+ subsections: #vmstate_subsections,
+ };
+
+ #[no_mangle]
+ pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void, version_id: core::ffi::c_int) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_load(instance.as_mut(), version_id)
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) -> bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::needed(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut ::core::ffi::c_void) -> bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
+ }
+ }
+ };
+
+ let expanded = quote! {
+ #vmstate_description
+ };
+ (vmstate_description_ident, expanded)
+}
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 0bd70b59afcc005251135802897954789b068e6e..11984abb878bef18be3c819f61da24ce1405ea59 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -4,6 +4,7 @@ _qemu_api_rs = static_library(
[
'src/lib.rs',
'src/objects.rs',
+ 'src/vmstate.rs',
],
{'.' : bindings_rs},
),
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index b94adc15288cdc62de7679988f549ebd80f895d7..d276adfb6622eee6e42494e089e1f20b0b5cdf08 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -26,8 +26,11 @@ unsafe impl Send for bindings::Property {}
unsafe impl Sync for bindings::Property {}
unsafe impl Sync for bindings::TypeInfo {}
unsafe impl Sync for bindings::VMStateDescription {}
+unsafe impl Sync for bindings::VMStateField {}
+unsafe impl Sync for bindings::VMStateInfo {}
pub mod objects;
+pub mod vmstate;
use std::alloc::{GlobalAlloc, Layout};
diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4478febc9ac2768cca3e638ebae27b042edb1bf2
--- /dev/null
+++ b/rust/qemu-api/src/vmstate.rs
@@ -0,0 +1,403 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Helper macros to declare migration state for device models.
+//!
+//! Some macros are direct equivalents to the C macros declared in `include/migration/vmstate.h`
+//! while [`vmstate_description`], [`vmstate_subsections`] and [`vmstate_fields`] are meant to be
+//! used when declaring a device model state struct with the [`Device`](qemu_api_macros::Device)
+//! `Derive` macro.
+
+#[doc(alias = "VMSTATE_UNUSED_BUFFER")]
+#[macro_export]
+macro_rules! vmstate_unused_buffer {
+ ($field_exists_fn:expr, $version_id:expr, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: c"unused".as_ptr(),
+ err_hint: ::core::ptr::null(),
+ offset: 0,
+ size: $size,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) },
+ flags: VMStateFlags::VMS_BUFFER,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: $field_exists_fn,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED_V")]
+#[macro_export]
+macro_rules! vmstate_unused_v {
+ ($version_id:expr, $size:expr) => {{
+ $crate::vmstate_unused_buffer!(None, $version_id, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED")]
+#[macro_export]
+macro_rules! vmstate_unused {
+ ($size:expr) => {{
+ $crate::vmstate_unused_v!(0, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE_TEST")]
+#[macro_export]
+macro_rules! vmstate_single_test {
+ ($field_name:ident, $struct_name:ty, $field_exists_fn:expr, $version_id:expr, $info:block, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::core::ffi::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: ::core::mem::offset_of!($struct_name, $field_name) as _,
+ size: $size,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: $info,
+ flags: VMStateFlags::VMS_SINGLE,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: $field_exists_fn,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE")]
+#[macro_export]
+macro_rules! vmstate_single {
+ ($field_name:ident, $struct_name:ty, $version_id:expr, $info:block, $size:expr) => {{
+ $crate::vmstate_single_test!($field_name, $struct_name, None, $version_id, $info, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_single!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32) } },
+ u32::BITS as u64
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32")]
+#[macro_export]
+macro_rules! vmstate_uint32 {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_uint32_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_INT32_V")]
+#[macro_export]
+macro_rules! vmstate_int32_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_single!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_int32) } },
+ i32::BITS as u64
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_INT32")]
+#[macro_export]
+macro_rules! vmstate_int32 {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_int32_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_array {
+ ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr, $info:block, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::core::ffi::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: ::core::mem::offset_of!($struct_name, $field_name) as _,
+ size: $size,
+ start: 0,
+ num: $length as _,
+ num_offset: 0,
+ size_offset: 0,
+ info: $info,
+ flags: VMStateFlags::VMS_ARRAY,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_array_v {
+ ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr) => {{
+ $crate::vmstate_array!(
+ $field_name,
+ $struct_name,
+ $length,
+ $version_id,
+ { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32) } },
+ u32::BITS as u64
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_uint32_array {
+ ($field_name:ident, $struct_name:ty, $length:expr) => {{
+ $crate::vmstate_uint32_array_v!($field_name, $struct_name, $length, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_STRUCT_POINTER_V")]
+#[macro_export]
+macro_rules! vmstate_struct_pointer_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr, $vmsd:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::core::ffi::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: ::core::mem::offset_of!($struct_name, $field_name) as _,
+ size: ::core::mem::size_of::<*const $type>() as _,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: ::core::ptr::null(),
+ flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0),
+ vmsd: $vmsd,
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $info:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::core::ffi::c_char,
+ version_id: $version_id,
+ num: $num,
+ info: $info,
+ size: ::core::mem::size_of::<*const $type>() as _,
+ flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0),
+ offset: ::core::mem::offset_of!($struct_name, $field_name) as _,
+ err_hint: ::core::ptr::null(),
+ start: 0,
+ num_offset: 0,
+ size_offset: 0,
+ vmsd: ::core::ptr::null(),
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER_TO_STRUCT")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer_to_struct {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $vmsd:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::core::ffi::c_char,
+ version_id: $version_id,
+ num: $num,
+ vmsd: $vmsd,
+ size: ::core::mem::size_of::<*const $type>() as _,
+ flags: VMStateFlags(
+ VMStateFlags::VMS_ARRAY.0
+ | VMStateFlags::VMS_STRUCT.0
+ | VMStateFlags::VMS_ARRAY_OF_POINTER.0,
+ ),
+ offset: ::core::mem::offset_of!($struct_name, $field_name) as _,
+ err_hint: ::core::ptr::null(),
+ start: 0,
+ num_offset: 0,
+ size_offset: 0,
+ vmsd: ::core::ptr::null(),
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_clock_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_struct_pointer_v!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) } },
+ $crate::bindings::Clock
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_clock {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_clock_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_array_clock_v {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr) => {{
+ $crate::vmstate_array_of_pointer_to_struct!(
+ $field_name,
+ $struct_name,
+ $num,
+ $version_id,
+ { unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) } },
+ $crate::bindings::Clock
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_array_clock {
+ ($field_name:ident, $struct_name:ty, $num:expr) => {{
+ $crate::vmstate_array_clock_v!($field_name, $struct_name, $name, 0)
+ }};
+}
+
+/// Helper macro to declare a list of
+/// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and return a
+/// pointer to the array of values it created.
+#[macro_export]
+macro_rules! vmstate_fields {
+ ($($field:expr),*$(,)*) => {{
+ #[used]
+ static _FIELDS: &[$crate::bindings::VMStateField] = &[
+ $($field),*,
+ $crate::bindings::VMStateField {
+ name: ::core::ptr::null(),
+ err_hint: ::core::ptr::null(),
+ offset: 0,
+ size: 0,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: ::core::ptr::null(),
+ flags: VMStateFlags::VMS_END,
+ vmsd: ::core::ptr::null(),
+ version_id: 0,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ ];
+ _FIELDS.as_ptr()
+ }}
+}
+
+/// A transparent wrapper type for the `subsections` field of
+/// [`VMStateDescription`](crate::bindings::VMStateDescription).
+///
+/// This is necessary to be able to declare subsection descriptions as statics, because the only
+/// way to implement `Sync` for a foreign type (and `*const` pointers are foreign types in Rust) is
+/// to create a wrapper struct and `unsafe impl Sync` for it.
+///
+/// This struct is used in the [`vmstate_subsections`] macro implementation.
+#[repr(transparent)]
+pub struct VMStateSubsectionsWrapper(pub &'static [*const crate::bindings::VMStateDescription]);
+
+unsafe impl Sync for VMStateSubsectionsWrapper {}
+
+/// Helper macro to declare a list of subsections
+/// ([`VMStateDescription`](`crate::bindings::VMStateDescription`)) into a static and return a
+/// pointer to the array of pointers it created.
+#[macro_export]
+macro_rules! vmstate_subsections {
+ ($($subsection:expr),*$(,)*) => {{
+ #[used]
+ static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = $crate::vmstate::VMStateSubsectionsWrapper(&[
+ $({
+ #[used]
+ static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection;
+ ::core::ptr::addr_of!(_SUBSECTION)
+ }),*,
+ ::core::ptr::null()
+ ]);
+ _SUBSECTIONS.0.as_ptr()
+ }}
+}
+
+/// Thin macro to declare a valid [`VMStateDescription`](`crate::bindings::VMStateDescription`)
+/// static.
+#[macro_export]
+macro_rules! vmstate_description {
+ ($(#[$outer:meta])*
+ pub static $name:ident: VMStateDescription = VMStateDescription {
+ name: $vname:expr,
+ unmigratable: $um_val:expr,
+ early_setup: $early_setup:expr,
+ version_id: $version_id:expr,
+ minimum_version_id: $minimum_version_id:expr,
+ priority: $priority:expr,
+ pre_load: $pre_load_fn:expr,
+ post_load: $post_load_fn:expr,
+ pre_save: $pre_save_fn:expr,
+ post_save: $post_save_fn:expr,
+ needed: $needed_fn:expr,
+ dev_unplug_pending: $dev_unplug_pending_fn:expr,
+ fields: $fields:expr,
+ subsections: $subsections:expr$(,)*
+ };
+ ) => {
+ #[used]
+ $(#[$outer])*
+ pub static $name: $crate::bindings::VMStateDescription = $crate::bindings::VMStateDescription {
+ name: ::core::ffi::CStr::as_ptr($vname),
+ unmigratable: $um_val,
+ early_setup: $early_setup,
+ version_id: $version_id,
+ minimum_version_id: $minimum_version_id,
+ priority: $priority,
+ pre_load: None,
+ post_load: None,
+ pre_save: None,
+ post_save: None,
+ needed: None,
+ dev_unplug_pending: None,
+ fields: $fields,
+ subsections: $subsections,
+ };
+ }
+}
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 05/11] rust/pl011: move CLK_NAME static to function scope
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (3 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 04/11] rust: add support for migration in device models Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device Manos Pitsidianakis
` (6 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
We do not need to have CLK_NAME public nor a static. No functional change.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 57dc37dadef631fbccfa3049a3d8701b4e62b5b3..115786f9fa7f03c16cd44462cb7df5623ba3a6d7 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -152,6 +152,8 @@ impl ObjectImpl for PL011State {
/// location/instance. All its fields are expected to hold unitialized
/// values with the sole exception of `parent_obj`.
unsafe fn instance_init(&mut self) {
+ const CLK_NAME: &CStr = c"clk";
+
let dev = addr_of_mut!(*self).cast::<DeviceState>();
// SAFETY:
//
@@ -255,9 +257,6 @@ unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
}
}
-#[used]
-pub static CLK_NAME: &CStr = c"clk";
-
impl PL011State {
pub fn read(
&mut self,
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (4 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 05/11] rust/pl011: move CLK_NAME static to function scope Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 17:27 ` Zhao Liu
2024-10-24 14:03 ` [PATCH 07/11] rust/pl011: move pub callback decl to local scope Manos Pitsidianakis
` (5 subsequent siblings)
11 siblings, 1 reply; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Add a device specialization for the Luminary UART device.
This commit adds a DeviceId enum that utilizes the Index trait to return
different bytes depending on what device id the UART has (Arm -default-
or Luminary)
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 59 ++++++++++++++++++++++++++++++++++++++--
rust/hw/char/pl011/src/lib.rs | 1 +
2 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 115786f9fa7f03c16cd44462cb7df5623ba3a6d7..3aa055dee4b10866a624505a9d05ef1ab8182dce 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -20,8 +20,6 @@
RegisterOffset,
};
-static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
-
/// Integer Baud Rate Divider, `UARTIBRD`
const IBRD_MASK: u32 = 0x3f;
@@ -64,6 +62,29 @@ extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool {
};
}
+#[derive(Clone, Copy, Debug)]
+enum DeviceId {
+ #[allow(dead_code)]
+ Arm = 0,
+ Luminary,
+}
+
+impl std::ops::Index<hwaddr> for DeviceId {
+ type Output = c_uchar;
+
+ fn index(&self, idx: hwaddr) -> &Self::Output {
+ match self {
+ Self::Arm => &Self::PL011_ID_ARM[idx as usize],
+ Self::Luminary => &Self::PL011_ID_LUMINARY[idx as usize],
+ }
+ }
+}
+
+impl DeviceId {
+ const PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
+ const PL011_ID_LUMINARY: [c_uchar; 8] = [0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1];
+}
+
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
#[device(
@@ -134,6 +155,8 @@ pub struct PL011State {
#[doc(alias = "migrate_clk")]
#[property(name = c"migrate-clk", qdev_prop = qdev_prop_bool)]
pub migrate_clock: bool,
+ /// The byte string that identifies the device.
+ device_id: DeviceId,
}
impl ObjectImpl for PL011State {
@@ -267,7 +290,7 @@ pub fn read(
std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
Err(v) if (0x3f8..0x400).contains(&v) => {
- u64::from(PL011_ID_ARM[((offset - 0xfe0) >> 2) as usize])
+ u64::from(self.device_id[(offset - 0xfe0) >> 2])
}
Err(_) => {
// qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
@@ -660,3 +683,33 @@ pub fn update(&self) {
dev
}
}
+
+#[repr(C)]
+#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
+/// PL011 Luminary device model.
+pub struct PL011Luminary {
+ parent_obj: PL011State,
+}
+
+impl ObjectImpl for PL011Luminary {
+ type Class = PL011LuminaryClass;
+
+ const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY;
+ const PARENT_TYPE_NAME: Option<&'static CStr> = Some(crate::TYPE_PL011);
+ const ABSTRACT: bool = false;
+
+ /// Initializes a pre-allocated, unitialized instance of `PL011Luminary`.
+ ///
+ /// # Safety
+ ///
+ /// `self` must point to a correctly sized and aligned location for the
+ /// `PL011Luminary` type. It must not be called more than once on the same
+ /// location/instance. All its fields are expected to hold unitialized
+ /// values with the sole exception of `parent_obj`.
+ unsafe fn instance_init(&mut self) {
+ self.parent_obj.device_id = DeviceId::Luminary;
+ }
+}
+
+impl DeviceImpl for PL011Luminary {}
+impl qemu_api::objects::Migrateable for PL011Luminary {}
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
index f4d9cce4b01f605cfcbec7ea87c8b2009d77ee52..5516e018b4bfebe5175c515e5aa4598f54b39dfc 100644
--- a/rust/hw/char/pl011/src/lib.rs
+++ b/rust/hw/char/pl011/src/lib.rs
@@ -45,6 +45,7 @@
pub mod memory_ops;
pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
+pub const TYPE_PL011_LUMINARY: &::core::ffi::CStr = c"pl011_luminary";
/// Offset of each register from the base memory address of the device.
///
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device
2024-10-24 14:03 ` [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device Manos Pitsidianakis
@ 2024-10-24 17:27 ` Zhao Liu
0 siblings, 0 replies; 25+ messages in thread
From: Zhao Liu @ 2024-10-24 17:27 UTC (permalink / raw)
To: Manos Pitsidianakis
Cc: qemu-devel, Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Kevin Wolf,
Richard Henderson, Gustavo Romero, Pierrick Bouvier
On Thu, Oct 24, 2024 at 05:03:04PM +0300, Manos Pitsidianakis wrote:
> Date: Thu, 24 Oct 2024 17:03:04 +0300
> From: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> Subject: [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device
> X-Mailer: b4 0.15-dev-12fc5
>
> Add a device specialization for the Luminary UART device.
>
> This commit adds a DeviceId enum that utilizes the Index trait to return
> different bytes depending on what device id the UART has (Arm -default-
> or Luminary)
>
> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> ---
> rust/hw/char/pl011/src/device.rs | 59 ++++++++++++++++++++++++++++++++++++++--
> rust/hw/char/pl011/src/lib.rs | 1 +
> 2 files changed, 57 insertions(+), 3 deletions(-)
>
This device type is required by qtest-arm/qom-test & qtest-aarch64/qom-test!
I will pick it up for testing on my side.
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 07/11] rust/pl011: move pub callback decl to local scope
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (5 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 08/11] rust/pl011: remove commented out C code Manos Pitsidianakis
` (4 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
extern "C" callbacks in instance_init() do not need to be public. Move
them to local function scope instead.
No functional change.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 104 +++++++++++++++++++--------------------
1 file changed, 50 insertions(+), 54 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 3aa055dee4b10866a624505a9d05ef1ab8182dce..75399fa6352916fa9cc24164af0ea2e20fe29399 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -219,6 +219,56 @@ unsafe fn instance_init(&mut self) {
impl DeviceImpl for PL011State {
fn realize(&mut self) {
+ /// # Safety
+ ///
+ /// We expect the FFI user of this function to pass a valid pointer, that has
+ /// the same size as [`PL011State`]. We also expect the device is
+ /// readable/writeable from one thread at any time.
+ unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_ref().can_receive().into()
+ }
+ }
+ /// # Safety
+ ///
+ /// We expect the FFI user of this function to pass a valid pointer, that has
+ /// the same size as [`PL011State`]. We also expect the device is
+ /// readable/writeable from one thread at any time.
+ ///
+ /// The buffer and size arguments must also be valid.
+ unsafe extern "C" fn pl011_receive(
+ opaque: *mut core::ffi::c_void,
+ buf: *const u8,
+ size: core::ffi::c_int,
+ ) {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ if state.as_ref().loopback_enabled() {
+ return;
+ }
+ if size > 0 {
+ debug_assert!(!buf.is_null());
+ state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
+ }
+ }
+ }
+
+ /// # Safety
+ ///
+ /// We expect the FFI user of this function to pass a valid pointer, that has
+ /// the same size as [`PL011State`]. We also expect the device is
+ /// readable/writeable from one thread at any time.
+ unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
+ unsafe {
+ debug_assert!(!opaque.is_null());
+ let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+ state.as_mut().event(event)
+ }
+ }
+
// SAFETY: self.char_backend has the correct size and alignment for a
// CharBackend object, and its callbacks are of the correct types.
unsafe {
@@ -611,60 +661,6 @@ pub fn update(&self) {
/// # Safety
///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
- unsafe {
- debug_assert!(!opaque.is_null());
- let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- state.as_ref().can_receive().into()
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-///
-/// The buffer and size arguments must also be valid.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_receive(
- opaque: *mut core::ffi::c_void,
- buf: *const u8,
- size: core::ffi::c_int,
-) {
- unsafe {
- debug_assert!(!opaque.is_null());
- let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- if state.as_ref().loopback_enabled() {
- return;
- }
- if size > 0 {
- debug_assert!(!buf.is_null());
- state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
- }
- }
-}
-
-/// # Safety
-///
-/// We expect the FFI user of this function to pass a valid pointer, that has
-/// the same size as [`PL011State`]. We also expect the device is
-/// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
- unsafe {
- debug_assert!(!opaque.is_null());
- let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
- state.as_mut().event(event)
- }
-}
-
-/// # Safety
-///
/// We expect the FFI user of this function to pass a valid pointer for `chr`.
#[no_mangle]
pub unsafe extern "C" fn pl011_create(
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 08/11] rust/pl011: remove commented out C code
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (6 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 07/11] rust/pl011: move pub callback decl to local scope Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 09/11] rust/pl011: Use correct masks for IBRD and FBRD Manos Pitsidianakis
` (3 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
This code juxtaposed what should be happening according to the C device
model but is not needed now that this has been reviewed (I hope) and its
validity checked against what the C device does (I hope, again).
No functional change.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 75399fa6352916fa9cc24164af0ea2e20fe29399..cf1964fecdfd6d9dae3378890aa8b515a1ddc036 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -347,7 +347,6 @@ pub fn read(
0
}
Ok(DR) => {
- // s->flags &= ~PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(false);
let c = self.read_fifo[self.read_pos];
if self.read_count > 0 {
@@ -355,11 +354,9 @@ pub fn read(
self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
}
if self.read_count == 0 {
- // self.flags |= PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(true);
}
if self.read_count + 1 == self.read_trigger {
- //self.int_level &= ~ INT_RX;
self.int_level &= !registers::INT_RX;
}
// Update error bits.
@@ -529,13 +526,6 @@ fn loopback_mdmctrl(&mut self) {
* dealt with here.
*/
- //fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
- // PL011_FLAG_DSR | PL011_FLAG_CTS);
- //fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
- //fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
- //fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
- //fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
- //
self.flags.set_ring_indicator(self.control.out_2());
self.flags.set_data_carrier_detect(self.control.out_1());
self.flags.set_clear_to_send(self.control.request_to_send());
@@ -546,10 +536,6 @@ fn loopback_mdmctrl(&mut self) {
let mut il = self.int_level;
il &= !Interrupt::MS;
- //il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
- //il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
- //il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
- //il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
if self.flags.data_set_ready() {
il |= Interrupt::DSR as u32;
@@ -622,10 +608,8 @@ pub fn put_fifo(&mut self, value: c_uint) {
let slot = (self.read_pos + self.read_count) & (depth - 1);
self.read_fifo[slot] = value;
self.read_count += 1;
- // s->flags &= ~PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(false);
if self.read_count == depth {
- //s->flags |= PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(true);
}
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 09/11] rust/pl011: Use correct masks for IBRD and FBRD
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (7 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 08/11] rust/pl011: remove commented out C code Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 10/11] rust/qemu-api: add log module Manos Pitsidianakis
` (2 subsequent siblings)
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Port fix from commit cd247eae16ab1b9ce97fd34c000c1b883feeda45
"hw/char/pl011: Use correct masks for IBRD and FBRD"
Related issue: <https://gitlab.com/qemu-project/qemu/-/issues/2610>
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index cf1964fecdfd6d9dae3378890aa8b515a1ddc036..6d1353dafc14bfe73703b5cff7e1ff7659de220e 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -21,10 +21,10 @@
};
/// Integer Baud Rate Divider, `UARTIBRD`
-const IBRD_MASK: u32 = 0x3f;
+const IBRD_MASK: u32 = 0xffff;
/// Fractional Baud Rate Divider, `UARTFBRD`
-const FBRD_MASK: u32 = 0xffff;
+const FBRD_MASK: u32 = 0x3f;
const DATA_BREAK: u32 = 1 << 10;
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 10/11] rust/qemu-api: add log module
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (8 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 09/11] rust/pl011: Use correct masks for IBRD and FBRD Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 11/11] rust/pl011: log guest/unimp errors Manos Pitsidianakis
2024-10-25 9:33 ` [PATCH 00/11] Rust device model patches and misc cleanups Paolo Bonzini
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Logging in QEMU is done by function-like C macros, which we cannot use
directly from Rust with bindgen.
This commit adds a new qemu_api module, `log`, that provides the
following interfaces:
- a `LogMask` enum type that uses the mask values from the generated
bindings, and makes sure the rust enum variant names and values will
match.
- `LogMask` aliases `LogMask::GUEST_ERROR` and `LogMask::UNIMPLEMENTED`
for convenience.
- a private qemu_loglevel_mask() function, counterpart of
`qemu_loglevel_mask` in `include/qemu/log-for-trace.h`, which we
cannot use from bindgen since it's a static inline item.
- public qemu_log(), qemu_log_mask() and qemu_log_mask_and_addr()
functions that should act like the C equivalent:
pub fn qemu_log_mask_and_addr(log_mask: LogMask, address: u64, str: &str);
pub fn qemu_log_mask(log_mask: LogMask, str: &str);
pub fn qemu_log(str: &str);
It takes a 'static or allocated string slice as argument, but in the
feature we will introduce better log macros in Rust that make use of
Rust's format arguments. This is not really a bad compromise since
generating a log item is not a hot path so allocating here is fine.
Example usage will be:
```rust
qemu_log_mask(LogMask::GUEST_ERROR, "device XYZ failed spectacularly");
qemu_log_mask(
LogMask::UNIMPLEMENTED,
&format!(
"We haven't implemented this feature in {file}:{line} out of pure laziness.",
file = file!(),
line = line!()
)
);
```
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/wrapper.h | 1 +
rust/qemu-api/meson.build | 1 +
rust/qemu-api/src/lib.rs | 1 +
rust/qemu-api/src/log.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 143 insertions(+)
diff --git a/rust/wrapper.h b/rust/wrapper.h
index 77e40213efb686d23f6b768b78602e4337623280..8f76ef26f111d5e1f308268f445696acc7ddbfef 100644
--- a/rust/wrapper.h
+++ b/rust/wrapper.h
@@ -32,6 +32,7 @@
#include "qemu/osdep.h"
#include "qemu/module.h"
+#include "qemu/log.h"
#include "qemu-io.h"
#include "sysemu/sysemu.h"
#include "hw/sysbus.h"
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 11984abb878bef18be3c819f61da24ce1405ea59..a82ff0d39a2263d15bda312aa0a2d46c77b2501f 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -3,6 +3,7 @@ _qemu_api_rs = static_library(
structured_sources(
[
'src/lib.rs',
+ 'src/log.rs',
'src/objects.rs',
'src/vmstate.rs',
],
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index d276adfb6622eee6e42494e089e1f20b0b5cdf08..c3eb464f66361ee2349e636c49e38d3a6b57ad97 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -29,6 +29,7 @@ unsafe impl Sync for bindings::VMStateDescription {}
unsafe impl Sync for bindings::VMStateField {}
unsafe impl Sync for bindings::VMStateInfo {}
+pub mod log;
pub mod objects;
pub mod vmstate;
diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs
new file mode 100644
index 0000000000000000000000000000000000000000..50525ac6b7f49786c2975843b7dc70b91c18d5a0
--- /dev/null
+++ b/rust/qemu-api/src/log.rs
@@ -0,0 +1,140 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Logging functionality.
+//!
+//! This module provides:
+//!
+//! - a [`LogMask`] enum type that uses the mask values from the generated
+//! bindings, and makes sures the rust enum variant names and values will
+//! match.
+//! - [`LogMask`] aliases [`LogMask::GUEST_ERROR`] and [`LogMask::UNIMPLEMENTED`]
+//! for convenience.
+//! - a private `qemu_loglevel_mask()` function, counterpart of
+//! `qemu_loglevel_mask` in `include/qemu/log-for-trace.h`, which we
+//! cannot use from bindgen since it's a `static inline` item.
+//! - public [`qemu_log`], [`qemu_log_mask`] and [`qemu_log_mask_and_addr`] functions that act like
+//! the C equivalents.
+//!
+//! # Examples
+//!
+//! ```rust
+//! # use qemu_api::log::*;
+//! # fn main() {
+//! qemu_log_mask(LogMask::GUEST_ERROR, "device XYZ failed spectacularly");
+//!
+//! qemu_log_mask(
+//! LogMask::UNIMPLEMENTED,
+//! &format!(
+//! "We haven't implemented this feature in {file}:{line} out of pure laziness.",
+//! file = file!(),
+//! line = line!()
+//! )
+//! );
+//! # }
+//! ```
+
+use crate::bindings;
+
+macro_rules! mask_variants {
+ ($(#[$outer:meta])*
+ pub enum $name:ident {
+ $(
+ $(#[$attrs:meta])*
+ $symbol:ident
+ ),*$(,)*
+ }) => {
+ $(#[$outer])*
+ pub enum $name {
+ $(
+ $(#[$attrs])*
+ $symbol = bindings::$symbol
+ ),*
+ }
+ };
+}
+
+mask_variants! {
+ /// A wrapper type for the various log mask `#defines` in the C code base.
+ #[allow(non_camel_case_types)]
+ #[derive(Copy, Clone, Eq, PartialEq, Debug)]
+ #[repr(u32)]
+ pub enum LogMask {
+ CPU_LOG_TB_OUT_ASM,
+ CPU_LOG_TB_IN_ASM,
+ CPU_LOG_TB_OP,
+ CPU_LOG_TB_OP_OPT,
+ CPU_LOG_INT,
+ CPU_LOG_EXEC,
+ CPU_LOG_PCALL,
+ CPU_LOG_TB_CPU,
+ CPU_LOG_RESET,
+ LOG_UNIMP,
+ LOG_GUEST_ERROR,
+ CPU_LOG_MMU,
+ CPU_LOG_TB_NOCHAIN,
+ CPU_LOG_PAGE,
+ LOG_TRACE,
+ CPU_LOG_TB_OP_IND,
+ CPU_LOG_TB_FPU,
+ CPU_LOG_PLUGIN,
+ /// For user-mode strace logging.
+ LOG_STRACE,
+ LOG_PER_THREAD,
+ CPU_LOG_TB_VPU,
+ LOG_TB_OP_PLUGIN,
+ }
+}
+
+impl LogMask {
+ /// Alias.
+ pub const GUEST_ERROR: Self = LogMask::LOG_GUEST_ERROR;
+ /// Alias.
+ pub const UNIMPLEMENTED: Self = LogMask::LOG_UNIMP;
+}
+
+/// Returns `true` if a bit is set in the current loglevel mask.
+///
+/// Counterpart of `qemu_loglevel_mask` in `include/qemu/log-for-trace.h`.
+fn qemu_loglevel_mask(mask: LogMask) -> bool {
+ // SAFETY: This is an internal global variable. We only read from it and reading invalid values
+ // is not a concern here.
+ let current_level = unsafe { bindings::qemu_loglevel };
+ let mask = mask as ::core::ffi::c_int;
+
+ (current_level & mask) != 0
+}
+
+/// Log a message in QEMU's log, given a specific log mask.
+pub fn qemu_log_mask(log_mask: LogMask, str: &str) {
+ if qemu_loglevel_mask(log_mask) {
+ qemu_log(str);
+ }
+}
+
+/// Log a message in QEMU's log only if a bit is set on the current loglevel mask and we are in the
+/// address range we care about.
+pub fn qemu_log_mask_and_addr(log_mask: LogMask, address: u64, str: &str) {
+ if qemu_loglevel_mask(log_mask) && {
+ // SAFETY: This function reads global variables/system state but an error here is not a
+ // concern.
+ unsafe { bindings::qemu_log_in_addr_range(address) }
+ } {
+ qemu_log(str);
+ }
+}
+
+/// Log a message in QEMU's log, without a log mask.
+pub fn qemu_log(str: &str) {
+ let Ok(cstr) = ::std::ffi::CString::new(str) else {
+ panic!(
+ "qemu_log_mask: Converting passed string {:?} to CString failed.",
+ str
+ );
+ };
+ // SAFETY: We're passing two valid CStr pointers. The second argument for the variadic
+ // `qemu_log` function must be a `*const c_char` since the format specifier is `%s`.
+ // Therefore this is a safe call.
+ unsafe { bindings::qemu_log(c"%s\n".as_ptr(), cstr.as_ptr()) };
+}
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 11/11] rust/pl011: log guest/unimp errors
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (9 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 10/11] rust/qemu-api: add log module Manos Pitsidianakis
@ 2024-10-24 14:03 ` Manos Pitsidianakis
2024-10-25 9:33 ` [PATCH 00/11] Rust device model patches and misc cleanups Paolo Bonzini
11 siblings, 0 replies; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-24 14:03 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Philippe Mathieu-Daudé, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
Use the qemu_log_mask() functions introduced in previous commit to log
errors like the C pl011 device does.
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
---
rust/hw/char/pl011/src/device.rs | 37 +++++++++++++++++++++++++++++++------
1 file changed, 31 insertions(+), 6 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 6d1353dafc14bfe73703b5cff7e1ff7659de220e..57b38f44f90d74d0c1a94bb9144eff08db94fadf 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -9,6 +9,7 @@
use qemu_api::{
bindings::{self, *},
+ log::*,
objects::*,
vmstate_clock, vmstate_fields, vmstate_int32, vmstate_subsections, vmstate_uint32,
vmstate_uint32_array, vmstate_unused,
@@ -343,7 +344,14 @@ pub fn read(
u64::from(self.device_id[(offset - 0xfe0) >> 2])
}
Err(_) => {
- // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
+ qemu_log_mask(
+ LogMask::GUEST_ERROR,
+ &format!(
+ "pl011:{file}:{line}: Bad offset 0x{offset:x}",
+ file = file!(),
+ line = line!(),
+ ),
+ );
0
}
Ok(DR) => {
@@ -389,15 +397,30 @@ pub fn read(
}
pub fn write(&mut self, offset: hwaddr, value: u64) {
- // eprintln!("write offset {offset} value {value}");
use RegisterOffset::*;
let value: u32 = value as u32;
match RegisterOffset::try_from(offset) {
Err(_bad_offset) => {
- eprintln!("write bad offset {offset} value {value}");
+ qemu_log_mask(
+ LogMask::GUEST_ERROR,
+ &format!(
+ "pl011:{file}:{line}: Bad write offset 0x{offset:x} of value 0x:{value:x}",
+ file = file!(),
+ line = line!(),
+ ),
+ );
}
Ok(DR) => {
- // ??? Check if transmitter is enabled.
+ // Check if transmitter is enabled.
+ if !self.control.enable_uart() {
+ qemu_log_mask(LogMask::GUEST_ERROR, "PL011 data written to disabled UART");
+ }
+ if !self.control.enable_transmit() {
+ qemu_log_mask(
+ LogMask::GUEST_ERROR,
+ "PL011 data written to disabled TX UART",
+ );
+ }
let ch: u8 = value as u8;
// XXX this blocks entire thread. Rewrite to use
// qemu_chr_fe_write and background I/O callbacks
@@ -474,8 +497,10 @@ pub fn write(&mut self, offset: hwaddr, value: u64) {
Ok(DMACR) => {
self.dmacr = value;
if value & 3 > 0 {
- // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
- eprintln!("pl011: DMA not implemented");
+ qemu_log_mask(
+ LogMask::UNIMPLEMENTED,
+ "pl011: DMA functionality is not implemented",
+ );
}
}
}
--
2.45.2
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 00/11] Rust device model patches and misc cleanups
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
` (10 preceding siblings ...)
2024-10-24 14:03 ` [PATCH 11/11] rust/pl011: log guest/unimp errors Manos Pitsidianakis
@ 2024-10-25 9:33 ` Paolo Bonzini
2024-10-26 10:06 ` Manos Pitsidianakis
11 siblings, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-25 9:33 UTC (permalink / raw)
To: Manos Pitsidianakis, qemu-devel
Cc: Peter Maydell, Marc-André Lureau, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Alex Bennée, Thomas Huth,
Junjie Mao, Junjie Mao, Zhao Liu, Kevin Wolf, Richard Henderson,
Gustavo Romero, Pierrick Bouvier
On 10/24/24 16:02, Manos Pitsidianakis wrote:
> Hello everyone, the pathological corrosion of QEMU code continues.
> This series expands the device model harness work performed in the
> initial Rust work from the previous month. In particular:
Wow, there's a lot of stuff here!
The very good news is that it's basically all the code that is needed to
get CI running, after which we can start with the fun stuff. At the
same time, "the fun stuff" is also the one that risks introducing
technical debt, so we need to switch to quality-over-quantity mode and
have a serious design discussion about it. I'll do that later as a
reply to the patches.
> Code and functionality duplication is not fun, and pl011 was mostly
> done as a proof of concept for a Rust device because of its small
> complexity. As of this moment we have not decided on a policy for how
> to handle these things and it is not in **scope for this patch series
> anyway**.
That's fine.
Looking at the currently posted series, it seems that we have three main
themes:
1) small scale cleanups: duplicated and useless code, improved testing.
These are in
https://lore.kernel.org/qemu-devel/20241021163538.136941-1-pbonzini@redhat.com/T/
and they have been reviewed already. But these two:
> Revert "rust: add PL011 device model"
> rust: add PL011 device model
... should definitely be moved on top to clean up the authorship in "git
blame" and other similar tools. Sorry about that.
2) allow using older rustc/bindgen, extend CI to cover it. This is
https://lore.kernel.org/qemu-devel/20241022100956.196657-1-pbonzini@redhat.com/T/
which still needs review. These five:
> rust: add support for migration in device models
> rust/pl011: move CLK_NAME static to function scope
> rust/pl011: add TYPE_PL011_LUMINARY device
> rust/pl011: remove commented out C code
> rust/pl011: Use correct masks for IBRD and FBRD
(minus the usage of #[derive()] should be included in that series, so
that qtests pass. It's not a huge amount of work and I can extract it,
of course with proper attribution/authorship.
The rest are future work:
> rust/qemu-api-macros: introduce Device proc macro
This is useful as a starting point but it has the limit of being very
device-specific. This is of course okay with properties and vmstate,
but in my opinion the implementation of class_init should be as generic
as possible, and not too specialized for methods in Object or Device.
As I said above, we first need to agree on the design.
> rust/pl011: move pub callback decl to local scope
This depends a lot on how we go implementing bindings to chardev. For
example if the callbacks turn out to be a trait, it would have to be
undone. Possibly the C callback wrappers would move to
rust/qemu-api/chardev. For now I'd leave it aside.
> rust/qemu-api: add log module
> rust/pl011: log guest/unimp errors
This also needs design discussion. Do we want the API to be the same as
C, i.e. keep the qemu_* prefix? Do we want wrapper macros that include
the format!() call?
You also have "pub enum LogMask" to work around the fact that log masks
are preprocessor macros. Is that okay, or do we want to modify C code
to make the bindings nicer? I for example would prefer the latter and
then declaring LogMask as a bitfield in bindgen.
Thanks,
Paolo
>
> rust/wrapper.h | 1 +
> rust/hw/char/pl011/src/device.rs | 419 +++++++++++++++++---------
> rust/hw/char/pl011/src/device_class.rs | 70 -----
> rust/hw/char/pl011/src/lib.rs | 2 +-
> rust/qemu-api-macros/src/device.rs | 370 +++++++++++++++++++++++
> rust/qemu-api-macros/src/lib.rs | 46 +--
> rust/qemu-api-macros/src/object.rs | 107 +++++++
> rust/qemu-api-macros/src/symbols.rs | 57 ++++
> rust/qemu-api-macros/src/utilities.rs | 152 ++++++++++
> rust/qemu-api-macros/src/vmstate.rs | 113 +++++++
> rust/qemu-api/meson.build | 5 +-
> rust/qemu-api/src/definitions.rs | 97 ------
> rust/qemu-api/src/device_class.rs | 128 --------
> rust/qemu-api/src/lib.rs | 10 +-
> rust/qemu-api/src/log.rs | 140 +++++++++
> rust/qemu-api/src/objects.rs | 90 ++++++
> rust/qemu-api/src/tests.rs | 49 ---
> rust/qemu-api/src/vmstate.rs | 403 +++++++++++++++++++++++++
> subprojects/packagefiles/syn-2-rs/meson.build | 1 +
> 19 files changed, 1726 insertions(+), 534 deletions(-)
> ---
> base-commit: 55522f72149fbf95ee3b057f1419da0cad46d0dd
> change-id: 20241024-rust-round-2-69fa10c9a0c9
>
> --
> γαῖα πυρί μιχθήτω
>
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 00/11] Rust device model patches and misc cleanups
2024-10-25 9:33 ` [PATCH 00/11] Rust device model patches and misc cleanups Paolo Bonzini
@ 2024-10-26 10:06 ` Manos Pitsidianakis
2024-10-27 8:13 ` Paolo Bonzini
0 siblings, 1 reply; 25+ messages in thread
From: Manos Pitsidianakis @ 2024-10-26 10:06 UTC (permalink / raw)
To: Paolo Bonzini
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Richard Henderson, Gustavo Romero, Pierrick Bouvier
Hi Paolo,
Please reply with review comments underneath individual patches, this
is hard to follow and I might miss some points.
On Fri, 25 Oct 2024 at 12:33, Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> On 10/24/24 16:02, Manos Pitsidianakis wrote:
> > Hello everyone, the pathological corrosion of QEMU code continues.
> > This series expands the device model harness work performed in the
> > initial Rust work from the previous month. In particular:
>
> Wow, there's a lot of stuff here!
>
> The very good news is that it's basically all the code that is needed to
> get CI running, after which we can start with the fun stuff. At the
> same time, "the fun stuff" is also the one that risks introducing
> technical debt, so we need to switch to quality-over-quantity mode and
> have a serious design discussion about it. I'll do that later as a
> reply to the patches.
>
> > Code and functionality duplication is not fun, and pl011 was mostly
> > done as a proof of concept for a Rust device because of its small
> > complexity. As of this moment we have not decided on a policy for how
> > to handle these things and it is not in **scope for this patch series
> > anyway**.
>
> That's fine.
>
> Looking at the currently posted series, it seems that we have three main
> themes:
>
> 1) small scale cleanups: duplicated and useless code, improved testing.
> These are in
> https://lore.kernel.org/qemu-devel/20241021163538.136941-1-pbonzini@redhat.com/T/
> and they have been reviewed already. But these two:
>
> > Revert "rust: add PL011 device model"
> > rust: add PL011 device model
>
> ... should definitely be moved on top to clean up the authorship in "git
> blame" and other similar tools. Sorry about that.
I will send them on a separate series and merge them from my tree when reviewed.
>
> 2) allow using older rustc/bindgen, extend CI to cover it. This is
> https://lore.kernel.org/qemu-devel/20241022100956.196657-1-pbonzini@redhat.com/T/
> which still needs review. These five:
>
> > rust: add support for migration in device models
> > rust/pl011: move CLK_NAME static to function scope
> > rust/pl011: add TYPE_PL011_LUMINARY device
> > rust/pl011: remove commented out C code
> > rust/pl011: Use correct masks for IBRD and FBRD
>
> (minus the usage of #[derive()] should be included in that series, so
> that qtests pass. It's not a huge amount of work and I can extract it,
> of course with proper attribution/authorship.
These are independent from CI; i.e. you can merge your CI patches after those.
>
> The rest are future work:
>
> > rust/qemu-api-macros: introduce Device proc macro
>
> This is useful as a starting point but it has the limit of being very
> device-specific. This is of course okay with properties and vmstate,
> but in my opinion the implementation of class_init should be as generic
> as possible, and not too specialized for methods in Object or Device.
>
> As I said above, we first need to agree on the design.
Post your review under the patches please,
>
> > rust/pl011: move pub callback decl to local scope
>
> This depends a lot on how we go implementing bindings to chardev. For
> example if the callbacks turn out to be a trait, it would have to be
> undone. Possibly the C callback wrappers would move to
> rust/qemu-api/chardev. For now I'd leave it aside.
This patch moves the callbacks scope from public to inside the
function, no functional change related. It doesn't change or have
anything to do with chardev interfaces
>
> > rust/qemu-api: add log module
> > rust/pl011: log guest/unimp errors
>
> This also needs design discussion. Do we want the API to be the same as
> C, i.e. keep the qemu_* prefix? Do we want wrapper macros that include
> the format!() call?
I'm guessing you did not see the patch messages, which cover these
points. Post your review under the patches please,
>
> You also have "pub enum LogMask" to work around the fact that log masks
> are preprocessor macros. Is that okay, or do we want to modify C code
> to make the bindings nicer? I for example would prefer the latter and
> then declaring LogMask as a bitfield in bindgen.
A bindgen type is definitely preferred but for a Rust idiomatic
interface a wrapper type is a UX improvement (`CPU_LOG_PCALL`?
`LOG_GUEST_ERROR`? We can use friendlier symbols in the LogMask
variants for that)
Thanks, Manos
>
> Thanks,
>
> Paolo
>
> >
> > rust/wrapper.h | 1 +
> > rust/hw/char/pl011/src/device.rs | 419 +++++++++++++++++---------
> > rust/hw/char/pl011/src/device_class.rs | 70 -----
> > rust/hw/char/pl011/src/lib.rs | 2 +-
> > rust/qemu-api-macros/src/device.rs | 370 +++++++++++++++++++++++
> > rust/qemu-api-macros/src/lib.rs | 46 +--
> > rust/qemu-api-macros/src/object.rs | 107 +++++++
> > rust/qemu-api-macros/src/symbols.rs | 57 ++++
> > rust/qemu-api-macros/src/utilities.rs | 152 ++++++++++
> > rust/qemu-api-macros/src/vmstate.rs | 113 +++++++
> > rust/qemu-api/meson.build | 5 +-
> > rust/qemu-api/src/definitions.rs | 97 ------
> > rust/qemu-api/src/device_class.rs | 128 --------
> > rust/qemu-api/src/lib.rs | 10 +-
> > rust/qemu-api/src/log.rs | 140 +++++++++
> > rust/qemu-api/src/objects.rs | 90 ++++++
> > rust/qemu-api/src/tests.rs | 49 ---
> > rust/qemu-api/src/vmstate.rs | 403 +++++++++++++++++++++++++
> > subprojects/packagefiles/syn-2-rs/meson.build | 1 +
> > 19 files changed, 1726 insertions(+), 534 deletions(-)
> > ---
> > base-commit: 55522f72149fbf95ee3b057f1419da0cad46d0dd
> > change-id: 20241024-rust-round-2-69fa10c9a0c9
> >
> > --
> > γαῖα πυρί μιχθήτω
> >
> >
> >
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 00/11] Rust device model patches and misc cleanups
2024-10-26 10:06 ` Manos Pitsidianakis
@ 2024-10-27 8:13 ` Paolo Bonzini
0 siblings, 0 replies; 25+ messages in thread
From: Paolo Bonzini @ 2024-10-27 8:13 UTC (permalink / raw)
To: Manos Pitsidianakis
Cc: qemu-devel, Peter Maydell, Marc-André Lureau,
Daniel P. Berrangé, Philippe Mathieu-Daudé,
Alex Bennée, Thomas Huth, Junjie Mao, Junjie Mao, Zhao Liu,
Kevin Wolf, Richard Henderson, Gustavo Romero, Pierrick Bouvier
On Sat, Oct 26, 2024 at 12:06 PM Manos Pitsidianakis
<manos.pitsidianakis@linaro.org> wrote:
> Please reply with review comments underneath individual patches, this
> is hard to follow and I might miss some points.
Will do.
> > > Revert "rust: add PL011 device model"
> > > rust: add PL011 device model
> >
> > ... should definitely be moved on top to clean up the authorship in "git
> > blame" and other similar tools. Sorry about that.
>
> I will send them on a separate series and merge them from my tree when reviewed.
Those are trivial to review, just "git diff HEAD^^". No need to
separate them into a new submission.
> > > rust: add support for migration in device models
> > > rust/pl011: move CLK_NAME static to function scope
> > > rust/pl011: add TYPE_PL011_LUMINARY device
> > > rust/pl011: remove commented out C code
> > > rust/pl011: Use correct masks for IBRD and FBRD
> >
> > (minus the usage of #[derive()] should be included in that series, so
> > that qtests pass. It's not a huge amount of work and I can extract it,
> > of course with proper attribution/authorship.
>
> These are independent from CI; i.e. you can merge your CI patches after those.
That's what I did: I put them at the beginning of the series of
pending patches, so they _are_ indeed merged after the CI patches. The
only issue here is that patch 4 ("rust: add support for migration in
device models") was dependent on the Device proc macro, but that was
easy enough to extract.
> >
> > The rest are future work:
> >
> > > rust/qemu-api-macros: introduce Device proc macro
> >
> > As I said above, we first need to agree on the design.
>
> Post your review under the patches please,
Yep.
> > > rust/pl011: move pub callback decl to local scope
> >
> > This depends a lot on how we go implementing bindings to chardev. For
> > example if the callbacks turn out to be a trait, it would have to be
> > undone. Possibly the C callback wrappers would move to
> > rust/qemu-api/chardev. For now I'd leave it aside.
>
> This patch moves the callbacks scope from public to inside the
> function, no functional change related. It doesn't change or have
> anything to do with chardev interfaces
I understand. My point is that the callbacks you move
(pl011_can_receive, pl011_receive, pl011_event) in the end will belong
into the C<->Rust chardev bindings. A patch to remove "pub" would have
basically the same benefit without the churn in indentation.
> > > rust/qemu-api: add log module
> > > rust/pl011: log guest/unimp errors
> >
> > This also needs design discussion. Do we want the API to be the same as
> > C, i.e. keep the qemu_* prefix? Do we want wrapper macros that include
> > the format!() call?
>
> I'm guessing you did not see the patch messages, which cover these
> points. Post your review under the patches please,
I will but for now I'll say that the commit messages do not address
the questions I'm asking above.
More in general, we need to establish conventions on what the Rust
APIs look like (about qemu_* prefixes, for example). Personally I
prefer to have APIs that, while easy enough to connect to the C ones,
are idiomatic. But you can post these two patches separately and we
can discuss it there.
> > You also have "pub enum LogMask" to work around the fact that log masks
> > are preprocessor macros. Is that okay, or do we want to modify C code
> > to make the bindings nicer? I for example would prefer the latter and
> > then declaring LogMask as a bitfield in bindgen.
>
> A bindgen type is definitely preferred but for a Rust idiomatic
> interface a wrapper type is a UX improvement (`CPU_LOG_PCALL`?
> `LOG_GUEST_ERROR`? We can use friendlier symbols in the LogMask
> variants for that)
What I'm saying is that these are discussions where we need to reach
an agreement on, and document the choices.
Paolo
^ permalink raw reply [flat|nested] 25+ messages in thread