From nobody Mon Sep 28 02:05:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1785077603; cv=none; d=zohomail.com; s=zohoarc; b=F6lul+ZYj7nqgOfaqL1D4n0DuOsI7q7djJiddtxLguYwQrwtvpnTCGPlxF/DW86GXU0D965+WJCONEFRcf8KRz6TNUSB8NQgzh8zMvU0e/svEEIg3manyca15M404zkVK6OzKHV8863qR0tH6l/EXP2OZOSP5IKZ8KnKHW0I1uo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785077603; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=RGu17vjDGeJ3mfUvccD1KTtBIiIb8HX6pbI7ZxqAZFg=; b=JVvIA6b/sbIDrO7oyFH5UbedRMWReLBOogK+gIkYvlcr6xMXR7U4zofFAF5zDUUTk9Vq1LHuW5J71C1D/nhjGe9X7koI6N++gHyE6BG9XdsUZyYbyJHOq8kE6TSqddPdzjbBrpcnwbH9ZDITmaNWs5Ij5nHwrI5TPPxu1xOvk8Y= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<163wangjack@gmail.com> (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785077603678261.43412376308106; Sun, 26 Jul 2026 07:53:23 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wo0Cv-0005wA-V8; Sun, 26 Jul 2026 10:52:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Cq-0005ua-GE for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:56 -0400 Received: from mail-pl1-x62d.google.com ([2607:f8b0:4864:20::62d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Ch-0005zo-82 for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:55 -0400 Received: by mail-pl1-x62d.google.com with SMTP id d9443c01a7336-2caea3f742bso24353685ad.0 for ; Sun, 26 Jul 2026 07:51:45 -0700 (PDT) Received: from localhost.localdomain ([82.40.42.115]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cfde81d7f5sm20934195ad.83.2026.07.26.07.51.39 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 26 Jul 2026 07:51:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785077504; x=1785682304; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RGu17vjDGeJ3mfUvccD1KTtBIiIb8HX6pbI7ZxqAZFg=; b=bO8Iwaw2dIJeEEjk1vOK7wuTXv3jNA2cf/yjcx252QPavdufvrP8w8W+TeHmBwqFxK 8aHJtj3lKQdPsyEIDhdmmihT+0UAGY0/EDkqeFBzT9jXK8U+WPUszc2o0nQwFEg3/EUX F2S7Qknr27+wP9CDgODJgjbDIlxxBQYEKeO7ykJdSTLzR0z3a6HJIejRuqPfmk/RnHy1 4hvYOuh1YvIJoTeY3jcgTbxP2JKIuYjTqYfWcsJWOzUSmhExEAQprMT/K0cvzlYI1lS+ v5FbNxhPTUaIPuAhKrcy/AV3B9JvJ9s4qJPKhB7Ejzf83iP5twpebwSubFVSUJ2KxEFs oaxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785077504; x=1785682304; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=RGu17vjDGeJ3mfUvccD1KTtBIiIb8HX6pbI7ZxqAZFg=; b=Aaq0fCKaQBDsZ4hJC245J3TIvuqchmmowVX6C91U50izjAoKXGKDmFsFzH3/pP8JHV 3aUNMk5cBRaVIjN6hURHKyuq6260/M3/WmEaeW/IoqNpGnR/U7aPS4xt0O7R4C1qt7GV 09/74aGzAIzjal9MtKe3ZsU+cDaYOFjQvKMN1r5P1RKzn15joENUZpetB7tybSM49hLH uOUmx6KuT4rfXdCRMj8Eudl/uO/rcwqtWaqrVuwdgJ1Hoji+b8KFYvhfJr8IGEfUgpIZ NVU4OvJ5Z3en4gRysGm3sfcjq3d9gHPo2fm4kZ5IKUCFfbv6IaOFZItBKo0IkBKjChbC jIlA== X-Gm-Message-State: AOJu0YwgLCCsPqcqc35tojD2i2TT03M1TZ3ltuQya3k/zLSfOlzTDUKS F8dAbAHktK14ZV4j2O+GyfTVY1Vg/mKd4nR/nDImBVcu5IQLgWSyiTDQdtbGeA== X-Gm-Gg: AR+sD12pHuR3SmW5zEmanDnNMYv8xIDsAMxy1uhyWmmGpgbB+MLbvqhE0rI+5NWCSVe tNYOiCs8ax3z8SBszCawKW+8FwPy4CPvHOy5tx6P4alxYc5WpC+qVkqZKqHPujk4GXeFnlooYo9 Hp/M+ia9QEetVKEi5sZH0ClMASTp7nY9H2CH8lcDwITh4M+TJT2NkKNJ8HvR2bwJo+5sBmRglex CipLEd2Li/YxC6f6yAasePqiw3/XCmS3iigaeS5NSbfp/e4kzl9ggHrbpXZweHI+LE2OdBcrqEg F6TmEsQ0ylwfL84dGf2TEe+vW4FlXOm0eem3Z2sUUbPkbh5EwBZo8LJDaGEbH1BcL8L3ofz+1GI 9EUrDcUmyMES36qjOZRR12P0uMLeH+njXh9Xc/4yULPN8/yWrMZrZaMDj4eSRfjrZZNNowM+n6A y6fI3Zj4YYQQj6MtNZpbQ3PgDp X-Received: by 2002:a17:902:e544:b0:2cc:f43b:fa4a with SMTP id d9443c01a7336-2cfde8253b7mr53274675ad.24.1785077503855; Sun, 26 Jul 2026 07:51:43 -0700 (PDT) From: Jack Wang <163wangjack@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , Alistair Francis , Fabiano Rosas , Laurent Vivier , qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com, Jack Wang <163wangjack@gmail.com> Subject: [RFC PATCH v1 1/5] hw/gpio: add stm32f1xx GPIO device model (Rust) Date: Sun, 26 Jul 2026 22:51:15 +0800 Message-ID: <20260726145119.19881-2-163wangjack@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com> References: <20260726145119.19881-1-163wangjack@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::62d; envelope-from=163wangjack@gmail.com; helo=mail-pl1-x62d.google.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_STARTS_WITH_NUMS=0.738, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1785077605454158500 Content-Type: text/plain; charset="utf-8" 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 <163wangjack@gmail.com> --- 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 diff --git a/MAINTAINERS b/MAINTAINERS index 72357df7c4..4870da54ab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1184,6 +1184,13 @@ L: qemu-arm@nongnu.org S: Maintained F: hw/arm/stm32f100_soc.c =20 +STM32F1xx GPIO (Rust) +M: Jack Wang <163wangjack@gmail.com> +L: qemu-arm@nongnu.org +S: Maintained +F: rust/hw/gpio/ +F: tests/qtest/stm32f103_gpio-test.c + STM32F205 M: Alistair Francis M: Peter Maydell 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 =20 +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 a= ddr: 0x%" PRIx64 " " stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s add= r: 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) "GPI= O%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) "offse= t 0x%" PRIx64 " value 0x%" PRIx64 " size %u" +stm32f1xx_gpio_write(uint64_t offset, uint64_t value, unsigned size) "offs= et 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 <163wangjack@gmail.com> +// 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, forwa= rds +// MMIO/pin callbacks into it, and propagates the resulting pin-level chan= ges +// onto the out lines. Keeping the logic free of C dependencies lets the u= nit +// 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 =3D 16; + +pub const TYPE_STM32F1XX_GPIO_RUST: &CStr =3D c"stm32f1xx-gpio-rust"; +qom_isa!(Stm32f1xxGpioState: SysBusDevice, DeviceState, Object); + +unsafe impl ObjectType for Stm32f1xxGpioState { + type Class =3D ::Class; + const TYPE_NAME: &'static CStr =3D TYPE_STM32F1XX_GPIO_RUST; +} + +// Trace bindings are generated from hw/gpio/trace-events (group "hw_gpio"= ). +// Each `stm32f1xx_gpio_` event yields a +// `trace::trace_stm32f1xx_gpio_` function whose argument types matc= h 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 =3D 0x00; +pub const REG_CRH: u64 =3D 0x04; +pub const REG_IDR: u64 =3D 0x08; +pub const REG_ODR: u64 =3D 0x0C; +pub const REG_BSRR: u64 =3D 0x10; +pub const REG_BRR: u64 =3D 0x14; +pub const REG_LCKR: u64 =3D 0x18; + +/// RM0008 reset values: CRL/CRH =3D 0x44444444 (all pins floating input). +pub const RESET_CRL: u32 =3D 0x4444_4444; +pub const RESET_CRH: u32 =3D 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 c= an +/// 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, + crh: BqlCell, + idr: BqlCell, + odr: BqlCell, + lckr: BqlCell, + + // Bit i set =3D> pin i is not driven by an external source. + // Bit i clear =3D> pin i is driven, and `pins_connected_high` bit i + // gives the level driven. + disconnected_pins: BqlCell, + pins_connected_high: BqlCell, +} + +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 <=3D 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 =3D if pin < 8 { + self.crl.get() + } else { + self.crh.get() + }; + let shift =3D ((pin % 8) * 4) as u32; + Self::extract(reg, shift, 4) as u8 + } + + // MODE !=3D 0 means output (any speed). + fn is_output(&self, pin: usize) -> bool { + (self.get_modecnf(pin) & 0x03) !=3D 0 + } + + fn is_alternate_open_drain(&self, pin: usize) -> bool { + let modecnf =3D self.get_modecnf(pin); + ((modecnf & 0x03) !=3D 0) && ((modecnf >> 2) =3D=3D 0x03) + } + + fn is_alternate_push_pull(&self, pin: usize) -> bool { + let modecnf =3D self.get_modecnf(pin); + ((modecnf & 0x03) !=3D 0) && ((modecnf >> 2) =3D=3D 0x02) + } + + fn is_general_push_pull(&self, pin: usize) -> bool { + let modecnf =3D self.get_modecnf(pin); + ((modecnf & 0x03) !=3D 0) && ((modecnf >> 2) =3D=3D 0x00) + } + + fn is_general_open_drain(&self, pin: usize) -> bool { + let modecnf =3D self.get_modecnf(pin); + ((modecnf & 0x03) !=3D 0) && ((modecnf >> 2) =3D=3D 0x01) + } + + // Input with pull-up/pull-down selected: MODE=3D00, CNF=3D10 (0b1000). + fn is_input_pull_up_down_mode(&self, pin: usize) -> bool { + self.get_modecnf(pin) =3D=3D 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 << p= in)) !=3D 0) + } + + fn is_input_pulldown(&self, pin: usize) -> bool { + self.is_input_pull_up_down_mode(pin) && ((self.odr.get() & (1 << p= in)) =3D=3D 0) + } + + fn check_and_warn_af(&self, start_pin: usize) { + for i in 0..8 { + let pin =3D 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 {}. Downgra= ding 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-d= rain + // output driven low is allowed. + if self.is_output(line) + && !((self.is_alternate_open_drain(line) || self.is_general_op= en_drain(line)) + && (level =3D=3D 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 !=3D 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), whi= ch + // must be disconnected to avoid a short circuit. + fn get_gpio_pinmask_to_disconnect(&self) -> u16 { + let mut pins_to_disconnect: u16 =3D 0; + for i in 0..GPIO_NUM_PINS { + if (self.disconnected_pins.get() & (1 << i) =3D=3D 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) !=3D 0) + { + pins_to_disconnect |=3D 1 << i; + log_mask_ln!( + Log::GuestError, + "stm32f1xx_gpio: Line {} can't be driven externall= y", + 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 =3D> { + 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_disconn= ect()) + } + REG_CRH =3D> { + self.crh.set(data); + self.check_and_warn_af(8); + self.disconnect_gpio_pins(self.get_gpio_pinmask_to_disconn= ect()) + } + REG_ODR =3D> { + self.odr.set(data & 0xFFFF); + self.update_gpio_idr() + } + REG_BRR =3D> { + let bits_to_reset =3D data & 0xFFFF; + self.odr.set(self.odr.get() & !bits_to_reset); + self.update_gpio_idr() + } + REG_BSRR =3D> { + let bits_to_set =3D data & 0xFFFF; + let bits_to_reset =3D (data >> 16) & 0xFFFF; + + // If both BSx and BRx are set, BSx has priority. + let mut current_odr =3D self.odr.get(); + current_odr &=3D !bits_to_reset; + current_odr |=3D bits_to_set; + self.odr.set(current_odr); + + self.update_gpio_idr() + } + REG_LCKR =3D> { + self.lckr.set(data); + log_mask_ln!( + Log::Unimp, + "stm32f1xx_gpio: Locking port bits configuration isn't= supported" + ); + PinUpdate::default() + } + _ =3D> { + 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 =3D> self.crl.get(), + REG_CRH =3D> self.crh.get(), + REG_IDR =3D> self.idr.get(), + REG_ODR =3D> self.odr.get(), + REG_BSRR =3D> 0, + REG_BRR =3D> 0, + REG_LCKR =3D> self.lckr.get(), + _ =3D> { + 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 =3D> pin i has a determined level in new_idr. + let mut new_idr_mask =3D 0u32; + let mut new_idr =3D self.odr.get(); + let old_idr =3D 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 |=3D 1 << i; + } else if (self.odr.get() & (1 << i)) =3D=3D 0 { + // open-drain, ODR 0: pin driven low + new_idr_mask |=3D 1 << i; + } else if (self.disconnected_pins.get() & (1 << i)) =3D=3D= 0 + && (self.pins_connected_high.get() & (1 << i)) =3D=3D 0 + { + // open-drain, ODR 1, externally pulled low + new_idr &=3D !(1 << i); + new_idr_mask |=3D 1 << i; + } else if self.is_input_pullup(i) { + new_idr_mask |=3D 1 << i; + } else if self.is_input_pulldown(i) { + new_idr &=3D !(1 << i); + new_idr_mask |=3D 1 << i; + } + // otherwise open-drain ODR 1 floating: keep old value + } else if (self.disconnected_pins.get() & (1 << i)) =3D=3D 0 { + // input/analog, externally driven + if (self.pins_connected_high.get() & (1 << i)) !=3D 0 { + new_idr_mask |=3D 1 << i; + new_idr |=3D 1 << i; + } else { + new_idr_mask |=3D 1 << i; + new_idr &=3D !(1 << i); + } + } else { + // input/analog, not driven: pull-up/down or floating + if self.is_input_pullup(i) { + new_idr |=3D 1 << i; + new_idr_mask |=3D 1 << i; + } else if self.is_input_pulldown(i) { + new_idr &=3D !(1 << i); + new_idr_mask |=3D 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 =3D 0u32; + for i in 0..GPIO_NUM_PINS { + if (new_idr_mask & (1 << i)) !=3D 0 + && ((new_idr & (1 << i)) !=3D 0) !=3D ((old_idr & (1 << i)= ) !=3D 0) + { + changed |=3D 1 << i; + } + } + PinUpdate { + mask: changed, + levels: new_idr, + } + } +} + +impl_vmstate_struct!( + GpioRegisters, + VMStateDescriptionBuilder::::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, + 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) { + static STM32F1XX_GPIO_OPS: MemoryRegionOps =3D + MemoryRegionOpsBuilder::::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)) !=3D 0 { + let level =3D (upd.levels & (1 << i)) !=3D 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 =3D 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 =3D 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 =3D self.regs.write(offset, data as u32); + self.apply_pin_update(upd); + } + + fn read(&self, offset: hwaddr, size: u32) -> u64 { + let value =3D self.regs.read(offset); + trace::trace_stm32f1xx_gpio_read(offset, u64::from(value), size); + u64::from(value) + } +} + +impl ObjectImpl for Stm32f1xxGpioState { + type ParentType =3D SysBusDevice; + + const CLASS_INIT: fn(&mut Self::Class) =3D Self::Class::class_init::; + const INSTANCE_INIT: Option)> =3D Some(Self= ::init); + const INSTANCE_POST_INIT: Option =3D Some(Self::post_init); +} + +impl DeviceImpl for Stm32f1xxGpioState { + const VMSTATE: Option> =3D Some(VMSTATE_STM32= F1XX_GPIO); +} + +impl SysBusDeviceImpl for Stm32f1xxGpioState {} + +impl ResettablePhasesImpl for Stm32f1xxGpioState { + const HOLD: Option =3D Some(Self::reset_hold); +} + +// Migration: register block only; IRQ links are reconstructed on wire-up. +const VMSTATE_STM32F1XX_GPIO: VMStateDescription =3D + VMStateDescriptionBuilder::::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; --=20 2.53.0 From nobody Mon Sep 28 02:05:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1785077572; cv=none; d=zohomail.com; s=zohoarc; b=jXumd46KhxNk5MX+heoqoeilvIZW6VNhWfQvkDQ6tVpuWqu8XOdw97LBh5fPbUjPBCQDsBGjRDGHoVxhwsvSxHwlk5s7U1Vmn+bBvlQshtr+kVNEkUKT6OnW+JsebTlQHaztVsPyQ6kKGPifcM+4ZHIQiugLHS4CFTJHuIEjulI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785077572; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=FCW4PvYCh0nfjidNnYDFUOliq890MpmlX0B+4xSIUTs=; b=Tz49oJOPwRHQkRvZgR9sUKEIlOg/H7O8Siq3Sy6p8mLsXmM29jsdcoV4Pve0A0YmQbV4NdTM4NkdA37NGqtdbsL+r77c5/1X/Obrw3QhURJh5r//JzvGSc45P25mrqldAHgHPakJk45RjWSExTCD46X2c+yNROX3DvWlhYOY1sc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<163wangjack@gmail.com> (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785077572986555.5088500737448; Sun, 26 Jul 2026 07:52:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wo0Cy-0005wu-Uf; Sun, 26 Jul 2026 10:52:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Co-0005te-LM for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:55 -0400 Received: from mail-pl1-x631.google.com ([2607:f8b0:4864:20::631]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Cl-00060L-3t for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:54 -0400 Received: by mail-pl1-x631.google.com with SMTP id d9443c01a7336-2cf50c6f235so23078465ad.0 for ; Sun, 26 Jul 2026 07:51:50 -0700 (PDT) Received: from localhost.localdomain ([82.40.42.115]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cfde81d7f5sm20934195ad.83.2026.07.26.07.51.44 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 26 Jul 2026 07:51:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785077510; x=1785682310; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=FCW4PvYCh0nfjidNnYDFUOliq890MpmlX0B+4xSIUTs=; b=K+kjkcvyA495BUndzLI0vlEwUq9yLIDzRq/ELzy+35wVYXeXaAM4lYYIAsU9Yb4ngJ Ryd6JKwwAh2sA9iBBBOqwtSUbBM3Wj0ifotw+a4lChaync7Wka6/bIDlMTSJEZxeDfSI PYJcVjwEh1cMdxL4ColYotQ2L+X7+x4KPArSG8Llv9AMekuZHhYV5EUbYyHS3LtmlgWw s/fWT3hkfWw2UeRV8aFtwDZ3KPY+rclt/ggcckw7CPIh8xYZkZnIhMEvxnlidq3I0wuy AijolQWdoKyljFMh6f8xMTH6vYdg7N4RJQUXoGJ5hffIIi8PzusOLai9yKbFb0+i5J3o hqig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785077510; x=1785682310; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=FCW4PvYCh0nfjidNnYDFUOliq890MpmlX0B+4xSIUTs=; b=UisHnrp3qz5tTie82y+MsX6zeQvgkAN3HZAu/VyYWaZgMNIbfrG5HPMfN9F80wVlpv e1eutDlsSFCjNubkpoPoC2m1knRhgb9CDdNJ+v3tG0kT8umCaokpjdYpQMCC5gfKLS5R Wl0xP/QKf4sFpmXoor0tmHIhlRvDj7dljzvgpk8VL/iGHrfuYv3oFtSneqa8VPES9DhQ Q8OEoCFwnUv1ISocajssQpmfPOxA4OA/K0aI1aJWLueC0I4OgYggW5qI/M8jdbn7cBjz h+jw24HuGlut8G023ZDdmEM9EgNGJHR5KoVR2bGdK1WSlKddPRm9F9VPWgoQTr6e0H/4 +37w== X-Gm-Message-State: AOJu0YyuFBqErYBGwsQJ1Av0WRu6PyufnwExMB7flvbcqCGBE5fGJL6o LVlXbL7zmOmMlfPysr03DIRjilkcQCMpbjKJcAjYPmGLIiMs/ziheNSjFKCQmQ== X-Gm-Gg: AR+sD12oRUjIVF9mm3foNX5XcFEZn/4LRxrodoWiZmQnYVNmrmqaF1BtLzcqqKO/Hro 3ylVG+ucPfXG4zL0H/MeUgcs+cQ6EHczWEPb3Vb46HTHKgeUUrGQ4r5qbrPKdOC30pn5XSYTZRB bvTVYJJw5ni6+Q12KA/vRGgOFw00KvrUeuLSIL0XjqBz6YBqJguRP0EBEKLs6pERl195pC1Cj/Q A9ElPh4/ZCJ5BpchlmDAnQhcLRmCZUJuyGqNGhXdrR28md3Oi7c1CPSONafAZzBIS7+TWj6TTBd G++d7puEU8wsVWh7V1uID915l4KPYi5M24ZROyuan8Gik5ox2S/XMxKnVyiCWscpW9NkE2foepj d41a4zIt7krNVZG8EDqEddXNLdJV9gbSMLVLR0JTGZG0Il9CdDIwgnvMmc22c2aDFegYeqFD6nn JGfGI8Z37McHaMDKKYPJQqpAa1 X-Received: by 2002:a17:902:cec3:b0:2cf:4339:aaa with SMTP id d9443c01a7336-2cfde7aa806mr51642055ad.12.1785077509760; Sun, 26 Jul 2026 07:51:49 -0700 (PDT) From: Jack Wang <163wangjack@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , Alistair Francis , Fabiano Rosas , Laurent Vivier , qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com, Jack Wang <163wangjack@gmail.com> Subject: [RFC PATCH v1 2/5] rust: add hw/gpio crate to the workspace Date: Sun, 26 Jul 2026 22:51:16 +0800 Message-ID: <20260726145119.19881-3-163wangjack@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com> References: <20260726145119.19881-1-163wangjack@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::631; envelope-from=163wangjack@gmail.com; helo=mail-pl1-x631.google.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_STARTS_WITH_NUMS=0.738, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1785077575642158500 Content-Type: text/plain; charset="utf-8" Register the new stm32f1xx_gpio crate in the Rust workspace and hook its meson build and Kconfig into rust/hw so it is compiled into the softmmu build when Rust is enabled. Signed-off-by: Jack Wang <163wangjack@gmail.com> --- rust/Cargo.lock | 17 ++++++++++++- rust/Cargo.toml | 18 ++++++++++++++ rust/hw/Kconfig | 1 + rust/hw/gpio/Cargo.toml | 28 +++++++++++++++++++++ rust/hw/gpio/meson.build | 54 ++++++++++++++++++++++++++++++++++++++++ rust/hw/meson.build | 1 + 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 rust/hw/gpio/Cargo.toml create mode 100644 rust/hw/gpio/meson.build diff --git a/rust/Cargo.lock b/rust/Cargo.lock index cbb3ca15f7..2daadaaf15 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -184,7 +184,6 @@ dependencies =3D [ "glib-sys", "migration-sys", "qom-sys", - "system-sys", "util-sys", ] =20 @@ -387,6 +386,21 @@ version =3D "1.15.1" source =3D "registry+https://github.com/rust-lang/crates.io-index" checksum =3D "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6f= bd03" =20 +[[package]] +name =3D "stm32f1xx_gpio" +version =3D "0.1.0" +dependencies =3D [ + "bql", + "common", + "glib-sys", + "hwcore", + "migration", + "qom", + "system", + "trace", + "util", +] + [[package]] name =3D "syn" version =3D "2.0.104" @@ -431,6 +445,7 @@ version =3D "0.1.0" dependencies =3D [ "common", "glib-sys", + "hwcore-sys", "migration-sys", "qom-sys", "util-sys", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0d24eb84e1..7661205d3c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,9 +1,27 @@ [workspace] resolver =3D "2" members =3D [ + "bindings/chardev-sys", + "bindings/hwcore-sys", + "bindings/migration-sys", + "bindings/qom-sys", + "bindings/system-sys", + "bindings/util-sys", + "bits", + "bql", + "chardev", + "common", "hw/char/pl011", + "hw/core", + "hw/gpio", "hw/timer/hpet", + "migration", + "qemu-macros", + "qom", + "system", "tests", + "trace", + "util", ] =20 [workspace.package] diff --git a/rust/hw/Kconfig b/rust/hw/Kconfig index 36f92ec028..ba1297ca2d 100644 --- a/rust/hw/Kconfig +++ b/rust/hw/Kconfig @@ -1,3 +1,4 @@ # devices Kconfig source char/Kconfig +source gpio/Kconfig source timer/Kconfig diff --git a/rust/hw/gpio/Cargo.toml b/rust/hw/gpio/Cargo.toml new file mode 100644 index 0000000000..5c6eade306 --- /dev/null +++ b/rust/hw/gpio/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name =3D "stm32f1xx_gpio" +version =3D "0.1.0" +description =3D "STM32F1xx GPIO device model for QEMU" +resolver =3D "2" +publish =3D false + +edition.workspace =3D true +homepage.workspace =3D true +license.workspace =3D true +repository.workspace =3D true +rust-version.workspace =3D true + +[dependencies] +glib-sys.workspace =3D true + +# QEMU Rust foundations crate +common =3D { path =3D "../../common" } +util =3D { path =3D "../../util" } +bql =3D { path =3D "../../bql" } +migration =3D { path =3D "../../migration" } +qom =3D { path =3D "../../qom" } +system =3D { path =3D "../../system" } +hwcore =3D { path =3D "../../hw/core" } +trace =3D { path =3D "../../trace" } + +[lints] +workspace =3D true diff --git a/rust/hw/gpio/meson.build b/rust/hw/gpio/meson.build new file mode 100644 index 0000000000..35da59991f --- /dev/null +++ b/rust/hw/gpio/meson.build @@ -0,0 +1,54 @@ +_libstm32f1xx_gpio_rs =3D static_library( + 'stm32f1xx_gpio', + files( + 'src/lib.rs', + 'src/gpio.rs', + 'src/bindings.rs', + ), + override_options: ['rust_std=3D2021', 'build.rust_std=3D2021'], + rust_abi: 'rust', + link_with: [ + _util_rs, + _migration_rs, + _bql_rs, + _qom_rs, + _system_rs, + _hwcore_rs, + _trace_rs, + ], + dependencies: [ + common_rs, + glib_sys_rs, + ], +) + +rust_devices_ss.add(when: 'CONFIG_X_STM32F1XX_GPIO_RUST', if_true: [declar= e_dependency( + link_whole: [_libstm32f1xx_gpio_rs], + variables: {'crate': 'stm32f1xx_gpio'}, +)]) + +# Dependency usable by the in-crate integration test below. +stm32f1xx_gpio_rs =3D declare_dependency(link_with: [_libstm32f1xx_gpio_rs= ], + dependencies: [common_rs, qom_rs, hwcore_rs, bql_rs, migration_rs, + system_rs, util_rs]) + +# Native Rust unit tests for the pure register logic (GpioRegisters). These +# need no qtest and no machine boot: they construct GpioRegisters directly= and +# only require the BQL mock (bql::start_test). Run with: +# meson test --suite rust rust-stm32f1xx-gpio-integration +test('rust-stm32f1xx-gpio-integration', + executable( + 'rust-stm32f1xx-gpio-integration', + files('tests/tests.rs'), + override_options: ['rust_std=3D2021', 'build.rust_std=3D2021'], + 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/meson.build b/rust/hw/meson.build index 9749d4adfc..d6b273170e 100644 --- a/rust/hw/meson.build +++ b/rust/hw/meson.build @@ -1,2 +1,3 @@ subdir('char') +subdir('gpio') subdir('timer') --=20 2.53.0 From nobody Mon Sep 28 02:05:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1785077573; cv=none; d=zohomail.com; s=zohoarc; b=iZ7d5SnXabRl4zMzbRpXjgihM8hewfzEyANzK7SzpzATAJD4nj7lC1/KNIG3tttbP/dghHFo0Mma97LHUxTG1d8OrCFEF9Fts3s6Zpncpe48Ar1BhiWCvpbPosEaQAeagjb2Hyohu/Rvgyi5ln935CMBBDU4NXfAbvLNabbd+RY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785077573; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=IjwguSNnxaa5a5wI2q+FKJUSG7PlKND5S7DmrRXF5ds=; b=McFq2sBJFCBLWWl1mqqnQe25u0ukX/+VtooRwvsHz3YnRcN6VPQcEyn5UjbU7uV9xD/BLEndY+Lj9Ji88PhE+6ClbMJ6aW59uj2fMJuDrIfgiN14WvY2V/cZG+RWdxBopUN5NGwZZU1YrAGXKzy6k+/J2umYP2SRqs9D06C7LFs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<163wangjack@gmail.com> (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 178507757301080.87608691120045; Sun, 26 Jul 2026 07:52:53 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wo0DB-0005yj-F6; Sun, 26 Jul 2026 10:52:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Cr-0005v0-Vm for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:58 -0400 Received: from mail-pl1-x62f.google.com ([2607:f8b0:4864:20::62f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Cq-000616-6J for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:51:57 -0400 Received: by mail-pl1-x62f.google.com with SMTP id d9443c01a7336-2ca64c3ce5fso22786685ad.3 for ; Sun, 26 Jul 2026 07:51:55 -0700 (PDT) Received: from localhost.localdomain ([82.40.42.115]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cfde81d7f5sm20934195ad.83.2026.07.26.07.51.50 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 26 Jul 2026 07:51:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785077514; x=1785682314; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=IjwguSNnxaa5a5wI2q+FKJUSG7PlKND5S7DmrRXF5ds=; b=g2r/mpo+SbAutMyp/LnCUBxAqXIwMkjeaFFUhL+OMUJOAs+HQ1nF54SpP0CP3gagdN m2XbzJ3DLd6NAw3QxnGoCA8S2OavFSEqUIbG+x4NjucoUXwBsNgdJbpZ06gQbjS3hsCU lwO9sINGntRSAzbXJlMPN87FHzP/y+TEqGprAqHdoJtFDNReegyd+IndDLUSABr2XtwN 0EUHlExvsDUh6SVx8uiqzSFYR+fuhDiXXV73G99jloUo/4vco3wkcbxhNetH4m3zogys QgxCNUGzW9cI+SzNN32u5IYpt/dwqCz1rHahzcjh3tJ6c7POI7WNr/wZcvcCEfVXHIrH pCYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785077514; x=1785682314; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=IjwguSNnxaa5a5wI2q+FKJUSG7PlKND5S7DmrRXF5ds=; b=OoySREznCJMlyUCidVVDJyxkueVpSn7McivjKb974CFioE8TF8FopCAN06BwE0tGv8 odtF+JIAuV23FI36NKQS3mx82g97K12XQ+va0VNMu/2ArtFhLzZ0qitxAKGRQrhIbrOw frsYEnov9kdJ7dqOr2R3oKpT9lApc1KCWbHvjYvMtrCXlHLWwkcgbj6MKNxBpm9s83Cf rAX6+Rdla0Z+FFL9Uf4TncHmQhM0xxZUq5HDT96Il7KFEfoCH7+c2wQcrO5lGhAAaTjw jUVtxwu6xrCv5Q1Fz/IVwcg9FfTCW4XIoRwAzSv63cDVHauR3ET/VTfMZjGEgr5Hu98G Hbjw== X-Gm-Message-State: AOJu0YyPszeF3qrPLWEV++DdEaq0TyZ+Or0MhvIlABbM8/EDhixq7r/f ughigY7QF4Jht0b8kYJdvH57ryUzZHHnG23Gto/4/Yr4OprlRWcSMdXolzlZOw== X-Gm-Gg: AR+sD12ZbX8hrNN4MIBd9wjJRqAwTkGF/M404Hpz+al2TyeUt3vT8kLv3tfLtQXv2sL /0FhBbfsVw1q1h29aV4O3GrbjNf5Y1FHIuFaLvyWXvAXLxohS1L5cW0ZScVQFiqgZPvg/zql+AS vHL0lr4R4O8pB0O6A3J+ab9l3qdzP4kTdeEH2QtPlcsQcGgXgkiVWujypCnTNBfrc/I4SqEq/FY p61Cg87qfrZh4wQNUTeX/SeoMR9jsjegMgXO0VeGBLK9TroqLOLjbZs8eobaor0gthGUPLem44u 9AwmGvbIx1hLrkoVFVuMMLeB97t813qw979MK/VI/Fbgt/sg/SU3NETerKqLe6R24PeCzTMwMav KaNWDC/1ghxps/KVuGQFUUsvOaOc6l2TyuWDLxGghdeGzt9Q5Ysm52xJPcKx4s4vmcu+E9UBF8d yVWwc5L4FRyQdc3A== X-Received: by 2002:a17:903:2c0e:b0:2ca:ef16:8e8 with SMTP id d9443c01a7336-2cfde825055mr53974475ad.29.1785077514288; Sun, 26 Jul 2026 07:51:54 -0700 (PDT) From: Jack Wang <163wangjack@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , Alistair Francis , Fabiano Rosas , Laurent Vivier , qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com, Jack Wang <163wangjack@gmail.com> Subject: [RFC PATCH v1 3/5] hw/arm/stm32f103: instantiate and map GPIO ports Date: Sun, 26 Jul 2026 22:51:17 +0800 Message-ID: <20260726145119.19881-4-163wangjack@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com> References: <20260726145119.19881-1-163wangjack@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::62f; envelope-from=163wangjack@gmail.com; helo=mail-pl1-x62f.google.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_STARTS_WITH_NUMS=0.738, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1785077575648158500 Content-Type: text/plain; charset="utf-8" Create the seven GPIO ports GPIOA..GPIOG by name via qdev_new() and map them at their APB2 base addresses, removing the corresponding unimplemented-device stubs so the real devices back that range. The Rust GPIO type is only present in a Rust-enabled build, so the ports are created behind an object_class_by_name() check; with --disable-rust the type is absent and the range is simply left unbacked. Signed-off-by: Jack Wang <163wangjack@gmail.com> --- hw/arm/Kconfig | 1 + hw/arm/stm32f103_board.c | 2 +- hw/arm/stm32f103_soc.c | 54 +++++++++++++++++++++++++++++----- include/hw/arm/stm32f103_soc.h | 9 ++++++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 66aed6edd7..7aa194c64a 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -410,6 +410,7 @@ config STM32F103_SOC select STM32F2XX_USART select STM32F2XX_SPI select STM32F1XX_ADC + select STM32F1XX_GPIO if HAVE_RUST =20 config B_L475E_IOT01A bool diff --git a/hw/arm/stm32f103_board.c b/hw/arm/stm32f103_board.c index a740483575..fb5041e484 100644 --- a/hw/arm/stm32f103_board.c +++ b/hw/arm/stm32f103_board.c @@ -26,7 +26,7 @@ =20 #include "qemu/osdep.h" #include "qapi/error.h" -#include "hw/boards.h" +#include "hw/core/boards.h" #include "hw/core/qdev-properties.h" #include "hw/core/qdev-clock.h" #include "qemu/error-report.h" diff --git a/hw/arm/stm32f103_soc.c b/hw/arm/stm32f103_soc.c index e76640ec03..75fc99a554 100644 --- a/hw/arm/stm32f103_soc.c +++ b/hw/arm/stm32f103_soc.c @@ -24,12 +24,16 @@ =20 #include "qemu/osdep.h" #include "qapi/error.h" +#include "qom/object.h" #include "system/address-spaces.h" #include "system/system.h" #include "hw/arm/stm32f103_soc.h" #include "hw/core/qdev-clock.h" #include "hw/misc/unimp.h" =20 +/* QOM type name of the Rust GPIO device (rust/hw/gpio/src/gpio.rs) */ +#define TYPE_STM32F1XX_GPIO_RUST "stm32f1xx-gpio-rust" + #define RCC_ADDR 0x40021000 =20 /* USART addresses: USART1, USART2, USART3, UART4, UART5 */ @@ -47,6 +51,12 @@ static const uint32_t adc_addr[] =3D { 0x40012400, 0x40012800, 0x40013C00 }; =20 +/* GPIO addresses: GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG */ +static const uint32_t gpio_addr[] =3D { + 0x40010800, 0x40010C00, 0x40011000, 0x40011400, 0x40011800, 0x40011C00, + 0x40012000 +}; + static const int usart_irq[] =3D { 37, 38, 39, 52, 53 }; static const int spi_irq[] =3D { 35, 36, 51 }; #define ADC_IRQ 18 @@ -199,6 +209,37 @@ static void stm32f103_soc_realize(DeviceState *dev_soc= , Error **errp) qdev_get_gpio_in(DEVICE(&s->adc_irqs), i)); } =20 + /* + * GPIO ports GPIOA..GPIOG. + * + * These are implemented by the Rust device "stm32f1xx-gpio-rust", cre= ated + * by name with qdev_new() and mapped at its APB2 base address. On ST= M32F1 + * a GPIO port has no dedicated NVIC line of its own: pin-change inter= rupts + * are delivered through the EXTI controller (not yet modelled), so no + * sysbus_connect_irq() to the NVIC is done here. + * + * The GPIO port is only provided by the Rust build; when QEMU is built + * with --disable-rust the type is not registered, so skip creating the + * ports and leave the address range unbacked. + */ + if (object_class_by_name(TYPE_STM32F1XX_GPIO_RUST)) { + for (i =3D 0; i < STM32F103_NUM_GPIOS; i++) { + g_autofree char *name =3D g_strdup_printf("gpio%c", 'a' + i); + s->gpio[i] =3D qdev_new(TYPE_STM32F1XX_GPIO_RUST); + /* + * Give each port a stable QOM name ("/machine/soc/gpioa"..) so + * tests and the monitor can reach it and its gpio-out lines by + * path. + */ + object_property_add_child(OBJECT(s), name, OBJECT(s->gpio[i])); + busdev =3D SYS_BUS_DEVICE(s->gpio[i]); + if (!sysbus_realize_and_unref(busdev, errp)) { + return; + } + sysbus_mmio_map(busdev, 0, gpio_addr[i]); + } + } + /* * Unimplemented peripherals -- STM32F103 devices that do not yet * have QEMU models. @@ -231,13 +272,12 @@ static void stm32f103_soc_realize(DeviceState *dev_so= c, Error **errp) /* APB2 peripherals */ create_unimplemented_device("AFIO", 0x40010000, 0x400); create_unimplemented_device("EXTI", 0x40010400, 0x400); - create_unimplemented_device("GPIOA", 0x40010800, 0x400); - create_unimplemented_device("GPIOB", 0x40010C00, 0x400); - create_unimplemented_device("GPIOC", 0x40011000, 0x400); - create_unimplemented_device("GPIOD", 0x40011400, 0x400); - create_unimplemented_device("GPIOE", 0x40011800, 0x400); - create_unimplemented_device("GPIOF", 0x40011C00, 0x400); - create_unimplemented_device("GPIOG", 0x40012000, 0x400); + /* + * GPIOA..GPIOG (0x40010800..0x40012000) are real devices mapped in the + * GPIO loop above when the Rust build is used, so they must NOT be + * registered as unimplemented devices here: overlapping MemoryRegions= at + * the same address abort at runtime. + */ create_unimplemented_device("timer[1]", 0x40012C00, 0x400); create_unimplemented_device("timer[8]", 0x40013400, 0x400); create_unimplemented_device("timer[9]", 0x40014C00, 0x400); diff --git a/include/hw/arm/stm32f103_soc.h b/include/hw/arm/stm32f103_soc.h index 260f3695c0..8d78360ce0 100644 --- a/include/hw/arm/stm32f103_soc.h +++ b/include/hw/arm/stm32f103_soc.h @@ -40,6 +40,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(STM32F103State, STM32F103_SOC) #define STM32F103_NUM_USARTS 5 #define STM32F103_NUM_SPIS 3 #define STM32F103_NUM_ADCS 3 +#define STM32F103_NUM_GPIOS 7 =20 /* High-density STM32F103: 512 KB Flash, 64 KB SRAM */ #define STM32F103_FLASH_BASE_ADDRESS 0x08000000 @@ -58,6 +59,14 @@ struct STM32F103State { OrIRQState adc_irqs; STM32F1XXADCState adc[STM32F103_NUM_ADCS]; =20 + /* + * GPIOA..GPIOG are implemented by the Rust device "stm32f1xx-gpio-rus= t". + * That device is created by name via qdev_new() (pointer-based mounti= ng) + * rather than embedded by value, so no C struct definition is required + * here and no struct-size coupling with the Rust side is needed. + */ + DeviceState *gpio[STM32F103_NUM_GPIOS]; + MemoryRegion sram; MemoryRegion flash; MemoryRegion flash_alias; --=20 2.53.0 From nobody Mon Sep 28 02:05:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1785077590; cv=none; d=zohomail.com; s=zohoarc; b=aKfvE8+0AnDYa4lpTA6xzmWe7+eBjqWg4e+v4tcEZZJrz9Xy4nYL/pJO2f6/KVpTtAMZAnWE6ZIQc9WoSl2COzxYo1EJDG6sktZR7t2QnaErxGMII6sX8NhZRFkh2M/5+gvZjCa/Gn5yincKuPXTeSg0+lXol8sSLtNZBEFWnmU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785077590; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=ZnuUzF0dJIBznvvjx9kVt4TXwDvHfe0DU2pEHtYFjzE=; b=P4bLzFFgZa+PtOz7wp+BRI+95osOCaP+PmHxYVLcm9Y27/yVU7Y/TZXMSKBK3tFY457o5xYLf8p2DJEBXeeG58EOuMlH1VIWmuxMuE9eywqgUxN8s1C3PyBp9XEekXm1vOIGlgAY8eaKsg7Lx/29d6Ef7jNbfEFNPZ393vhjyk8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<163wangjack@gmail.com> (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785077590472376.5668145515907; Sun, 26 Jul 2026 07:53:10 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wo0DJ-000649-GJ; Sun, 26 Jul 2026 10:52:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0D3-0005yY-2F for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:52:11 -0400 Received: from mail-pl1-x62a.google.com ([2607:f8b0:4864:20::62a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0Cz-00062G-4A for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:52:08 -0400 Received: by mail-pl1-x62a.google.com with SMTP id d9443c01a7336-2cc61541f8cso32295795ad.0 for ; Sun, 26 Jul 2026 07:52:04 -0700 (PDT) Received: from localhost.localdomain ([82.40.42.115]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cfde81d7f5sm20934195ad.83.2026.07.26.07.51.54 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 26 Jul 2026 07:52:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785077524; x=1785682324; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ZnuUzF0dJIBznvvjx9kVt4TXwDvHfe0DU2pEHtYFjzE=; b=jTp8PVG01HB7mgsj2TPgmWmjM/01brADE1qkizvUjZDqrbUsas50aRZfZObsYxFlJQ G5oLZLyBaHKHWfYU/QeCxJC7o+3rQQzSHFdAGmt8q1eJ079WhQAEGmqfCFq9tXI7MmBj 2TC8QWOlBT6Q+HbxD9cQgCAcfKvycYqPplzLN9VWmhGzilcBaxrrC1ys2cOjfuord2iV LAiRHNY434pbLJ2vQUyFPjY7tw9VtJeD9+lv8JEyk+5xmaLPZQ0LGECEBdkfANt7/46M EpD3kCN/CMyv7+iSoqTV3n5y07OCtjSkPJHYpm5RkiNXNbFGrab72Q4Cxg/A0TKY82ec MJ4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785077524; x=1785682324; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=ZnuUzF0dJIBznvvjx9kVt4TXwDvHfe0DU2pEHtYFjzE=; b=iZVdQvIwKseRohPD28m9R5Ueut2Z/8a/5UVU0+CFVZ6N13dNwrilShfWr19Y73Klee 0r1kHdx8aaKqspGNsVV3SJbIG/RiPniUw2HPc/sq9h7YjsbtjsFKYJ/HmzqHYP8tJr6f M0dJFy0Qsf1HUCSUZ/eqHL3tHJmoJ8mndtL2ucWiiMhaBvwgga3JylDx0/cSIkj306DK W4LKOAl2T+mAVYKiza8psatZKM8LGV4AOf+Y78yFdGeI7gR44vQaEoyO5DdmRkUuzRQ3 CIdFEthqNnErkeMw90BYPOt0AJG3MU+sIr5g2g0ZuNYzlmb/dvY0TkB0SA5dbak+vrd7 nO1Q== X-Gm-Message-State: AOJu0YyyP8BpZAtH8y+/+H/+gv6pl6hSrjTdbRNQIOI4wPfPQbCio6tz A2c+F8rZJvMTqA3gYfvBLyqFxRlzxPstEd0QjvmPzYMhLhtoWcUcT09b805qEg== X-Gm-Gg: AR+sD11S+MFX6zZNVAgTvvo+fOLxIr4I54rXo6lr/jE/WOMnX4dBB6CrnuVzlw4j79g GlgYFEw4LsACQ46gpzHCaC3m69vK1w9mKq7dSOv2pnjKT6e+9is3lZOPzcgyUBaWdcv26wo9ZT7 IAUtaf6Ygx9gBztoVgh6eyjEjiPRdAs2jBgTCqeJd6XaD64bQgwyiOHYbw3si3A/EJwg9lOcIUI ZEArF5+YsNbJnBc+e0sBiPKEAHlUMpOCLZTrRZSl0YO5r8qntKwQCHqdG33npbgTUlzcgW2fWUQ gthwYF46VukLUwctJRX+MDMAmDbsmN3wXXFX6C7zc+/eDQiN+laNPZe9NzdbMvB/nSwTNebKmZF tyCi2DnKS46qgfWbdHeVxxKJ2HgzXGdJWnIipgD7/xEUHNqvWMNdlf9HhsOccKnlSY4L3s7OhNd 4IkFOf0sY6REsG9gNg1P/cNBx1 X-Received: by 2002:a17:902:d2c6:b0:2cf:7de1:4a00 with SMTP id d9443c01a7336-2cfd70c0f61mr70085545ad.1.1785077523444; Sun, 26 Jul 2026 07:52:03 -0700 (PDT) From: Jack Wang <163wangjack@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , Alistair Francis , Fabiano Rosas , Laurent Vivier , qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com, Jack Wang <163wangjack@gmail.com> Subject: [RFC PATCH v1 4/5] tests/qtest: add stm32f103 GPIO test Date: Sun, 26 Jul 2026 22:51:18 +0800 Message-ID: <20260726145119.19881-5-163wangjack@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com> References: <20260726145119.19881-1-163wangjack@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::62a; envelope-from=163wangjack@gmail.com; helo=mail-pl1-x62a.google.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_STARTS_WITH_NUMS=0.738, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1785077591602158500 Content-Type: text/plain; charset="utf-8" Add a qtest that drives the STM32F103 GPIO ports over the qtest socket, covering the RM0008 reset state, BSRR/BRR set/reset semantics and the output-line behaviour for push-pull and open-drain configurations. Signed-off-by: Jack Wang <163wangjack@gmail.com> --- tests/qtest/meson.build | 4 + tests/qtest/stm32f103_gpio-test.c | 328 ++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 tests/qtest/stm32f103_gpio-test.c diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index e154abb05d..4f407b68fe 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -235,6 +235,9 @@ qtests_stm32l4x5 =3D \ 'stm32l4x5_gpio-test', 'stm32l4x5_usart-test'] =20 +qtests_stm32f103 =3D \ + ['stm32f103_gpio-test'] + qtests_arm =3D \ (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \ (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-= dualtimer-test'] : []) + \ @@ -250,6 +253,7 @@ qtests_arm =3D \ (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_STM32F103_SOC') ? qtests_stm32f103 := []) + \ (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-= test'] : []) + \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \ diff --git a/tests/qtest/stm32f103_gpio-test.c b/tests/qtest/stm32f103_gpio= -test.c new file mode 100644 index 0000000000..97ef7dd3e8 --- /dev/null +++ b/tests/qtest/stm32f103_gpio-test.c @@ -0,0 +1,328 @@ +/* + * QTest testcase for STM32F103 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 RM0008 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 7 +#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 GPIO_F 0x40011C00 +#define GPIO_G 0x40012000 + +/* Register offsets (RM0008). */ +#define CRL 0x00 +#define CRH 0x04 +#define IDR 0x08 +#define ODR 0x0C +#define BSRR 0x10 +#define BRR 0x14 +#define LCKR 0x18 + +/* 4-bit MODE[1:0]+CNF[1:0] pin configurations. */ +#define CFG_INPUT_FLOATING 0x4 /* MODE=3D00 CNF=3D01 (reset default) */ +#define CFG_INPUT_PULL 0x8 /* MODE=3D00 CNF=3D10, ODR selects up/do= wn */ +#define CFG_OUTPUT_PP 0x1 /* MODE=3D01 CNF=3D00, general push-pull= */ +#define CFG_OUTPUT_OD 0x5 /* MODE=3D01 CNF=3D01, general open-drai= n */ + +/* CRL/CRH reset value: every pin floating input. */ +#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 v= alue) +{ + writel(gpio + offset, value); +} + +/* Set the 4-bit config nibble for `pin` in CRL/CRH. */ +static void gpio_set_config(unsigned int gpio, unsigned int pin, uint8_t c= fg) +{ + unsigned int reg =3D (pin < 8) ? CRL : CRH; + unsigned int shift =3D (pin % 8) * 4; + uint32_t mask =3D ~(0xFu << shift); + uint32_t val =3D (gpio_readl(gpio, reg) & mask) | ((uint32_t)cfg << sh= ift); + gpio_writel(gpio, reg, val); +} + +static void gpio_set_odr_bit(unsigned int gpio, unsigned int pin, + uint32_t value) +{ + uint32_t mask =3D ~(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(gp= io)); + return buf; +} + +/* Drive pin `num` of `gpio` to `level` from an external source. */ +static void gpio_set_irq(unsigned int gpio, int num, int level) +{ + qtest_set_irq_in(global_qtest, gpio_path(gpio), NULL, num, level); +} + +/* + * Intercept this port's own gpio-out lines so get_irq(pin) observes them. + * + * qtest only supports intercepting one device's out-GPIOs per session (the + * interception device is global), so every test that observes gpio-out via + * get_irq() must target the SAME port. We use GPIO_A for all of them. Tes= ts + * that only read IDR/ODR over MMIO can use any port and skip interception. + */ +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) +{ + /* + * Scribble over the config/data registers, issue a system reset, and + * check the RM0008 documented reset state is restored: CRL/CRH back to + * 0x44444444 (floating input) and IDR/ODR back to 0. + */ + 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), =3D=3D, CR_RESET); + g_assert_cmphex(gpio_readl(GPIO_A, CRH), =3D=3D, CR_RESET); + g_assert_cmphex(gpio_readl(GPIO_A, IDR), =3D=3D, 0); + g_assert_cmphex(gpio_readl(GPIO_A, ODR), =3D=3D, 0); + + g_assert_cmphex(gpio_readl(GPIO_C, CRL), =3D=3D, CR_RESET); + g_assert_cmphex(gpio_readl(GPIO_C, IDR), =3D=3D, 0); + g_assert_cmphex(gpio_readl(GPIO_C, ODR), =3D=3D, 0); +} + +static void test_output_mode(const void *data) +{ + /* + * Configure a pin as push-pull output and check ODR drives IDR and the + * gpio-out line. Also check ODR writes made while the pin is still an + * input are latched (not discarded), taking effect once switched. + */ + unsigned int pin =3D (uintptr_t)data & 0xF; + uint32_t gpio =3D (uintptr_t)data & ~(GPIO_SIZE - 1); + bool observe =3D (gpio =3D=3D GPIO_A); + + reset_gpio(gpio); + if (observe) { + intercept_out(gpio); + } + + /* Set ODR while floating input: IDR unaffected, no output edge. */ + gpio_set_odr_bit(gpio, pin, 1); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + if (observe) { + g_assert_false(get_irq(pin)); + } + + /* Switch to push-pull output: pin now follows ODR (high). */ + gpio_set_config(gpio, pin, CFG_OUTPUT_PP); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, (1u << pin)); + if (observe) { + g_assert_true(get_irq(pin)); + } + + /* Clear ODR bit: pin goes low. */ + gpio_set_odr_bit(gpio, pin, 0); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + if (observe) { + g_assert_false(get_irq(pin)); + } + + reset_gpio(gpio); +} + +static void test_input_mode(const void *data) +{ + /* + * Configure a pin as floating input and check that an external drive + * (via the gpio-in line) is reflected in IDR and on the gpio-out line. + */ + unsigned int pin =3D (uintptr_t)data & 0xF; + uint32_t gpio =3D (uintptr_t)data & ~(GPIO_SIZE - 1); + bool observe =3D (gpio =3D=3D 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), =3D=3D, (1u << pin)); + if (observe) { + g_assert_true(get_irq(pin)); + } + + gpio_set_irq(gpio, pin, 0); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + if (observe) { + g_assert_false(get_irq(pin)); + } + + reset_gpio(gpio); +} + +static void test_pull_up_down(const void *data) +{ + /* + * In input pull mode (CNF=3D10), ODR selects the resistor: ODR=3D1 pu= lls the + * pin up (IDR=3D1) and ODR=3D0 pulls it down (IDR=3D0), even with no = external + * driver connected. + */ + unsigned int pin =3D (uintptr_t)data & 0xF; + uint32_t gpio =3D (uintptr_t)data & ~(GPIO_SIZE - 1); + + reset_gpio(gpio); + intercept_out(gpio); + + /* Pull-up. */ + gpio_set_odr_bit(gpio, pin, 1); + gpio_set_config(gpio, pin, CFG_INPUT_PULL); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, (1u << pin)); + g_assert_true(get_irq(pin)); + + /* Pull-down. */ + gpio_set_odr_bit(gpio, pin, 0); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + g_assert_false(get_irq(pin)); + + reset_gpio(gpio); +} + +static void test_bsrr_brr(const void *data) +{ + /* + * BSRR: low 16 bits set ODR bits, high 16 bits reset them, with set + * taking priority. BRR resets ODR bits. + */ + unsigned int pin =3D (uintptr_t)data & 0xF; + uint32_t gpio =3D (uintptr_t)data & ~(GPIO_SIZE - 1); + + reset_gpio(gpio); + + gpio_writel(gpio, BSRR, (1u << pin)); + g_assert_cmphex(gpio_readl(gpio, ODR), =3D=3D, (1u << pin)); + + gpio_writel(gpio, BSRR, (1u << (pin + 16))); + g_assert_cmphex(gpio_readl(gpio, ODR), =3D=3D, 0); + + gpio_writel(gpio, BSRR, (1u << pin)); + g_assert_cmphex(gpio_readl(gpio, ODR), =3D=3D, (1u << pin)); + + gpio_writel(gpio, BRR, (1u << pin)); + g_assert_cmphex(gpio_readl(gpio, ODR), =3D=3D, 0); + + /* Set has priority over reset in BSRR. */ + gpio_writel(gpio, BSRR, (1u << pin) | (1u << (pin + 16))); + g_assert_cmphex(gpio_readl(gpio, ODR), =3D=3D, (1u << pin)); + + reset_gpio(gpio); +} + +static void test_push_pull_disconnect(const void *data) +{ + /* + * A pin driven externally, then reconfigured as push-pull output, mus= t be + * disconnected from the external driver: its IDR follows ODR, not the + * stale external level. + */ + unsigned int pin =3D (uintptr_t)data & 0xF; + uint32_t gpio =3D (uintptr_t)data & ~(GPIO_SIZE - 1); + + reset_gpio(gpio); + + /* Drive high externally as input. */ + gpio_set_config(gpio, pin, CFG_INPUT_FLOATING); + gpio_set_irq(gpio, pin, 1); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, (1u << pin)); + + /* Reconfigure as push-pull output with ODR=3D0: pin must drop to 0. */ + gpio_set_odr_bit(gpio, pin, 0); + gpio_set_config(gpio, pin, CFG_OUTPUT_PP); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + + /* Further external drives are ignored while output push-pull. */ + gpio_set_irq(gpio, pin, 1); + g_assert_cmphex(gpio_readl(gpio, IDR), =3D=3D, 0); + + reset_gpio(gpio); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + qtest_add_func("stm32f103/gpio/reset_values", test_reset_values); + qtest_add_data_func("stm32f103/gpio/output_mode_a5", + (void *)(uintptr_t)(GPIO_A | 5), test_output_mode); + qtest_add_data_func("stm32f103/gpio/output_mode_c13", + (void *)(uintptr_t)(GPIO_C | 13), test_output_mode= ); + qtest_add_data_func("stm32f103/gpio/input_mode_b6", + (void *)(uintptr_t)(GPIO_B | 6), test_input_mode); + qtest_add_data_func("stm32f103/gpio/input_mode_d10", + (void *)(uintptr_t)(GPIO_D | 10), test_input_mode); + qtest_add_data_func("stm32f103/gpio/pull_up_down_a0", + (void *)(uintptr_t)(GPIO_A | 0), test_pull_up_down= ); + qtest_add_data_func("stm32f103/gpio/bsrr_brr_a1", + (void *)(uintptr_t)(GPIO_A | 1), test_bsrr_brr); + qtest_add_data_func("stm32f103/gpio/bsrr_brr_e12", + (void *)(uintptr_t)(GPIO_E | 12), test_bsrr_brr); + qtest_add_data_func("stm32f103/gpio/push_pull_disconnect_g7", + (void *)(uintptr_t)(GPIO_G | 7), + test_push_pull_disconnect); + + qtest_start("-machine stm32f103"); + ret =3D g_test_run(); + qtest_end(); + + return ret; +} --=20 2.53.0 From nobody Mon Sep 28 02:05:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1785077591; cv=none; d=zohomail.com; s=zohoarc; b=Frmw5P73DxzKaFhMXS2zI+4k9Si1zoCJ10UnNAOlR18eJV46kpF36P/rly99tbDoU3A1aVnuvLxXccaojZ64Bw1+P/bndC+GbyOKARMkQMsp/9K/u6+QSoEGw95S8sI85BHaFibRIidCKJ08rDRC6EVyoUmg8/XrIJTpQa59t50= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1785077591; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=QoTWc3iMRVDAE87hZh8x5OJDt+vuk8Y9yYBBrbPBSAo=; b=Y9n++1Fg6i1b/OQry/kzjh6nvM5Odg84hjZ9XX2LiiKyFr5sQSqoClPytaYhpTFMSQ3WcRPn+fn+sZMtKGeRb9gjTDOkOf4g6EpbYn1nv/63/Z845sO1zslJsDzAdyzQiztNgUD6d5FvBdke5OZnTDG+gRbCq3YRbb2k4q3QcB4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<163wangjack@gmail.com> (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1785077591172599.6165571027392; Sun, 26 Jul 2026 07:53:11 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wo0DI-00063o-05; Sun, 26 Jul 2026 10:52:24 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0D8-0005yv-Q1 for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:52:15 -0400 Received: from mail-pl1-x635.google.com ([2607:f8b0:4864:20::635]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <163wangjack@gmail.com>) id 1wo0D6-00063I-Sy for qemu-devel@nongnu.org; Sun, 26 Jul 2026 10:52:14 -0400 Received: by mail-pl1-x635.google.com with SMTP id d9443c01a7336-2ceae1ed204so19242065ad.0 for ; Sun, 26 Jul 2026 07:52:12 -0700 (PDT) Received: from localhost.localdomain ([82.40.42.115]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cfde81d7f5sm20934195ad.83.2026.07.26.07.52.04 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 26 Jul 2026 07:52:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785077531; x=1785682331; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=QoTWc3iMRVDAE87hZh8x5OJDt+vuk8Y9yYBBrbPBSAo=; b=eNsxMuGo1duma8nBqmm+YeJr7cCjCG8R9zVebVFZk1xDQy61mgRIZ/rTeSv62g04il UToa3db+asnmIfy4f0unlnR1yGZUTPN4YnO1xwzAHBgaDJ479KzD9Ub6NBPkuXT7S1n5 ccCjAMHdRz5cQOUCu79UBGUdDPgIk0UOgsuEr8md2kch7zxYt3GF0c5eYgD1akMcpKEC hMN+4yT/JbMmpmNOOcGn/6JTLqyJDawidVZrMiNDoY2mAr+t/DslkuQLizWJVb2iK08K W8dpnrXz2Nh9JMWXSZUjWCqrzQxwCR97FlAx07COxcqDfh68zfjEDnuJzQutV6SxPxRR MFRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785077531; x=1785682331; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=QoTWc3iMRVDAE87hZh8x5OJDt+vuk8Y9yYBBrbPBSAo=; b=daKP9wsIpl/K6mnagwbTyENjlECSR3xmaS4DyyWik0OukXzLD5ljAlDrnvbQJg+A39 2bJSByjXxuUihpj9V3ru8BjPiPpkAbQCgvrJwGucbuLhI9Oh7D89LVkgPJbpae0bKvm2 KFVXHqTNSj457JADGu1T2nmvBbWgrZHWd461HhvvlLWbERHmPMUvGqJShPi3zmY1FYxA FyHruIPWkaTlE5KmY9fXll71XEnjYaVoatek0vs51Wb8m261jEN+YY4+YElAdTKnY3MF LC7KsozvD9Fphd4WDqhJMXXrN6Wa3AXADtzmgek/VTHfP0x+y2MrvNaJMN29lG6fHroy yyyQ== X-Gm-Message-State: AOJu0YxniYa1l899zO9Y+nvIDSzuTOqF91lhuZaiO7NhFrD+F1D1YSHU TxNnVd8reS5GpA1KCO5xaCZILwFT+DemKKV1nFMUGf8J9iucw1SsL/nDosrXfw== X-Gm-Gg: AR+sD11J2ikAZ0S4BaHJJP+nHzPCO12Wv8F+euUCDz2Xw30lcZPEDKnjgPe5rbBJqz7 wPSVXmgBB073mAnJpaF7ozBkYyLnRvYZmEKD1QeyJbQf7w98iJTyHbHgLqiSLjerEMtBEiV4exT VTY0/Nwwo8ZE5kaIArZqRdoZ7gO0kdX9QOSW/u3knummpj8R3w4ikWWKRfTp92sDCd/3yAD76Dg JJgI3DCHm63H3LV9ZtVz5R7yvGUOAROjUwYfefOh5u/8he7PPox8n2kZG2SmAdYnyLoXtkGpgCR keXhE/pRaHFqanITnOfZ23X60HsjTzVjRmONzuv9qZ9AKliGYfjQVRaXJTeuDVSNvR9qoKWftHs HSHwuMWsJrqgyxFl7Ebg4dW/ThHpbX/JCysKwGVGbku/fnwrYjpvMppQ2oXRjwYnx4kHya1joEQ H1EdpQ6uJD5lS39/FprdHpo4iU X-Received: by 2002:a17:903:22cc:b0:2c9:97a7:f543 with SMTP id d9443c01a7336-2cfde885feamr52557135ad.41.1785077531314; Sun, 26 Jul 2026 07:52:11 -0700 (PDT) From: Jack Wang <163wangjack@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , Alistair Francis , Fabiano Rosas , Laurent Vivier , qemu-arm@nongnu.org, qemu-rust@nongnu.org, caojunze424@gmail.com, Jack Wang <163wangjack@gmail.com> Subject: [RFC PATCH v1 5/5] rust/hw/gpio: add native unit tests Date: Sun, 26 Jul 2026 22:51:19 +0800 Message-ID: <20260726145119.19881-6-163wangjack@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260726145119.19881-1-163wangjack@gmail.com> References: <20260726145119.19881-1-163wangjack@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::635; envelope-from=163wangjack@gmail.com; helo=mail-pl1-x635.google.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_STARTS_WITH_NUMS=0.738, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1785077593480158500 Content-Type: text/plain; charset="utf-8" Add native Rust unit tests that link GpioRegisters directly and exercise the CRL/CRH decode and IDR update algorithm in-process, without booting a machine or pulling in the softmmu memory subsystem. Signed-off-by: Jack Wang <163wangjack@gmail.com> --- rust/hw/gpio/tests/tests.rs | 191 ++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 rust/hw/gpio/tests/tests.rs diff --git a/rust/hw/gpio/tests/tests.rs b/rust/hw/gpio/tests/tests.rs new file mode 100644 index 0000000000..ca4d5ed4d6 --- /dev/null +++ b/rust/hw/gpio/tests/tests.rs @@ -0,0 +1,191 @@ +// Native Rust unit tests for the STM32F1xx GPIO register logic. +// +// Copyright 2026, Jack Wang +// Author(s): Jack Wang <163wangjack@gmail.com> +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Unlike tests/qtest/stm32f103_gpio-test.c (which drives the device over a +// socket from an external qemu-system-arm process), these tests link the +// device crate directly and exercise `GpioRegisters` in-process. This giv= es +// fast, native Rust coverage of the CRL/CRH decode + IDR algorithm without +// booting a machine or pulling in the softmmu memory subsystem. +// +// `GpioRegisters` deliberately has no QOM object, no MemoryRegion and no +// InterruptSource, so it can be constructed with `GpioRegisters::new()` a= nd +// called directly. Only the BQL mock (bql::start_test) is required, becau= se +// the register cells are `BqlCell`s. +// +// 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_ODR, RESET_CRH, + RESET_CRL, +}; + +/// Take the mock BQL so `BqlCell` accesses are permitted. Integration tes= ts +/// run with `--test-threads=3D1`, so this is safe to call from every test. +fn regs() -> GpioRegisters { + bql::start_test(); + GpioRegisters::new() +} + +// 4-bit MODE[1:0]+CNF[1:0] pin configurations (RM0008). +const CFG_INPUT_FLOATING: u32 =3D 0x4; // MODE=3D00 CNF=3D01 (reset defaul= t) +const CFG_INPUT_PULL: u32 =3D 0x8; // MODE=3D00 CNF=3D10, ODR selects up/d= own +const CFG_OUTPUT_PP: u32 =3D 0x1; // MODE=3D01 CNF=3D00, general push-pull + +/// Set the 4-bit config nibble for `pin` in CRL/CRH via an MMIO write. +fn set_config(r: &GpioRegisters, pin: usize, cfg: u32) { + let reg =3D if pin < 8 { REG_CRL } else { REG_CRH }; + let shift =3D ((pin % 8) * 4) as u32; + let old =3D r.read(reg); + r.write(reg, (old & !(0xF << shift)) | (cfg << shift)); +} + +fn set_odr_bit(r: &GpioRegisters, pin: usize, value: u32) { + let old =3D r.read(REG_ODR); + r.write(REG_ODR, (old & !(1 << pin)) | (value << pin)); +} + +/// True if pin `pin` is set in a [`PinUpdate`] and its reported level is +/// `level`. +fn pin_changed_to(upd: PinUpdate, pin: usize, level: bool) -> bool { + (upd.mask & (1 << pin)) !=3D 0 && (((upd.levels >> pin) & 1) !=3D 0) = =3D=3D level +} + +#[test] +/// A freshly created register block holds the RM0008 reset values. +fn test_reset_values() { + let r =3D 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] +/// `reset` restores the reset state after the registers are dirtied. +fn test_reset_restores() { + let r =3D 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] +/// Push-pull output: IDR follows ODR; ODR written while input is latched, +/// and the out-pin change is reported. +fn test_output_mode() { + let r =3D regs(); + let pin =3D 5; + + // Set ODR while still a floating input: IDR unaffected, no out change. + let idle =3D set_odr_and_report(&r, pin, 1); + assert_eq!(r.read(REG_IDR), 0); + assert_eq!(idle.mask & (1 << pin), 0); + + // Switch to push-pull output: pin now follows ODR (high), and flips. + let old =3D r.read(REG_CRL); + let to_output =3D 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)); + + // Clear ODR bit: pin goes low, and flips back. + let to_low =3D set_odr_and_report(&r, pin, 0); + assert_eq!(r.read(REG_IDR), 0); + assert!(pin_changed_to(to_low, pin, false)); +} + +/// Helper: write one ODR bit and return the resulting [`PinUpdate`]. +fn set_odr_and_report(r: &GpioRegisters, pin: usize, value: u32) -> PinUpd= ate { + let old =3D r.read(REG_ODR); + r.write(REG_ODR, (old & !(1 << pin)) | (value << pin)) +} + +#[test] +/// Floating input: an external drive is reflected in IDR and reported. +fn test_input_mode() { + let r =3D regs(); + let pin =3D 6usize; + + set_config(&r, pin, CFG_INPUT_FLOATING); + + let drive_high =3D r.gpio_set(pin, 1); + assert_eq!(r.read(REG_IDR), 1 << pin); + assert!(pin_changed_to(drive_high, pin, true)); + + let drive_low =3D r.gpio_set(pin, 0); + assert_eq!(r.read(REG_IDR), 0); + assert!(pin_changed_to(drive_low, pin, false)); +} + +#[test] +/// Input pull mode: ODR selects pull-up (IDR=3D1) vs pull-down (IDR=3D0), +/// with no external driver connected. +fn test_pull_up_down() { + let r =3D regs(); + let pin =3D 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] +/// BSRR sets/resets ODR bits (set has priority); BRR resets ODR bits. +fn test_bsrr_brr() { + let r =3D regs(); + let pin =3D 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] +/// A pin driven externally, then reconfigured as push-pull output, is +/// disconnected: IDR follows ODR, not the stale external level. +fn test_push_pull_disconnect() { + let r =3D regs(); + let pin =3D 7usize; + + // Drive high externally as input. + 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); // now connected + + // Reconfigure as push-pull output with ODR=3D0: pin must drop to 0, + // and the external driver must be disconnected. + 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); // disconnected aga= in + + // Further external drives are ignored while output push-pull. + r.gpio_set(pin, 1); + assert_eq!(r.read(REG_IDR), 0); +} --=20 2.53.0