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:52 UTC|newest]
Thread overview: 6+ 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-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-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
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.