From: Jack Wang <163wangjack@gmail.com>
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>,
Peter Maydell <peter.maydell@linaro.org>,
Alistair Francis <alistair@alistair23.me>,
Fabiano Rosas <farosas@suse.de>,
Laurent Vivier <lvivier@redhat.com>,
qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com,
Jack Wang <163wangjack@gmail.com>
Subject: [RFC PATCH v1 1/5] hw/gpio: add stm32f1xx GPIO device model (Rust)
Date: Sun, 26 Jul 2026 22:51:15 +0800 [thread overview]
Message-ID: <20260726145119.19881-2-163wangjack@gmail.com> (raw)
In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com>
Add a native-Rust model of the STM32F1xx GPIO port (RM0008). The device
implements the CRL/CRH mode/config decode and the IDR update algorithm,
driving its 16 gpio-out lines from ODR according to each pin's push-pull,
open-drain or input pull configuration.
The pure register logic lives in GpioRegisters with no QOM/MemoryRegion
coupling so it can be unit-tested natively.
Signed-off-by: Jack Wang <163wangjack@gmail.com>
---
MAINTAINERS | 7 +
hw/gpio/Kconfig | 5 +
hw/gpio/trace-events | 7 +
rust/hw/gpio/Kconfig | 2 +
rust/hw/gpio/src/bindings.rs | 19 ++
rust/hw/gpio/src/gpio.rs | 539 +++++++++++++++++++++++++++++++++++
rust/hw/gpio/src/lib.rs | 3 +
7 files changed, 582 insertions(+)
create mode 100644 rust/hw/gpio/Kconfig
create mode 100644 rust/hw/gpio/src/bindings.rs
create mode 100644 rust/hw/gpio/src/gpio.rs
create mode 100644 rust/hw/gpio/src/lib.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 72357df7c4..4870da54ab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1184,6 +1184,13 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/arm/stm32f100_soc.c
+STM32F1xx GPIO (Rust)
+M: Jack Wang <163wangjack@gmail.com>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: rust/hw/gpio/
+F: tests/qtest/stm32f103_gpio-test.c
+
STM32F205
M: Alistair Francis <alistair@alistair23.me>
M: Peter Maydell <peter.maydell@linaro.org>
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index a209294c20..3762135114 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -16,6 +16,11 @@ config SIFIVE_GPIO
config STM32L4X5_GPIO
bool
+config STM32F1XX_GPIO
+ bool
+ depends on HAVE_RUST
+ select X_STM32F1XX_GPIO_RUST
+
config PCA9552
bool
depends on I2C
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index cea896b28f..c77c6e24a3 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -46,3 +46,10 @@ stm32l4x5_gpio_read(char *gpio, uint64_t addr) "GPIO%s addr: 0x%" PRIx64 " "
stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
+
+# stm32f1xx GPIO Rust device
+stm32f1xx_gpio_instance_init(void) "instance init"
+stm32f1xx_gpio_reset(void) "reset"
+stm32f1xx_gpio_read(uint64_t offset, uint64_t value, unsigned size) "offset 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
+stm32f1xx_gpio_write(uint64_t offset, uint64_t value, unsigned size) "offset 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
+stm32f1xx_gpio_irq(unsigned line, int level) "line %u level %d"
diff --git a/rust/hw/gpio/Kconfig b/rust/hw/gpio/Kconfig
new file mode 100644
index 0000000000..1f2bc9e0e4
--- /dev/null
+++ b/rust/hw/gpio/Kconfig
@@ -0,0 +1,2 @@
+config X_STM32F1XX_GPIO_RUST
+ bool
diff --git a/rust/hw/gpio/src/bindings.rs b/rust/hw/gpio/src/bindings.rs
new file mode 100644
index 0000000000..b0189065b4
--- /dev/null
+++ b/rust/hw/gpio/src/bindings.rs
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#![allow(
+ dead_code,
+ improper_ctypes_definitions,
+ improper_ctypes,
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals,
+ unnecessary_transmutes,
+ unsafe_op_in_unsafe_fn,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::style,
+ clippy::missing_const_for_fn,
+ clippy::ptr_offset_with_cast,
+ clippy::useless_transmute,
+ clippy::missing_safety_doc,
+ clippy::too_many_arguments
+)]
diff --git a/rust/hw/gpio/src/gpio.rs b/rust/hw/gpio/src/gpio.rs
new file mode 100644
index 0000000000..a8197d003d
--- /dev/null
+++ b/rust/hw/gpio/src/gpio.rs
@@ -0,0 +1,539 @@
+// STM32F1xx GPIO port device model (Rust)
+// Copyright 2026, Jack Wang
+// Author(s): Jack Wang <163wangjack@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Reference: ST RM0008, GPIO and AFIO registers.
+// The register model deliberately follows the C stm32l4x5_gpio.c IDR
+// algorithm, adapted to the F1 CRL/CRH (MODE[1:0]+CNF[1:0]) layout.
+//
+// The register state and all of its logic live in `GpioRegisters`, a plain
+// struct with no QOM/MMIO/IRQ coupling. `Stm32f1xxGpioState` is a thin QOM
+// device shell that owns a `GpioRegisters` plus the GPIO-out lines, forwards
+// MMIO/pin callbacks into it, and propagates the resulting pin-level changes
+// onto the out lines. Keeping the logic free of C dependencies lets the unit
+// tests in tests/tests.rs exercise it natively, without booting a machine.
+
+use std::ffi::CStr;
+
+use bql::prelude::*;
+use common::prelude::*;
+use hwcore::prelude::*;
+use migration::prelude::*;
+use qom::prelude::*;
+use system::prelude::*;
+use util::prelude::*;
+
+pub const GPIO_NUM_PINS: usize = 16;
+
+pub const TYPE_STM32F1XX_GPIO_RUST: &CStr = c"stm32f1xx-gpio-rust";
+qom_isa!(Stm32f1xxGpioState: SysBusDevice, DeviceState, Object);
+
+unsafe impl ObjectType for Stm32f1xxGpioState {
+ type Class = <SysBusDevice as ObjectType>::Class;
+ const TYPE_NAME: &'static CStr = TYPE_STM32F1XX_GPIO_RUST;
+}
+
+// Trace bindings are generated from hw/gpio/trace-events (group "hw_gpio").
+// Each `stm32f1xx_gpio_<name>` event yields a
+// `trace::trace_stm32f1xx_gpio_<name>` function whose argument types match the
+// trace-events declaration verbatim.
+::trace::include_trace!("hw_gpio");
+
+/// Register offsets (RM0008), shared by the device and its tests.
+pub const REG_CRL: u64 = 0x00;
+pub const REG_CRH: u64 = 0x04;
+pub const REG_IDR: u64 = 0x08;
+pub const REG_ODR: u64 = 0x0C;
+pub const REG_BSRR: u64 = 0x10;
+pub const REG_BRR: u64 = 0x14;
+pub const REG_LCKR: u64 = 0x18;
+
+/// RM0008 reset values: CRL/CRH = 0x44444444 (all pins floating input).
+pub const RESET_CRL: u32 = 0x4444_4444;
+pub const RESET_CRH: u32 = 0x4444_4444;
+
+/// Pure register state + behaviour of one GPIO port.
+///
+/// This holds no QOM object, no `MemoryRegion` and no `InterruptSource`, so it
+/// can be constructed and driven directly from unit tests. Methods that can
+/// change pin levels return a [`PinUpdate`] describing which output lines
+/// flipped; the QOM shell turns those into `InterruptSource` edges.
+#[repr(C)]
+#[derive(Debug)]
+pub struct GpioRegisters {
+ crl: BqlCell<u32>,
+ crh: BqlCell<u32>,
+ idr: BqlCell<u32>,
+ odr: BqlCell<u32>,
+ lckr: BqlCell<u32>,
+
+ // Bit i set => pin i is not driven by an external source.
+ // Bit i clear => pin i is driven, and `pins_connected_high` bit i
+ // gives the level driven.
+ disconnected_pins: BqlCell<u16>,
+ pins_connected_high: BqlCell<u16>,
+}
+
+impl Default for GpioRegisters {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Which output pins changed level as a result of an operation.
+///
+/// `mask` bit i set means pin i has a determined level in `levels` bit i and
+/// that level differs from before the operation.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct PinUpdate {
+ pub mask: u32,
+ pub levels: u32,
+}
+
+impl GpioRegisters {
+ pub const fn new() -> Self {
+ Self {
+ crl: BqlCell::new(RESET_CRL),
+ crh: BqlCell::new(RESET_CRH),
+ idr: BqlCell::new(0),
+ odr: BqlCell::new(0),
+ lckr: BqlCell::new(0),
+ disconnected_pins: BqlCell::new(0xFFFF),
+ pins_connected_high: BqlCell::new(0),
+ }
+ }
+
+ // ---- register accessors (for tests / vmstate / shell) ----
+
+ pub fn idr(&self) -> u32 {
+ self.idr.get()
+ }
+ pub fn odr(&self) -> u32 {
+ self.odr.get()
+ }
+ pub fn crl(&self) -> u32 {
+ self.crl.get()
+ }
+ pub fn crh(&self) -> u32 {
+ self.crh.get()
+ }
+ pub fn disconnected_pins(&self) -> u16 {
+ self.disconnected_pins.get()
+ }
+
+ // ---- pin configuration helpers (decode CRL/CRH nibbles) ----
+
+ fn extract(value: u32, start: u32, length: u32) -> u32 {
+ assert!(length > 0 && length <= 32 - start);
+ (value >> start) & (!0u32 >> (32 - length))
+ }
+
+ // 4-bit MODE[1:0]+CNF[1:0] field for `pin`.
+ fn get_modecnf(&self, pin: usize) -> u8 {
+ let reg = if pin < 8 {
+ self.crl.get()
+ } else {
+ self.crh.get()
+ };
+ let shift = ((pin % 8) * 4) as u32;
+ Self::extract(reg, shift, 4) as u8
+ }
+
+ // MODE != 0 means output (any speed).
+ fn is_output(&self, pin: usize) -> bool {
+ (self.get_modecnf(pin) & 0x03) != 0
+ }
+
+ fn is_alternate_open_drain(&self, pin: usize) -> bool {
+ let modecnf = self.get_modecnf(pin);
+ ((modecnf & 0x03) != 0) && ((modecnf >> 2) == 0x03)
+ }
+
+ fn is_alternate_push_pull(&self, pin: usize) -> bool {
+ let modecnf = self.get_modecnf(pin);
+ ((modecnf & 0x03) != 0) && ((modecnf >> 2) == 0x02)
+ }
+
+ fn is_general_push_pull(&self, pin: usize) -> bool {
+ let modecnf = self.get_modecnf(pin);
+ ((modecnf & 0x03) != 0) && ((modecnf >> 2) == 0x00)
+ }
+
+ fn is_general_open_drain(&self, pin: usize) -> bool {
+ let modecnf = self.get_modecnf(pin);
+ ((modecnf & 0x03) != 0) && ((modecnf >> 2) == 0x01)
+ }
+
+ // Input with pull-up/pull-down selected: MODE=00, CNF=10 (0b1000).
+ fn is_input_pull_up_down_mode(&self, pin: usize) -> bool {
+ self.get_modecnf(pin) == 0b1000
+ }
+
+ // In pull mode, ODR bit selects pull-up (1) vs pull-down (0).
+ fn is_input_pullup(&self, pin: usize) -> bool {
+ self.is_input_pull_up_down_mode(pin) && ((self.odr.get() & (1 << pin)) != 0)
+ }
+
+ fn is_input_pulldown(&self, pin: usize) -> bool {
+ self.is_input_pull_up_down_mode(pin) && ((self.odr.get() & (1 << pin)) == 0)
+ }
+
+ fn check_and_warn_af(&self, start_pin: usize) {
+ for i in 0..8 {
+ let pin = start_pin + i;
+ if self.is_alternate_push_pull(pin) || self.is_alternate_open_drain(pin) {
+ log_mask_ln!(
+ Log::Unimp,
+ "stm32f1xx_gpio: AF mode configured on pin {}. Downgrading to General Output \
+ to keep simulation running.",
+ pin
+ );
+ }
+ }
+ }
+
+ // ---- operations (all return which out pins changed) ----
+
+ /// Restore the RM0008 documented reset state.
+ pub fn reset(&self) -> PinUpdate {
+ self.crl.set(RESET_CRL);
+ self.crh.set(RESET_CRH);
+ self.idr.set(0);
+ self.odr.set(0);
+ self.lckr.set(0);
+ self.disconnected_pins.set(0xFFFF);
+ self.pins_connected_high.set(0);
+ self.update_gpio_idr()
+ }
+
+ /// External source drives pin `line` to `level`.
+ pub fn gpio_set(&self, line: usize, level: u32) -> PinUpdate {
+ // A push-pull output (or an open-drain output driven high) cannot be
+ // driven externally without a short-circuit; refuse it. An open-drain
+ // output driven low is allowed.
+ if self.is_output(line)
+ && !((self.is_alternate_open_drain(line) || self.is_general_open_drain(line))
+ && (level == 0))
+ {
+ log_mask_ln!(
+ Log::GuestError,
+ "stm32f1xx_gpio: Line {} can't be driven externally",
+ line
+ );
+ return PinUpdate::default();
+ }
+
+ self.disconnected_pins
+ .set(self.disconnected_pins.get() & !(1 << line));
+
+ if level != 0 {
+ self.pins_connected_high
+ .set(self.pins_connected_high.get() | (1 << line));
+ } else {
+ self.pins_connected_high
+ .set(self.pins_connected_high.get() & !(1 << line));
+ }
+
+ self.update_gpio_idr()
+ }
+
+ // Return mask of pins that are both configured in output mode and
+ // externally driven (except open-drain pins externally set to 0), which
+ // must be disconnected to avoid a short circuit.
+ fn get_gpio_pinmask_to_disconnect(&self) -> u16 {
+ let mut pins_to_disconnect: u16 = 0;
+ for i in 0..GPIO_NUM_PINS {
+ if (self.disconnected_pins.get() & (1 << i) == 0) && self.is_output(i) {
+ if self.is_general_push_pull(i)
+ || self.is_alternate_push_pull(i)
+ || (self.pins_connected_high.get() & (1 << i) != 0)
+ {
+ pins_to_disconnect |= 1 << i;
+ log_mask_ln!(
+ Log::GuestError,
+ "stm32f1xx_gpio: Line {} can't be driven externally",
+ i
+ );
+ }
+ }
+ }
+ pins_to_disconnect
+ }
+
+ fn disconnect_gpio_pins(&self, lines: u16) -> PinUpdate {
+ self.disconnected_pins
+ .set(self.disconnected_pins.get() | lines);
+ self.update_gpio_idr()
+ }
+
+ /// MMIO write. Returns the resulting output-pin changes.
+ pub fn write(&self, offset: u64, data: u32) -> PinUpdate {
+ match offset {
+ REG_CRL => {
+ self.crl.set(data);
+ self.check_and_warn_af(0);
+ // A pin reconfigured to output can no longer be driven.
+ self.disconnect_gpio_pins(self.get_gpio_pinmask_to_disconnect())
+ }
+ REG_CRH => {
+ self.crh.set(data);
+ self.check_and_warn_af(8);
+ self.disconnect_gpio_pins(self.get_gpio_pinmask_to_disconnect())
+ }
+ REG_ODR => {
+ self.odr.set(data & 0xFFFF);
+ self.update_gpio_idr()
+ }
+ REG_BRR => {
+ let bits_to_reset = data & 0xFFFF;
+ self.odr.set(self.odr.get() & !bits_to_reset);
+ self.update_gpio_idr()
+ }
+ REG_BSRR => {
+ let bits_to_set = data & 0xFFFF;
+ let bits_to_reset = (data >> 16) & 0xFFFF;
+
+ // If both BSx and BRx are set, BSx has priority.
+ let mut current_odr = self.odr.get();
+ current_odr &= !bits_to_reset;
+ current_odr |= bits_to_set;
+ self.odr.set(current_odr);
+
+ self.update_gpio_idr()
+ }
+ REG_LCKR => {
+ self.lckr.set(data);
+ log_mask_ln!(
+ Log::Unimp,
+ "stm32f1xx_gpio: Locking port bits configuration isn't supported"
+ );
+ PinUpdate::default()
+ }
+ _ => {
+ log_mask_ln!(
+ Log::GuestError,
+ "stm32f1xx_gpio_write: Bad offset 0x{:x}",
+ offset
+ );
+ PinUpdate::default()
+ }
+ }
+ }
+
+ /// MMIO read.
+ pub fn read(&self, offset: u64) -> u32 {
+ match offset {
+ REG_CRL => self.crl.get(),
+ REG_CRH => self.crh.get(),
+ REG_IDR => self.idr.get(),
+ REG_ODR => self.odr.get(),
+ REG_BSRR => 0,
+ REG_BRR => 0,
+ REG_LCKR => self.lckr.get(),
+ _ => {
+ log_mask_ln!(
+ Log::GuestError,
+ "stm32f1xx_gpio_read: Bad offset 0x{:x}",
+ offset
+ );
+ 0
+ }
+ }
+ }
+
+ /// Recompute IDR from the pin configuration and external drive state.
+ /// Returns which determined output pins changed level.
+ fn update_gpio_idr(&self) -> PinUpdate {
+ // new_idr_mask bit i => pin i has a determined level in new_idr.
+ let mut new_idr_mask = 0u32;
+ let mut new_idr = self.odr.get();
+ let old_idr = self.idr.get();
+
+ for i in 0..GPIO_NUM_PINS {
+ if self.is_output(i) {
+ if self.is_general_push_pull(i) || self.is_alternate_push_pull(i) {
+ // push-pull output: pin follows ODR
+ new_idr_mask |= 1 << i;
+ } else if (self.odr.get() & (1 << i)) == 0 {
+ // open-drain, ODR 0: pin driven low
+ new_idr_mask |= 1 << i;
+ } else if (self.disconnected_pins.get() & (1 << i)) == 0
+ && (self.pins_connected_high.get() & (1 << i)) == 0
+ {
+ // open-drain, ODR 1, externally pulled low
+ new_idr &= !(1 << i);
+ new_idr_mask |= 1 << i;
+ } else if self.is_input_pullup(i) {
+ new_idr_mask |= 1 << i;
+ } else if self.is_input_pulldown(i) {
+ new_idr &= !(1 << i);
+ new_idr_mask |= 1 << i;
+ }
+ // otherwise open-drain ODR 1 floating: keep old value
+ } else if (self.disconnected_pins.get() & (1 << i)) == 0 {
+ // input/analog, externally driven
+ if (self.pins_connected_high.get() & (1 << i)) != 0 {
+ new_idr_mask |= 1 << i;
+ new_idr |= 1 << i;
+ } else {
+ new_idr_mask |= 1 << i;
+ new_idr &= !(1 << i);
+ }
+ } else {
+ // input/analog, not driven: pull-up/down or floating
+ if self.is_input_pullup(i) {
+ new_idr |= 1 << i;
+ new_idr_mask |= 1 << i;
+ } else if self.is_input_pulldown(i) {
+ new_idr &= !(1 << i);
+ new_idr_mask |= 1 << i;
+ }
+ }
+ }
+
+ self.idr
+ .set((old_idr & !new_idr_mask) | (new_idr & new_idr_mask));
+
+ // Report which determined pins actually flipped level.
+ let mut changed = 0u32;
+ for i in 0..GPIO_NUM_PINS {
+ if (new_idr_mask & (1 << i)) != 0
+ && ((new_idr & (1 << i)) != 0) != ((old_idr & (1 << i)) != 0)
+ {
+ changed |= 1 << i;
+ }
+ }
+ PinUpdate {
+ mask: changed,
+ levels: new_idr,
+ }
+ }
+}
+
+impl_vmstate_struct!(
+ GpioRegisters,
+ VMStateDescriptionBuilder::<GpioRegisters>::new()
+ .name(c"stm32f1xx-gpio/regs")
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
+ vmstate_of!(GpioRegisters, crl),
+ vmstate_of!(GpioRegisters, crh),
+ vmstate_of!(GpioRegisters, idr),
+ vmstate_of!(GpioRegisters, odr),
+ vmstate_of!(GpioRegisters, lckr),
+ vmstate_of!(GpioRegisters, disconnected_pins),
+ vmstate_of!(GpioRegisters, pins_connected_high),
+ })
+ .build()
+);
+
+#[repr(C)]
+#[derive(qom::Object, hwcore::Device)]
+pub struct Stm32f1xxGpioState {
+ parent_obj: ParentField<SysBusDevice>,
+ mmio: MemoryRegion,
+
+ regs: GpioRegisters,
+
+ // One output line per pin, exposed as GPIO-out (to EXTI/board).
+ outlines: [InterruptSource; GPIO_NUM_PINS],
+}
+
+impl Stm32f1xxGpioState {
+ unsafe fn init(mut this: ParentInit<Self>) {
+ static STM32F1XX_GPIO_OPS: MemoryRegionOps<Stm32f1xxGpioState> =
+ MemoryRegionOpsBuilder::<Stm32f1xxGpioState>::new()
+ .read(&Stm32f1xxGpioState::read)
+ .write(&Stm32f1xxGpioState::write)
+ .little_endian()
+ .impl_sizes(4, 4)
+ .build();
+
+ MemoryRegion::init_io(
+ &mut uninit_field_mut!(*this, mmio),
+ &STM32F1XX_GPIO_OPS,
+ "stm32f1xx-gpio-rust",
+ 0x400,
+ );
+
+ uninit_field_mut!(*this, regs).write(GpioRegisters::new());
+ uninit_field_mut!(*this, outlines).write(Default::default());
+
+ trace::trace_stm32f1xx_gpio_instance_init();
+ }
+
+ fn post_init(&self) {
+ self.init_mmio(&self.mmio);
+ // Pin-change interrupts on F1 are delivered through EXTI, not a
+ // dedicated NVIC line, so the port only exposes GPIO in/out lines
+ // (no sysbus IRQ).
+ self.init_gpio_in(GPIO_NUM_PINS as u32, Self::gpio_set);
+ self.init_gpio_out(&self.outlines);
+ }
+
+ /// Drive the out lines according to a [`PinUpdate`].
+ fn apply_pin_update(&self, upd: PinUpdate) {
+ for i in 0..GPIO_NUM_PINS {
+ if (upd.mask & (1 << i)) != 0 {
+ let level = (upd.levels & (1 << i)) != 0;
+ self.outlines[i].set(level);
+ trace::trace_stm32f1xx_gpio_irq(i as u32, i32::from(level));
+ }
+ }
+ }
+
+ fn reset_hold(&self, _type: ResetType) {
+ trace::trace_stm32f1xx_gpio_reset();
+ let upd = self.regs.reset();
+ self.apply_pin_update(upd);
+ }
+
+ // GPIO-in callback: external source drives a pin.
+ fn gpio_set(&self, line: u32, level: u32) {
+ let upd = self.regs.gpio_set(line as usize, level);
+ self.apply_pin_update(upd);
+ }
+
+ fn write(&self, offset: hwaddr, data: u64, size: u32) {
+ trace::trace_stm32f1xx_gpio_write(offset, data, size);
+ let upd = self.regs.write(offset, data as u32);
+ self.apply_pin_update(upd);
+ }
+
+ fn read(&self, offset: hwaddr, size: u32) -> u64 {
+ let value = self.regs.read(offset);
+ trace::trace_stm32f1xx_gpio_read(offset, u64::from(value), size);
+ u64::from(value)
+ }
+}
+
+impl ObjectImpl for Stm32f1xxGpioState {
+ type ParentType = SysBusDevice;
+
+ const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
+ const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
+ const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
+}
+
+impl DeviceImpl for Stm32f1xxGpioState {
+ const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_STM32F1XX_GPIO);
+}
+
+impl SysBusDeviceImpl for Stm32f1xxGpioState {}
+
+impl ResettablePhasesImpl for Stm32f1xxGpioState {
+ const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold);
+}
+
+// Migration: register block only; IRQ links are reconstructed on wire-up.
+const VMSTATE_STM32F1XX_GPIO: VMStateDescription<Stm32f1xxGpioState> =
+ VMStateDescriptionBuilder::<Stm32f1xxGpioState>::new()
+ .name(c"stm32f1xx-gpio")
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
+ vmstate_of!(Stm32f1xxGpioState, regs),
+ })
+ .build();
diff --git a/rust/hw/gpio/src/lib.rs b/rust/hw/gpio/src/lib.rs
new file mode 100644
index 0000000000..57a1420bb1
--- /dev/null
+++ b/rust/hw/gpio/src/lib.rs
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pub mod bindings;
+pub mod gpio;
--
2.53.0
next prev parent reply other threads:[~2026-07-26 14:53 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-26 14:51 [RFC PATCH v1 0/5] hw/gpio: STM32F1xx GPIO controller in Rust Jack Wang
2026-07-26 14:51 ` Jack Wang [this message]
2026-08-12 15:03 ` [RFC PATCH v1 1/5] hw/gpio: add stm32f1xx GPIO device model (Rust) Alistair
2026-07-26 14:51 ` [RFC PATCH v1 2/5] rust: add hw/gpio crate to the workspace Jack Wang
2026-07-26 14:51 ` [RFC PATCH v1 3/5] hw/arm/stm32f103: instantiate and map GPIO ports Jack Wang
2026-07-27 7:48 ` Philippe Mathieu-Daudé
2026-07-26 14:51 ` [RFC PATCH v1 4/5] tests/qtest: add stm32f103 GPIO test Jack Wang
2026-07-26 14:51 ` [RFC PATCH v1 5/5] rust/hw/gpio: add native unit tests Jack Wang
2026-08-12 6:31 ` Jack wang
2026-08-12 13:53 ` [RFC PATCH v1 0/5] hw/gpio: STM32F1xx GPIO controller in Rust Jack wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260726145119.19881-2-163wangjack@gmail.com \
--to=163wangjack@gmail.com \
--cc=alistair@alistair23.me \
--cc=caojunze424@gmail.com \
--cc=farosas@suse.de \
--cc=lvivier@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=qemu-rust@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).