From 5fb5297c2838bd1fae14828c8254db05198e58a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=BA=86=E4=B8=B0?= Date: Sat, 19 Sep 2026 17:10:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(bsp):=20CH57x=20=E6=9C=80=E5=B0=8F=20BSP?= =?UTF-8?q?=20+=20=E4=B8=8A=E6=9D=BF=E8=87=AA=E6=A3=80=E4=BE=8B=E7=A8=8B(b?= =?UTF-8?q?oard=5Fcheck=5Fch57x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 issue #14 里"只能上板回答"的那批核对点,收敛成**一次烧写就能出答案**的自检例程; 顺带补上 ch57x 家族的最小 BSP(ch583/ch572 的 UART 与 PA 口寄存器同址同偏移,一份够用)。 ## BSP(`src/bsp/ch57x/`) - `uart::configure(clk_hz, baud)` / `putc` / `write_str` + `core::fmt::Write` 出口。 - 寄存器与公式**照官方 EVT 源码**:基址 `0x40003400`(`IER@+1 / FCR@+2 / LCR@+3 / LSR@+5(bit6 发送全空) / THR@+8 / R16_DL@+0x0C / R8_DIV@+0x0E`)、初始化序列同 `UART1_DefInit()`、波特率 `DL = round(clk/(8*baud))`。 - PA 口:`PA_DIR@0x400010A0 / PA_OUT@+08 / PA_CLR@+0C`(LED/示波器打点)。 - 不碰 SAM 解锁/PLL —— 时钟树留给后续(见下"阶段一"为什么先不碰)。 ## 自检例程(`examples/board_check_ch57x.rs`) **阶段一**:按 8/16/24/32/48/60/100 MHz 逐个假设各打一行标记——**哪行可读,复位默认主频 就是那个值**(不配 PLL 时主频未知,只能这样测;这也顺手回答了 env 里的频率假设)。 **阶段二**(把 `CLK_HZ` 改成测出的值后重烧)打印: - 启动头:`_bootvec` 地址 + **魔数回读**(符号地址即它在 flash 的位置),期望 `F3F9BDA9`; - `STK_CTLR`(期望 ch583 `0x2F` / ch572 `0x0F`)、`STK_SR`、`PFIC_IENR[0]` bit12; - **INTSYSCR(自定义 CSR `0x804`)回读**——非 0 说明硬件压栈/嵌套没关,36 字帧前提不成立; - `mcycle` 前后差(0 = 无 Zicntr;本口 `delay_us` 已改用 SysTick,不依赖它); - **软中断自检**:`yield_now()` 前后读 `PFIC ISR[0]` bit14——pending 被硬件取走即 `SWI_IRQn=14` 通路成立,残留则可疑; - **活性测试**:两个同优先级任务各自周期打印计数器与 `tick()`——两者都涨 = 时基、上下文切换、同优先级时间片在真机上成立。 ## 验证(本机无硬件,能做的都做了) - 例程在 **ch583(riscv32imac)** 与 **ch572(riscv32imc)** 两种目标下都编译+链接通过, 启动头产物校验 PASS(三道:ch583-imac/ch583-imc/ch572-imc 里含新增的两个 board_check 构建)。 - `bash ci/gate.sh` 全绿(host 159 / ISR 向量 / 启动头 ×3 / 乒乓 / kernel_tests 24-24 ×3 / smp 9-9); 门禁新增两条 board_check 构建步骤,防止 BSP 腐烂。 - ⚠️ 例程的**输出**只能在真机上看到——本机没有硬件,故"阶段一测出的主频、阶段二的各项读数" 仍是待验项(这正是本 PR 的目的:让有板子的人 5 分钟拿到答案)。 Refs #14 --- README.md | 1 + ci/gate.sh | 7 ++ examples/board_check_ch57x.rs | 170 ++++++++++++++++++++++++++++++++++ src/bsp/ch57x/mod.rs | 35 +++++++ src/bsp/ch57x/uart.rs | 67 ++++++++++++++ src/bsp/mod.rs | 5 + 6 files changed, 285 insertions(+) create mode 100644 examples/board_check_ch57x.rs create mode 100644 src/bsp/ch57x/mod.rs create mode 100644 src/bsp/ch57x/uart.rs diff --git a/README.md b/README.md index 6838db6..46d7824 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ - [x] QEMU 执行级:`ci/gate.sh` 在 virt 机真跑内核——`qemu_pingpong` 200 轮乒乓 + `qemu_kernel_tests` 24 项全内核机制自测(抢占/时间片/阻塞类 IPC/定时器/时基/堆/总线/任务回收/可重入锁/优先级继承/完整 PI 多锁/PCP 天花板阻塞/PI 交叉持锁死锁确认/TLSF 碎片共限/TLSF 分配确定性/tickless 错峰唤醒/远期期限单次到点/UART RX 外部中断冻眠唤醒/早醒弹墙钟拍账/噪声风暴停留 idle 不漂移),全绿自退出;另有 tlsf 全局后端门禁(24/24 不变 = 分配器换引擎对内核透明)与 `qemu_smp` 9 项多核调度门禁 - [x] 产物级:`ci/gate.sh` 另校验 `ch583`(QingKe V4A,无 QEMU 机器可跑)的 ROM 启动头——flash `0x00..0x17` 的入口跳转 + 向量表第 5 字 boot option `0xF3F9BDA9` @`0x14`(官方 `startup_CH583.S`/`Link.ld` 的位置);链接期 `ASSERT` 钉地址、`ci/check_wch_boot.py` 钉产物字节,缺了它板上停在 ISP 而构建/门禁全绿 - [x] 无 A 扩展目标:`riscv32imc-unknown-none-elf`(CH572/青稞无 A 档位、esp32c3 真身)也能编译+链接同一示例——内核的原子读改写本就包在 `atomic-polyfill` 里(arc/semaphore/notify),缺的 `critical-section` RISC-V 实现已收归 `src/arch/riscv/critical.rs`(`mstatus.MIE` 存取恢复,单核语义);实测 **imc 产物 0 条 AMO/LR/SC**,有 A 的 imac 产物 12 条作阳性对照(即无 A 机器上不会踩非法指令),门禁含该构建步骤 +- [x] 上板自检例程:`examples/board_check_ch57x.rs`(ch583/ch572 共用;BSP 在 `src/bsp/ch57x/`,轮询 UART + PA 口,寄存器照官方 EVT 源码)——**阶段一**按 8/16/24/32/48/60/100MHz 逐个假设各打一行标记,哪行可读即**复位默认主频**;**阶段二**打印启动头魔数回读、`STK_CTLR`/`SR`/`PFIC_IENR` 回读、INTSYSCR(`0x804`)回读、`mcycle` 是否推进、`SWI_IRQn=14` 的 pending 是否被硬件取走,并起两个同优先级任务打印计数器与 `tick()`(两者都涨 = 时基/切换/时间片在真机成立) - [ ] 真机验证:gd32vf103 已验;f4/f1 常数、h7 时序、cm32m4、rp2040、ch32 系、ch58x、esp32c3 待上板 ### 移植的芯片 diff --git a/ci/gate.sh b/ci/gate.sh index 2dcc7aa..a188c64 100644 --- a/ci/gate.sh +++ b/ci/gate.sh @@ -82,6 +82,13 @@ fi # 真芯片执行到就是非法指令;按真 ISA 构建后为 0 条(原子走 critical-section 垫片) cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example multitask_esp32c3 \ --features esp32c3,timer --target riscv32imc-unknown-none-elf --release +# 上板自检例程(ch57x 家族共用,依赖 src/bsp/ch57x 的轮询控制台): +# 把 issue #14 里"只能上板回答"的核对点收敛成一次烧写——先扫主频假设, +# 再打印启动头/STK/PFIC/INTSYSCR 回读与 SWI pending 归宿,最后两任务活性测试 +cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example board_check_ch57x \ + --features ch583,timer --target riscv32imac-unknown-none-elf --release +cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example board_check_ch57x \ + --features ch572 --target riscv32imc-unknown-none-elf --release echo "== [3/3] QEMU 执行门禁(virt 机跑真内核——调度/切换/节拍执行级验证)==" QEMU_BIN="$(command -v qemu-system-riscv32 || true)" diff --git a/examples/board_check_ch57x.rs b/examples/board_check_ch57x.rs new file mode 100644 index 0000000..9c289bb --- /dev/null +++ b/examples/board_check_ch57x.rs @@ -0,0 +1,170 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +// CH57x(ch583 / ch572)上板自检 —— 把 issue #14 里"只能上板回答"的核对点收敛成一次烧写: +// +// 阶段一:逐个主频假设各打一行标记(115200 8N1)。**哪一行可读,复位主频就是那个值** +// (本口不做 SAM 解锁/PLL,主频就是复位默认值,只能这样测)。 +// 阶段二:按定稿假设打印核对点——启动头魔数回读、SysTick/PFIC 寄存器回读、 +// INTSYSCR(自定义 CSR 0x804)回读、`mcycle` 是否推进(仅供参考)、 +// 软中断(SWI_IRQn=14)pending 是否被硬件取走; +// 再起两个同优先级任务互抢 CPU,各自周期打印计数器与 `tick()`—— +// **两个计数器都在涨且 tick 在涨**,就说明时基、上下文切换、时间片在真机成立。 +// +// 用法:先按默认 CLK_HZ 烧一次看阶段一 → 把 CLK_HZ 改成可读的那个值 → 重烧看阶段二。 +use core::fmt::Write; + +use xtask::arch::riscv::rt; +use xtask::bsp::ch57x::{self, uart::Console}; +use xtask::prelude::*; + +/// 阶段二的主频定稿(首次上板前按阶段一结果改;默认 CH583 官方 `FREQ_SYS=60MHz`, +/// CH572 官方默认 100MHz,见 `src/chip/env.rs`) +const CLK_HZ: u32 = 60_000_000; +const BAUD: u32 = 115200; + +/// 与 port.S / mod.rs 一致的直址(零 PAC) +const STK_BASE: usize = 0xE000_F000; +const PFIC_BASE: usize = 0xE000_E000; + +/// 本口初始化后 `STK_CTLR` 的期望值:ch583 = INIT|STRE|STCLK|STIE|STE = 0x2F; +/// ch572 的 CTLR 没有 INIT 位 = 0x0F +#[cfg(feature = "ch583")] +const EXP_CTLR: u32 = 0x2F; +#[cfg(feature = "ch572")] +const EXP_CTLR: u32 = 0x0F; + +#[rt::entry] +fn main() -> ! { + extern "C" { + static _sheap: u8; + /// 启动头魔数(port.S 里 `.bootvec` 的第 5 字;符号地址即它在 flash 的位置) + static _boot_magic: u32; + static _bootvec: u32; + } + + // ---- 阶段一:复位主频假设扫描 ---- + for hz in [8u32, 16, 24, 32, 48, 60, 100] { + ch57x::uart::configure(hz * 1_000_000, BAUD); + let mut c = Console; + let _ = write!( + c, + "\r\n=== [假设] 复位主频 {} MHz —— 这行可读即此值(115200 8N1) ===\r\n", + hz + ); + } + + // ---- 阶段二:按定稿假设打印核对点 ---- + ch57x::uart::configure(CLK_HZ, BAUD); + let mut c = Console; + let _ = write!(c, "\r\n[board_check] 定稿假设 CLK={} Hz\r\n", CLK_HZ); + + let magic = unsafe { core::ptr::read_volatile(&_boot_magic as *const u32) }; + let vec_addr = unsafe { &_bootvec as *const u32 as usize }; + let _ = write!( + c, + "[boot] _bootvec=0x{:08X} 魔数=0x{:08X} {}\r\n", + vec_addr, + magic, + if magic == 0xF3F9_BDA9 { + "OK(boot option 正确)" + } else { + "!! 不是 F3F9BDA9 —— ROM 可能不认这个镜像" + } + ); + + let ctlr = unsafe { (STK_BASE as *const u32).read_volatile() }; + let sr = unsafe { ((STK_BASE + 4) as *const u32).read_volatile() }; + let ienr0 = unsafe { ((PFIC_BASE + 0x100) as *const u32).read_volatile() }; + let _ = write!( + c, + "[stk] CTLR=0x{:08X}(期望 0x{:08X}) SR=0x{:08X} PFIC_IENR[0]=0x{:08X}(bit12 应=1) {}\r\n", + ctlr, + EXP_CTLR, + sr, + ienr0, + if ctlr == EXP_CTLR && ienr0 & (1 << 12) != 0 { + "OK" + } else { + "!! 与初始化写入不符" + } + ); + + // INTSYSCR(自定义 CSR 0x804):官方 RT-Thread 口就靠 csrr 读它判嵌套层级, + // 本口写 0(关硬件压栈 + 关嵌套) + let intsyscr: u32; + unsafe { core::arch::asm!("csrr {}, 0x804", out(reg) intsyscr) }; + let _ = write!( + c, + "[csr] intsyscr(0x804)=0x{:08X} {}\r\n", + intsyscr, + if intsyscr & 0x3 == 0 { + "OK(HPE 与嵌套都关着,36 字帧前提成立)" + } else { + "!! 非 0 —— 硬件压栈/嵌套没关,36 字帧不成立" + } + ); + + // mcycle 是否推进(仅参考:delay_us 已改用 SysTick,不依赖它) + let m0 = xtask::arch::riscv::register::mcycle::read64(); + let mut spin = 0u32; + while spin < 1000 { + spin += 1; + core::hint::spin_loop(); + } + let m1 = xtask::arch::riscv::register::mcycle::read64(); + let _ = write!( + c, + "[misc] mcycle 前后差={}(0 表示没有 Zicntr;本口 delay_us 已改用 SysTick)\r\n", + m1.wrapping_sub(m0) + ); + + // ---- 起任务:既验证软中断 yield 通路,也验证时基/切换/时间片 ---- + let start_addr = unsafe { &_sheap as *const u8 as usize }; + // 两个 1K 栈任务 + 软件定时器/idle 的实测账见 examples/multitask_ch572.rs + xtask::init_heap(start_addr, 9 * 1024 + 512); + + TaskBuilder::new().name("probe").spawn(move || { + let mut c = Console; + // SWI_IRQn=14 自检:置 pending → 若 trap 侧按 mcause=14 走了切换,硬件取中断时 + // 会把 pending 清掉;清不掉说明这条通路在真机上没生效(PFIC 的 ISR[0] 即 pending 位图) + let isr14 = |()| unsafe { ((PFIC_BASE) as *const u32).read_volatile() & (1 << 14) }; + let before = isr14(()); + xtask::yield_now(); // 内部 Porting::irq() → 置 IPSR bit14 + let after = isr14(()); + let _ = write!( + c, + "[swi] yield 前后 ISR bit14 = {}/{} {}\r\n", + (before != 0) as u8, + (after != 0) as u8, + if before == 0 && after == 0 { + "OK(pending 被硬件取走 → SWI_IRQn=14 通路成立)" + } else { + "!! pending 残留 —— SWI 通路可疑" + } + ); + + // 与另一个任务互抢:两个计数器都在涨 + tick 在涨 = 时基/切换/时间片成立 + TaskBuilder::new().name("counter_b").spawn(move || { + let mut n = 0u64; + let mut c = Console; + loop { + n += 1; + if n % 200_000 == 0 { + let _ = write!(c, "[B] n={} tick={}\r\n", n, xtask::tick()); + } + } + }); + let mut n = 0u64; + loop { + n += 1; + if n % 200_000 == 0 { + let _ = write!(c, "[A] n={} tick={}\r\n", n, xtask::tick()); + } + } + }); + + xtask::start() +} diff --git a/src/bsp/ch57x/mod.rs b/src/bsp/ch57x/mod.rs new file mode 100644 index 0000000..1d4dc40 --- /dev/null +++ b/src/bsp/ch57x/mod.rs @@ -0,0 +1,35 @@ +//! CH57x(WCH CH583 / CH572)最小 BSP。 +//! +//! 只做两件事(够上板核对用,不做时钟树/外设全家桶): +//! - `uart`:轮询控制台(TX),寄存器与波特率公式照官方 EVT 源码; +//! - PA 口输出:给 LED / 示波器打点用。 +//! +//! 寄存器依据:`PA_DIR @0x400010A0 / PA_PIN @+04 / PA_OUT @+08 / PA_CLR @+0C` +//! (官方 `CH583SFR.h`、`CH572SFR.h`,两芯片同址)。 + +pub mod uart; + +const PA_DIR: usize = 0x4000_10A0; +const PA_OUT: usize = 0x4000_10A8; +const PA_CLR: usize = 0x4000_10AC; + +/// 把 `mask` 里的 PA 脚设为输出(0=输入,1=输出) +#[inline] +pub fn pa_enable_output(mask: u32) { + let dir = PA_DIR as *mut u32; + unsafe { dir.write_volatile(dir.read_volatile() | mask) } +} + +/// PA 脚置高 +#[inline] +pub fn pa_set(mask: u32) { + let out = PA_OUT as *mut u32; + unsafe { out.write_volatile(out.read_volatile() | mask) } +} + +/// PA 脚置低(`PA_CLR` 是写 1 清零) +#[inline] +pub fn pa_clear(mask: u32) { + let clr = PA_CLR as *mut u32; + unsafe { clr.write_volatile(mask) } +} diff --git a/src/bsp/ch57x/uart.rs b/src/bsp/ch57x/uart.rs new file mode 100644 index 0000000..4ef2760 --- /dev/null +++ b/src/bsp/ch57x/uart.rs @@ -0,0 +1,67 @@ +//! CH57x(WCH CH583 / CH572)轮询 UART 控制台(仅 TX;RX 留给后续 BSP)。 +//! +//! 寄存器与波特率公式**照抄官方 EVT 源码**: +//! - 基址 `0x40003400`,偏移 `IER @+1 / FCR @+2 / LCR @+3 / LSR @+5(bit6=发送全空) / +//! THR @+8 / R16_DL @+0x0C / R8_DIV @+0x0E` +//! (官方 `CH583SFR.h`、`CH572SFR.h`;两芯片同址同偏移,故一份驱动够用)。 +//! - 初始化序列同官方 `UART1_DefInit()` / `UART_DefInit()`: +//! `FCR=(2<<6)|TX_CLR|RX_CLR|FIFO_EN`、`LCR=0x03`(8 位字长)、`IER=0x40`(TXD 使能)、 +//! `DIV=1`。 +//! - 波特率:`DL = round(clk / (8*baud))`——官方写法 `x = 10*clk/8/baud; x = (x+5)/10`。 +//! +//! ⚠️ `clk_hz` 必须传**复位后的实际主频假设**:本驱动不碰 SAM 解锁/PLL 时钟树, +//! 真实主频由 `examples/board_check_ch57x.rs` 用"逐个假设各打一行标记、哪行可读即该值" +//! 的办法先测出来(这也是上板第一步要回答的问题)。 + +const UART_BASE: usize = 0x4000_3400; + +#[inline] +fn r8(off: usize) -> *mut u8 { + (UART_BASE + off) as *mut u8 +} +#[inline] +fn r16(off: usize) -> *mut u16 { + (UART_BASE + off) as *mut u16 +} + +/// 按给定主频假设配置控制台(8-N-1,波特率由调用方给) +pub fn configure(clk_hz: u32, baud: u32) { + let dl = (clk_hz / (8 * baud)) as u16; + unsafe { + r16(0x0C).write_volatile(dl); // R16_DL:除数锁存 + r8(0x0E).write_volatile(1); // R8_DIV:预分频(官方惯用法 1) + r8(0x02).write_volatile(0x80 | 0x04 | 0x02 | 0x01); // FCR:触发点 2|清 TX/RX FIFO|使能 + r8(0x03).write_volatile(0x03); // LCR:8 位字长 + r8(0x01).write_volatile(0x40); // IER:使能 TXD 引脚 + } +} + +/// 写单字节(等"发送全空";`\n` 前补 `\r` 由 `write_str` 负责) +pub fn putc(b: u8) { + let lsr = r8(0x05) as *const u8; + let thr = r8(0x08); + unsafe { + while lsr.read_volatile() & 0x40 == 0 {} + thr.write_volatile(b); + } +} + +/// 写字符串(把 `\n` 展开成 `\r\n`) +pub fn write_str(s: &str) { + for &b in s.as_bytes() { + if b == b'\n' { + putc(b'\r'); + } + putc(b); + } +} + +/// `core::fmt::Write` 出口:`write!(Console, ...)` 用 +pub struct Console; + +impl core::fmt::Write for Console { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + write_str(s); + Ok(()) + } +} diff --git a/src/bsp/mod.rs b/src/bsp/mod.rs index fa77b11..1fd68d5 100644 --- a/src/bsp/mod.rs +++ b/src/bsp/mod.rs @@ -12,3 +12,8 @@ pub mod bluepill; // rp-pico 板 feature 已并入 rp2040(BSP 用仓库自带 bsp_pins!,无官方 rp-pico 依赖) #[cfg(feature = "rp2040")] pub mod rp_pico; + +// CH57x 家族(CH583 / CH572)的最小 BSP:轮询控制台 + PA 口输出。 +// 没有独立板 feature —— 这两个芯片的 UART/PA 寄存器同址同偏移,一份够用。 +#[cfg(any(feature = "ch583", feature = "ch572"))] +pub mod ch57x;