[PATCH RFC v2 5/5] rust/hw/gpio: add native unit tests

Jack Wang <[email protected]>
Newsgroups org.nongnu.qemu-rust,org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
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
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.