[PATCH RFC v2 4/5] tests/qtest: add STM32F100 GPIO tests

Jack Wang <[email protected]>
Newsgroups org.nongnu.qemu-rust,org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
Add integration coverage for the STM32F1xx GPIO device on the stm32vldiscovery machine and its five GPIOA-E instances.

Retain the v1 register and GPIO-line coverage and add focused cases for analog input, released open-drain sampling, AF-output isolation, reset output deassertion, word-only MMIO and explicitly unimplemented LCKR behavior.

When the Rust device is unavailable, keep a machine-start smoke test for the unimplemented-device fallback.

Signed-off-by: Jack Wang <[email protected]>
---
 tests/qtest/meson.build           |   4 +
 tests/qtest/stm32f100_gpio-test.c | 380 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 384 insertions(+)

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 12ed368997..d05ce065cd 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -239,6 +239,9 @@ qtests_stm32l4x5 = \
    'stm32l4x5_gpio-test',
    'stm32l4x5_usart-test']
 
+qtests_stm32f100 = \
+  ['stm32f100_gpio-test']
+
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
@@ -254,6 +257,7 @@ qtests_arm = \
   (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
   (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
+  (config_all_devices.has_key('CONFIG_STM32F100_SOC') ? qtests_stm32f100 : []) + \
   (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
diff --git a/tests/qtest/stm32f100_gpio-test.c b/tests/qtest/stm32f100_gpio-test.c
new file mode 100644
index 0000000000..ea8483c1e4
--- /dev/null
+++ b/tests/qtest/stm32f100_gpio-test.c
@@ -0,0 +1,380 @@
+/*
+ * QTest testcase for STM32F100 GPIO (Rust device "stm32f1xx-gpio-rust")
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright (c) 2026 QEMU contributors
+ *
+ * The STM32F1 GPIO differs from the L4 family: each pin is configured by a
+ * 4-bit field MODE[1:0]+CNF[1:0] packed into CRL (pins 0-7) and CRH (pins
+ * 8-15), instead of the separate MODER/OTYPER/PUPDR registers of the L4.
+ * See ST RM0041 for the register map and reset values.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define GPIO_BASE_ADDR 0x40010800
+#define GPIO_SIZE      0x400
+#define NUM_GPIOS      5
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x40010800
+#define GPIO_B 0x40010C00
+#define GPIO_C 0x40011000
+#define GPIO_D 0x40011400
+#define GPIO_E 0x40011800
+
+#define CRL  0x00
+#define CRH  0x04
+#define IDR  0x08
+#define ODR  0x0C
+#define BSRR 0x10
+#define BRR  0x14
+#define LCKR 0x18
+
+#define CFG_INPUT_FLOATING   0x4
+#define CFG_INPUT_ANALOG     0x0
+#define CFG_INPUT_PULL       0x8
+#define CFG_OUTPUT_PP        0x1
+#define CFG_OUTPUT_OD        0x5
+#define CFG_AF_PP            0x9
+
+#define CR_RESET 0x44444444
+
+static uint32_t gpio_readl(unsigned int gpio, unsigned int offset)
+{
+    return readl(gpio + offset);
+}
+
+static void gpio_writel(unsigned int gpio, unsigned int offset, uint32_t value)
+{
+    writel(gpio + offset, value);
+}
+
+static void gpio_set_config(unsigned int gpio, unsigned int pin, uint8_t cfg)
+{
+    unsigned int reg = (pin < 8) ? CRL : CRH;
+    unsigned int shift = (pin % 8) * 4;
+    uint32_t mask = ~(0xFu << shift);
+    uint32_t val = (gpio_readl(gpio, reg) & mask) | ((uint32_t)cfg << shift);
+    gpio_writel(gpio, reg, val);
+}
+
+static void gpio_set_odr_bit(unsigned int gpio, unsigned int pin,
+                             uint32_t value)
+{
+    uint32_t mask = ~(0x1u << pin);
+    gpio_writel(gpio, ODR, (gpio_readl(gpio, ODR) & mask) | (value << pin));
+}
+
+static unsigned int get_gpio_id(uint32_t gpio_addr)
+{
+    return (gpio_addr - GPIO_BASE_ADDR) / GPIO_SIZE;
+}
+
+static const char *gpio_path(uint32_t gpio)
+{
+    static char buf[32];
+    snprintf(buf, sizeof(buf), "/machine/soc/gpio%c", 'a' + get_gpio_id(gpio));
+    return buf;
+}
+
+static void gpio_set_irq(unsigned int gpio, int num, int level)
+{
+    qtest_set_irq_in(global_qtest, gpio_path(gpio), NULL, num, level);
+}
+
+/* qtest can intercept one device's outputs per session; use GPIOA. */
+static void intercept_out(unsigned int gpio)
+{
+    qtest_irq_intercept_out(global_qtest, gpio_path(gpio));
+}
+
+static void reset_gpio(unsigned int gpio)
+{
+    gpio_writel(gpio, CRL, CR_RESET);
+    gpio_writel(gpio, CRH, CR_RESET);
+    gpio_writel(gpio, ODR, 0);
+}
+
+static void test_reset_values(void)
+{
+    gpio_writel(GPIO_A, CRL, 0xDEADBEEF);
+    gpio_writel(GPIO_A, CRH, 0xDEADBEEF);
+    gpio_writel(GPIO_A, ODR, 0xDEADBEEF);
+
+    gpio_writel(GPIO_C, CRL, 0x12345678);
+    gpio_writel(GPIO_C, ODR, 0x0000FFFF);
+
+    qtest_system_reset(global_qtest);
+
+    g_assert_cmphex(gpio_readl(GPIO_A, CRL), ==, CR_RESET);
+    g_assert_cmphex(gpio_readl(GPIO_A, CRH), ==, CR_RESET);
+    g_assert_cmphex(gpio_readl(GPIO_A, IDR), ==, 0);
+    g_assert_cmphex(gpio_readl(GPIO_A, ODR), ==, 0);
+
+    g_assert_cmphex(gpio_readl(GPIO_C, CRL), ==, CR_RESET);
+    g_assert_cmphex(gpio_readl(GPIO_C, IDR), ==, 0);
+    g_assert_cmphex(gpio_readl(GPIO_C, ODR), ==, 0);
+}
+
+static void test_output_mode(const void *data)
+{
+    unsigned int pin = (uintptr_t)data & 0xF;
+    uint32_t gpio = (uintptr_t)data & ~(GPIO_SIZE - 1);
+    bool observe = (gpio == GPIO_A);
+
+    reset_gpio(gpio);
+    if (observe) {
+        intercept_out(gpio);
+    }
+
+    gpio_set_odr_bit(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+    if (observe) {
+        g_assert_false(get_irq(pin));
+    }
+
+    gpio_set_config(gpio, pin, CFG_OUTPUT_PP);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, (1u << pin));
+    if (observe) {
+        g_assert_true(get_irq(pin));
+    }
+
+    gpio_set_odr_bit(gpio, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+    if (observe) {
+        g_assert_false(get_irq(pin));
+    }
+
+    reset_gpio(gpio);
+}
+
+static void test_input_mode(const void *data)
+{
+    unsigned int pin = (uintptr_t)data & 0xF;
+    uint32_t gpio = (uintptr_t)data & ~(GPIO_SIZE - 1);
+    bool observe = (gpio == GPIO_A);
+
+    reset_gpio(gpio);
+    if (observe) {
+        intercept_out(gpio);
+    }
+
+    gpio_set_config(gpio, pin, CFG_INPUT_FLOATING);
+
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, (1u << pin));
+    if (observe) {
+        g_assert_true(get_irq(pin));
+    }
+
+    gpio_set_irq(gpio, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+    if (observe) {
+        g_assert_false(get_irq(pin));
+    }
+
+    reset_gpio(gpio);
+}
+
+static void test_pull_up_down(const void *data)
+{
+    unsigned int pin = (uintptr_t)data & 0xF;
+    uint32_t gpio = (uintptr_t)data & ~(GPIO_SIZE - 1);
+
+    reset_gpio(gpio);
+    intercept_out(gpio);
+
+    gpio_set_odr_bit(gpio, pin, 1);
+    gpio_set_config(gpio, pin, CFG_INPUT_PULL);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, (1u << pin));
+    g_assert_true(get_irq(pin));
+
+    gpio_set_odr_bit(gpio, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+    g_assert_false(get_irq(pin));
+
+    reset_gpio(gpio);
+}
+
+static void test_bsrr_brr(const void *data)
+{
+    unsigned int pin = (uintptr_t)data & 0xF;
+    uint32_t gpio = (uintptr_t)data & ~(GPIO_SIZE - 1);
+
+    reset_gpio(gpio);
+
+    gpio_writel(gpio, BSRR, (1u << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, (1u << pin));
+
+    gpio_writel(gpio, BSRR, (1u << (pin + 16)));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, 0);
+
+    gpio_writel(gpio, BSRR, (1u << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, (1u << pin));
+
+    gpio_writel(gpio, BRR, (1u << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, 0);
+
+    /* Set has priority over reset in BSRR. */
+    gpio_writel(gpio, BSRR, (1u << pin) | (1u << (pin + 16)));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, (1u << pin));
+
+    reset_gpio(gpio);
+}
+
+static void test_push_pull_disconnect(const void *data)
+{
+    unsigned int pin = (uintptr_t)data & 0xF;
+    uint32_t gpio = (uintptr_t)data & ~(GPIO_SIZE - 1);
+
+    reset_gpio(gpio);
+
+    gpio_set_config(gpio, pin, CFG_INPUT_FLOATING);
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, (1u << pin));
+
+    gpio_set_odr_bit(gpio, pin, 0);
+    gpio_set_config(gpio, pin, CFG_OUTPUT_PP);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, 0);
+
+    reset_gpio(gpio);
+}
+
+static void test_analog_input(void)
+{
+    reset_gpio(GPIO_A);
+    gpio_set_config(GPIO_A, 0, CFG_INPUT_ANALOG);
+    gpio_set_irq(GPIO_A, 0, 1);
+
+    g_assert_cmphex(gpio_readl(GPIO_A, IDR) & 1, ==, 0);
+
+    reset_gpio(GPIO_A);
+}
+
+static void test_open_drain_released(void)
+{
+    reset_gpio(GPIO_A);
+    gpio_set_config(GPIO_A, 1, CFG_OUTPUT_OD);
+    gpio_set_odr_bit(GPIO_A, 1, 1);
+
+    gpio_set_irq(GPIO_A, 1, 1);
+    g_assert_cmphex(gpio_readl(GPIO_A, IDR) & (1u << 1), ==, 1u << 1);
+
+    gpio_set_irq(GPIO_A, 1, 0);
+    g_assert_cmphex(gpio_readl(GPIO_A, IDR) & (1u << 1), ==, 0);
+
+    reset_gpio(GPIO_A);
+}
+
+static void test_af_output_not_driven_by_odr(void)
+{
+    reset_gpio(GPIO_A);
+    gpio_set_config(GPIO_A, 2, CFG_AF_PP);
+    gpio_set_odr_bit(GPIO_A, 2, 1);
+
+    g_assert_cmphex(gpio_readl(GPIO_A, IDR) & (1u << 2), ==, 0);
+
+    reset_gpio(GPIO_A);
+}
+
+static void test_reset_deasserts_output(void)
+{
+    reset_gpio(GPIO_A);
+    intercept_out(GPIO_A);
+    gpio_set_config(GPIO_A, 3, CFG_OUTPUT_PP);
+    gpio_set_odr_bit(GPIO_A, 3, 1);
+    g_assert_true(get_irq(3));
+
+    qtest_system_reset(global_qtest);
+
+    g_assert_false(get_irq(3));
+    g_assert_cmphex(gpio_readl(GPIO_A, ODR), ==, 0);
+}
+
+static void test_lckr_unimplemented(void)
+{
+    reset_gpio(GPIO_A);
+    gpio_writel(GPIO_A, LCKR, 0x00010001);
+    g_assert_cmphex(gpio_readl(GPIO_A, LCKR), ==, 0);
+}
+
+static void test_word_access_only(void)
+{
+    gpio_writel(GPIO_A, ODR, 0x55aa);
+
+    writeb(GPIO_A + ODR, 0xff);
+    g_assert_cmphex(gpio_readl(GPIO_A, ODR), ==, 0x55aa);
+
+    writew(GPIO_A + ODR, 0xffff);
+    g_assert_cmphex(gpio_readl(GPIO_A, ODR), ==, 0x55aa);
+
+    writel(GPIO_A + ODR + 1, 0xffffffff);
+    g_assert_cmphex(gpio_readl(GPIO_A, ODR), ==, 0x55aa);
+
+    reset_gpio(GPIO_A);
+}
+
+static void test_machine_started(void)
+{
+    g_assert_nonnull(global_qtest);
+}
+
+static void register_gpio_tests(void)
+{
+    qtest_add_func("stm32f100/gpio/reset_values", test_reset_values);
+    qtest_add_data_func("stm32f100/gpio/output_mode_a5",
+                        (void *)(uintptr_t)(GPIO_A | 5), test_output_mode);
+    qtest_add_data_func("stm32f100/gpio/output_mode_c13",
+                        (void *)(uintptr_t)(GPIO_C | 13), test_output_mode);
+    qtest_add_data_func("stm32f100/gpio/input_mode_b6",
+                        (void *)(uintptr_t)(GPIO_B | 6), test_input_mode);
+    qtest_add_data_func("stm32f100/gpio/input_mode_d10",
+                        (void *)(uintptr_t)(GPIO_D | 10), test_input_mode);
+    qtest_add_data_func("stm32f100/gpio/pull_up_down_a0",
+                        (void *)(uintptr_t)(GPIO_A | 0), test_pull_up_down);
+    qtest_add_data_func("stm32f100/gpio/bsrr_brr_a1",
+                        (void *)(uintptr_t)(GPIO_A | 1), test_bsrr_brr);
+    qtest_add_data_func("stm32f100/gpio/bsrr_brr_e12",
+                        (void *)(uintptr_t)(GPIO_E | 12), test_bsrr_brr);
+    qtest_add_data_func("stm32f100/gpio/push_pull_disconnect_e7",
+                        (void *)(uintptr_t)(GPIO_E | 7),
+                        test_push_pull_disconnect);
+    qtest_add_func("stm32f100/gpio/analog_input", test_analog_input);
+    qtest_add_func("stm32f100/gpio/open_drain_released",
+                   test_open_drain_released);
+    qtest_add_func("stm32f100/gpio/af_output_not_driven_by_odr",
+                   test_af_output_not_driven_by_odr);
+    qtest_add_func("stm32f100/gpio/reset_deasserts_output",
+                   test_reset_deasserts_output);
+    qtest_add_func("stm32f100/gpio/lckr_unimplemented",
+                   test_lckr_unimplemented);
+    qtest_add_func("stm32f100/gpio/word_access_only",
+                   test_word_access_only);
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_set_nonfatal_assertions();
+
+    if (qtest_has_device("stm32f1xx-gpio-rust")) {
+        register_gpio_tests();
+    } else {
+        qtest_add_func("stm32f100/gpio/no_rust_machine_start",
+                       test_machine_started);
+    }
+
+    qtest_start("-machine stm32vldiscovery");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}

-- 
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.