Add native tests for the STM32F1xx GPIO register logic based on STM32F100xx 
RM0041.

Retain the v1 reset, push-pull, floating-input, pull and BSRR/BRR coverage. Add 
focused cases for analog input, released open-drain sampling, AF-output 
isolation, reset output deassertion and explicitly unimplemented LCKR behavior.

Register the native test target together with its source so intermediate 
commits do not reference a missing file.

Signed-off-by: Jack Wang <[email protected]>
---
 rust/hw/gpio/meson.build    |  41 +++++++++
 rust/hw/gpio/tests/tests.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 257 insertions(+)

diff --git a/rust/hw/gpio/meson.build b/rust/hw/gpio/meson.build
index e359d7e329..f0dd7d4acb 100644
--- a/rust/hw/gpio/meson.build
+++ b/rust/hw/gpio/meson.build
@@ -6,3 +6,44 @@ rust_devices_ss.add(when: 'CONFIG_X_STM32F1XX_GPIO_RUST', 
if_true: [declare_depe
   link_whole: [_libstm32f1xx_gpio_rs],
   variables: {'crate': 'stm32f1xx_gpio'},
 )])
+
+stm32f1xx_gpio_rs = declare_dependency(
+  link_with: [_libstm32f1xx_gpio_rs],
+  dependencies: [
+    common_rs,
+    qom_rs,
+    hwcore_rs,
+    bql_rs,
+    migration_rs,
+    system_rs,
+    util_rs,
+  ],
+)
+
+test(
+  'rust-stm32f1xx-gpio-integration',
+  executable(
+    'rust-stm32f1xx-gpio-integration',
+    files('tests/tests.rs'),
+    override_options: ['rust_std=2021', 'build.rust_std=2021'],
+    rust_args: ['--test'],
+    install: false,
+    dependencies: [
+      stm32f1xx_gpio_rs,
+      common_rs,
+      qom_rs,
+      hwcore_rs,
+      bql_rs,
+      migration_rs,
+      system_rs,
+      util_rs,
+    ],
+  ),
+  args: [
+    '--test',
+    '--test-threads', '1',
+    '--format', 'pretty',
+  ],
+  protocol: 'rust',
+  suite: ['unit', 'rust'],
+)
diff --git a/rust/hw/gpio/tests/tests.rs b/rust/hw/gpio/tests/tests.rs
new file mode 100644
index 0000000000..df71d205e0
--- /dev/null
+++ b/rust/hw/gpio/tests/tests.rs
@@ -0,0 +1,216 @@
+// Copyright 2026, Jack Wang
+// Author(s): Jack Wang <[email protected]>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use stm32f1xx_gpio::gpio::{
+    GpioRegisters, PinUpdate, REG_BRR, REG_BSRR, REG_CRH, REG_CRL, REG_IDR, 
REG_LCKR, REG_ODR,
+    RESET_CRH, RESET_CRL,
+};
+
+fn regs() -> GpioRegisters {
+    bql::start_test();
+    GpioRegisters::new()
+}
+
+const CFG_INPUT_ANALOG: u32 = 0x0;
+const CFG_INPUT_FLOATING: u32 = 0x4;
+const CFG_INPUT_PULL: u32 = 0x8;
+const CFG_OUTPUT_PP: u32 = 0x1;
+const CFG_OUTPUT_OD: u32 = 0x5;
+const CFG_AF_PP: u32 = 0x9;
+
+fn set_config(r: &GpioRegisters, pin: usize, cfg: u32) {
+    let reg = if pin < 8 { REG_CRL } else { REG_CRH };
+    let shift = ((pin % 8) * 4) as u32;
+    let old = r.read(reg);
+    r.write(reg, (old & !(0xF << shift)) | (cfg << shift));
+}
+
+fn set_odr_bit(r: &GpioRegisters, pin: usize, value: u32) {
+    let old = r.read(REG_ODR);
+    r.write(REG_ODR, (old & !(1 << pin)) | (value << pin));
+}
+
+fn pin_changed_to(upd: PinUpdate, pin: usize, level: bool) -> bool {
+    (upd.mask & (1 << pin)) != 0 && (((upd.levels >> pin) & 1) != 0) == level
+}
+
+#[test]
+fn test_reset_values() {
+    let r = regs();
+    assert_eq!(r.read(REG_CRL), RESET_CRL);
+    assert_eq!(r.read(REG_CRH), RESET_CRH);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert_eq!(r.read(REG_ODR), 0);
+}
+
+#[test]
+fn test_reset_restores() {
+    let r = regs();
+    r.write(REG_CRL, 0xDEAD_BEEF);
+    r.write(REG_ODR, 0x0000_FFFF);
+    assert_ne!(r.read(REG_CRL), RESET_CRL);
+
+    r.reset();
+
+    assert_eq!(r.read(REG_CRL), RESET_CRL);
+    assert_eq!(r.read(REG_CRH), RESET_CRH);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert_eq!(r.read(REG_ODR), 0);
+}
+
+#[test]
+fn test_output_mode() {
+    let r = regs();
+    let pin = 5;
+
+    let idle = set_odr_and_report(&r, pin, 1);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert_eq!(idle.mask & (1 << pin), 0);
+
+    let old = r.read(REG_CRL);
+    let to_output = r.write(
+        REG_CRL,
+        (old & !(0xF << (pin * 4))) | (CFG_OUTPUT_PP << (pin * 4)),
+    );
+    assert_eq!(r.read(REG_IDR), 1 << pin);
+    assert!(pin_changed_to(to_output, pin, true));
+
+    let to_low = set_odr_and_report(&r, pin, 0);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert!(pin_changed_to(to_low, pin, false));
+}
+
+fn set_odr_and_report(r: &GpioRegisters, pin: usize, value: u32) -> PinUpdate {
+    let old = r.read(REG_ODR);
+    r.write(REG_ODR, (old & !(1 << pin)) | (value << pin))
+}
+
+#[test]
+fn test_input_mode() {
+    let r = regs();
+    let pin = 6usize;
+
+    set_config(&r, pin, CFG_INPUT_FLOATING);
+
+    let drive_high = r.gpio_set(pin, 1);
+    assert_eq!(r.read(REG_IDR), 1 << pin);
+    assert!(pin_changed_to(drive_high, pin, true));
+
+    let drive_low = r.gpio_set(pin, 0);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert!(pin_changed_to(drive_low, pin, false));
+}
+
+#[test]
+fn test_pull_up_down() {
+    let r = regs();
+    let pin = 0;
+
+    set_odr_bit(&r, pin, 1);
+    set_config(&r, pin, CFG_INPUT_PULL);
+    assert_eq!(r.read(REG_IDR), 1 << pin);
+
+    set_odr_bit(&r, pin, 0);
+    assert_eq!(r.read(REG_IDR), 0);
+}
+
+#[test]
+fn test_bsrr_brr() {
+    let r = regs();
+    let pin = 1;
+
+    r.write(REG_BSRR, 1 << pin);
+    assert_eq!(r.read(REG_ODR), 1 << pin);
+
+    r.write(REG_BSRR, 1 << (pin + 16));
+    assert_eq!(r.read(REG_ODR), 0);
+
+    r.write(REG_BSRR, 1 << pin);
+    r.write(REG_BRR, 1 << pin);
+    assert_eq!(r.read(REG_ODR), 0);
+
+    // Set has priority over reset within one BSRR write.
+    r.write(REG_BSRR, (1 << pin) | (1 << (pin + 16)));
+    assert_eq!(r.read(REG_ODR), 1 << pin);
+}
+
+#[test]
+fn test_push_pull_disconnect() {
+    let r = regs();
+    let pin = 7usize;
+
+    set_config(&r, pin, CFG_INPUT_FLOATING);
+    r.gpio_set(pin, 1);
+    assert_eq!(r.read(REG_IDR), 1 << pin);
+    assert_eq!(r.disconnected_pins() & (1 << pin), 0);
+
+    set_odr_bit(&r, pin, 0);
+    set_config(&r, pin, CFG_OUTPUT_PP);
+    assert_eq!(r.read(REG_IDR), 0);
+    assert_ne!(r.disconnected_pins() & (1 << pin), 0);
+
+    r.gpio_set(pin, 1);
+    assert_eq!(r.read(REG_IDR), 0);
+}
+
+#[test]
+fn test_analog_input_is_digitally_low() {
+    let r = regs();
+    let pin = 0;
+
+    set_config(&r, pin, CFG_INPUT_ANALOG);
+    r.gpio_set(pin, 1);
+
+    assert_eq!(r.read(REG_IDR) & (1 << pin), 0);
+}
+
+#[test]
+fn test_open_drain_released_samples_external_input() {
+    let r = regs();
+    let pin = 1;
+
+    set_config(&r, pin, CFG_OUTPUT_OD);
+    set_odr_bit(&r, pin, 1);
+
+    r.gpio_set(pin, 1);
+    assert_eq!(r.read(REG_IDR) & (1 << pin), 1 << pin);
+
+    r.gpio_set(pin, 0);
+    assert_eq!(r.read(REG_IDR) & (1 << pin), 0);
+}
+
+#[test]
+fn test_af_output_does_not_follow_odr() {
+    let r = regs();
+    let pin = 2;
+
+    set_config(&r, pin, CFG_AF_PP);
+    set_odr_bit(&r, pin, 1);
+
+    assert_eq!(r.read(REG_IDR) & (1 << pin), 0);
+}
+
+#[test]
+fn test_reset_deasserts_previous_high_output() {
+    let r = regs();
+    let pin = 3;
+
+    set_config(&r, pin, CFG_OUTPUT_PP);
+    set_odr_bit(&r, pin, 1);
+    assert_eq!(r.read(REG_IDR) & (1 << pin), 1 << pin);
+
+    let update = r.reset();
+
+    assert_ne!(update.mask & (1 << pin), 0);
+    assert_eq!(update.levels & (1 << pin), 0);
+}
+
+#[test]
+fn test_lckr_is_explicitly_unimplemented() {
+    let r = regs();
+
+    r.write(REG_LCKR, 0x0001_0001);
+
+    assert_eq!(r.read(REG_LCKR), 0);
+}

-- 
2.53.0


Reply via email to