[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