Re: [RFC PATCH v1 1/5] hw/gpio: add stm32f1xx GPIO device model (Rust)

Alistair <[email protected]>
Newsgroups org.nongnu.qemu-rust,org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
On Sun, 2026-07-26 at 22:51 +0800, Jack Wang wrote:
> 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 <[email protected]>
> ---
>  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

Oh cool!

I don't know the QEMU Rust style, but from a quick look at this it
seems sane.

So overall looks good. Although I haven't looked too closely or checked
the data sheet. When you hook it up to an upstream machine I can check
a bit more

Alistair

> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 72357df7c4..4870da54ab 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1184,6 +1184,13 @@ L: [email protected]
>  S: Maintained
>  F: hw/arm/stm32f100_soc.c
>  
> +STM32F1xx GPIO (Rust)
> +M: Jack Wang <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: rust/hw/gpio/
> +F: tests/qtest/stm32f103_gpio-test.c
> +
>  STM32F205
>  M: Alistair Francis <[email protected]>
>  M: Peter Maydell <[email protected]>
> 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 <[email protected]>
> +// 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;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.