From 6782b0a9838071708fb758caacad7a3b1492c241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=BA=86=E4=B8=B0?= Date: Fri, 18 Sep 2026 15:57:41 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat(chip):=20=E6=96=B0=E5=A2=9E=20CH583(?= =?UTF-8?q?QingKe=20V4A)=20=E7=A7=BB=E6=A4=8D=E9=AA=A8=E6=9E=B6(=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E7=BA=A7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + build.rs | 3 + examples/multitask_ch583.rs | 220 +++++++++++++++++++++++++++++++++++ src/chip/ch583/memory.x | 17 +++ src/chip/ch583/mod.rs | 213 +++++++++++++++++++++++++++++++++ src/chip/ch583/port.S | 202 ++++++++++++++++++++++++++++++++ src/chip/ch583/port.rs | 52 +++++++++ src/chip/ch583/restore_ctx.S | 52 +++++++++ src/chip/env.rs | 7 ++ src/chip/mod.rs | 2 + 10 files changed, 770 insertions(+) create mode 100644 examples/multitask_ch583.rs create mode 100644 src/chip/ch583/memory.x create mode 100644 src/chip/ch583/mod.rs create mode 100644 src/chip/ch583/port.S create mode 100644 src/chip/ch583/port.rs create mode 100644 src/chip/ch583/restore_ctx.S diff --git a/Cargo.toml b/Cargo.toml index 1ce0c71..ef223e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,8 @@ qemu_riscv = ["riscv-rt"] # 零 PAC:标准 CLINT/UART 直址 qemu_arm_r52 = [] # 零 PAC、零运行时:R5 无 cortex-r-rt,启动/链接脚本自备(chip 内) ch32v203 = ["ch32v2", "riscv-rt"] ch32v307 = ["ch32v3", "riscv-rt"] +# CH583(QingKe V4A):CH58x 无现成 ch32-rs PAC,零 PAC 直址访问(见 chip/ch583/mod.rs) +ch583 = ["riscv-rt"] #ble 蓝牙(第 24 章):协议栈在 E104-BT5032A 模组固件内——我们的活是 #AT 命令编解码 + GATT 服务建模,纯 Rust 零新依赖 ble=[] diff --git a/build.rs b/build.rs index 91b8e17..ffa3933 100644 --- a/build.rs +++ b/build.rs @@ -26,6 +26,8 @@ fn main() { fs::copy("src/chip/ch32v203/memory.x", out_dir.join("memory.x")).unwrap(); #[cfg(feature = "ch32v103")] fs::copy("src/chip/ch32v103/memory.x", out_dir.join("memory.x")).unwrap(); + #[cfg(feature = "ch583")] + fs::copy("src/chip/ch583/memory.x", out_dir.join("memory.x")).unwrap(); #[cfg(feature = "esp32c3")] fs::copy("src/chip/esp32c3/memory.x", out_dir.join("memory.x")).unwrap(); #[cfg(feature = "qemu_riscv")] @@ -49,6 +51,7 @@ fn main() { println!("cargo:rerun-if-changed=src/chip/ch32v307/memory.x"); println!("cargo:rerun-if-changed=src/chip/ch32v203/memory.x"); println!("cargo:rerun-if-changed=src/chip/ch32v103/memory.x"); + println!("cargo:rerun-if-changed=src/chip/ch583/memory.x"); println!("cargo:rerun-if-changed=src/chip/esp32c3/memory.x"); println!("cargo:rerun-if-changed=src/chip/qemu_riscv/memory.x"); println!("cargo:rerun-if-changed=src/chip/qemu_arm_r52/memory.x"); diff --git a/examples/multitask_ch583.rs b/examples/multitask_ch583.rs new file mode 100644 index 0000000..144f364 --- /dev/null +++ b/examples/multitask_ch583.rs @@ -0,0 +1,220 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +// CH583(QingKe V4A,RISC-V RV32IMAC softfp)多任务示例 —— 以 ch32v103 为模板。 +// ⚠️ 真机核对点(构建级验证已过,板上行为待验):①HPE 关闭路径 +// (intsyscr 0x804 写 0 + 私有 CSR 0xbc0=0x1f);②mtvec Direct 分发与 +// mcause=12(SysTick);③SysTick CTLR=0x8000_002F + SR.CNTIF 写 0 清零; +// ④SWIE(bit31)触发软中断语义;⑤flash 基址 0x00000000(尾部 64K 留给 +// BootLoader);⑥默认主频 60MHz(env 按此配);⑦`mcycle` 是否实现(决定 +// `delay_us`,未实现则 `delay_us` 会死循环——见 chip/ch583/mod.rs 注)。 +// +// TODO(CH583 外设,待上板核对后补): +// - GPIO:R32_PA_DIR/R32_PA_OUT @0x400010A0..(与 CH572 窗口大小不同), +// PA 引脚复用 R32_PA_PIN/R32_PA_OUT 一带。 +// - UART:UART1 @0x40003400(与 ch32v103 的 USART1 不同址,4 个 UART 可选)。 +// - 日志后端:RTT/stdout 均未接(当前纯任务演示,log 走内核默认后端)。 +// 本示例目标是「构建级通过」,外设定义留待 BSP 层。 + +use xtask::arch::riscv::rt; +use xtask::prelude::*; + +#[rt::entry] +fn main() -> ! { + extern "C" { + /// 堆内存开始地址,在riscv-rt link.x文件里定义 + static _sheap: u8; + } + let start_addr = unsafe { &_sheap as *const u8 as usize }; + xtask::init_heap(start_addr, 16 * 1024); // 32K SRAM:静态+栈后保守 16 堆 + + //example_notify(); + //example_broadcast(); + example_semaphore(); + example_queue(); + example_task(); + xtask::start() +} + +fn example_notify() { + let notifier = Notifier::new(); + let waiter = notifier.clone(); + TaskBuilder::new().name("notifier").spawn(move || loop { + log::info!("发送通知信号 {}", xtask::tick()); + notifier.notify(); + xtask::sleep_ms(1000); + }); + TaskBuilder::new().name("waiter").spawn(move || loop { + waiter.wait(); + log::info!("收到通知信号 {}", xtask::tick()); + }); +} + +fn example_broadcast() { + let caster = Broadcast::new(); + let waiter1 = caster.clone(); + let waiter2 = caster.clone(); + let waiter3 = caster.clone(); + let waiter4 = caster.clone(); + let waiter5 = caster.clone(); + + TaskBuilder::new().name("caster").spawn(move || loop { + log::info!("发送广播信号 {}", xtask::tick()); + caster.notify(); + xtask::sleep_ms(1000); + }); + TaskBuilder::new().name("listener1").spawn(move || loop { + waiter1.wait(); + log::info!("1收到广播信号 {}", xtask::tick()); + }); + TaskBuilder::new().name("listener1").spawn(move || loop { + waiter2.wait(); + log::info!("2收到广播信号 {}", xtask::tick()); + }); + TaskBuilder::new().name("listener1").spawn(move || loop { + waiter3.wait(); + log::info!("3收到广播信号 {}", xtask::tick()); + }); + TaskBuilder::new().name("listener1").spawn(move || loop { + waiter4.wait(); + log::info!("4收到广播信号 {}", xtask::tick()); + }); + TaskBuilder::new().name("listener1").spawn(move || loop { + waiter5.wait(); + log::info!("5收到广播信号 {}", xtask::tick()); + }); +} +fn example_queue() { + #[derive(Debug, Clone)] + struct Message { + id: u64, + msg: String, + data: Vec, + } + let qsender = Queue::new(); + let qsender2 = qsender.clone(); + let qrecv = qsender.clone(); + let qrecv2 = qsender.clone(); + let qrecv3 = qsender.clone(); + TaskBuilder::new().name("queue.sender1").spawn(move || { + let mut id = 0; + loop { + id += 1; + let msg = Message { + id, + msg: format!("这是一条消息1 {}", xtask::tick()), + data: Vec::new(), + }; + qsender.push_back(msg); + xtask::sleep_ms(100); + } + }); + TaskBuilder::new().name("queue.sender2").spawn(move || { + let mut id = 0; + loop { + id += 1; + let msg = Message { + id, + msg: format!("这是一条消息2 {}", xtask::tick()), + data: Vec::new(), + }; + qsender2.push_back(msg); + xtask::sleep_ms(100); + } + }); + TaskBuilder::new().name("queue.recv1").spawn(move || loop { + if let Some(msg) = qrecv.pop_front() { + log::info!("收到消息1 {:?}", msg); + } + }); + TaskBuilder::new().name("queue.recv2").spawn(move || loop { + if let Some(msg) = qrecv2.pop_front() { + log::info!("收到消息2 {:?}", msg); + } + }); + TaskBuilder::new().name("queue.recv3").spawn(move || loop { + if let Some(msg) = qrecv3.pop_front() { + log::info!("收到消息3 {:?}", msg); + } + }); +} + +fn example_semaphore() { + let sender = Semaphore::new(); + let sender2 = sender.clone(); + let recver = sender.clone(); + let recver2 = sender.clone(); + let recver3 = sender.clone(); + TaskBuilder::new() + .name("semaphore.poster1") + .spawn(move || loop { + //log::info!("发送计数信号"); + sender.post(); + xtask::sleep_ms(100); + }); + TaskBuilder::new() + .name("semaphore.poster2") + .spawn(move || loop { + //log::info!("发送计数信号"); + sender2.post(); + xtask::sleep_ms(100); + }); + + TaskBuilder::new() + .name("semaphore.waiter1") + .spawn(move || loop { + recver.wait(); + log::info!("收到计数信号"); + }); + TaskBuilder::new() + .name("semaphore.waiter2") + .spawn(move || loop { + recver2.wait(); + log::info!("收到计数信号"); + }); + TaskBuilder::new() + .name("semaphore.waiter3") + .spawn(move || loop { + recver3.wait(); + log::info!("收到计数信号"); + }); +} + +fn example_task() { + xtask::spawn(|| { + for i in 0..10 { + log::info!("{} 循环测试任务0", i + 1); + xtask::sleep_ms(1000); + } + }); + xtask::spawn(|| { + for i in 0..50 { + log::info!("{} 循环测试任务1", i + 1); + xtask::sleep_ms(1000); + } + }); + + xtask::spawn(|| { + for i in 0..100 { + log::info!("{} 循环测试任务2", i + 1); + xtask::sleep_ms(1000); + } + }); + + xtask::spawn(|| { + for i in 0..500 { + log::info!("{} 循环测试任务4", i + 1); + xtask::sleep_ms(1000); + } + }); + + xtask::spawn(|| loop { + log::info!("死循环测试任务 {}", tick()); + xtask::sleep_ms(10000); + }); +} diff --git a/src/chip/ch583/memory.x b/src/chip/ch583/memory.x new file mode 100644 index 0000000..37c79f9 --- /dev/null +++ b/src/chip/ch583/memory.x @@ -0,0 +1,17 @@ +/* CH583(QingKe V4A)内存布局 —— 照抄官方 EVT `Link.ld`(SRC/Ld/Link.ld): + FLASH @0x00000000,LENGTH 448K(⚠️ 基址是 0,不是 0x08000000; + 尾部 64K(0x70000-0x7FFFF)留给 BootLoader/ISP/配置区,故只用 448K); + RAM @0x20000000,LENGTH 32K。 + 真机核对点:0x00000000 是否可写、是否存在 0x08000000 别名。 */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 448K + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} + +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); diff --git a/src/chip/ch583/mod.rs b/src/chip/ch583/mod.rs new file mode 100644 index 0000000..43d4baf --- /dev/null +++ b/src/chip/ch583/mod.rs @@ -0,0 +1,213 @@ +//! CH583 移植模块实现(QingKe V4A(第三代以上 PFIC 内核,SysTick 独立外设) 内核, +//! PFIC + WCH 自有 SysTick 模型)。以 `ch32v103`(V3A)为模板——两者架构同源: +//! 都用 `STK_CTLR.SWIE` 触发软中断、都是 36 字纯软件全保存帧、都是 Direct mtvec。 +//! +//! 与 `ch32v103` 的关键差异: +//! - **代码基址 = `0x00000000`**(不是 `0x08000000`!),见 `memory.x`; +//! 官方 EVT 只给 448K,尾部 64K 留给 BootLoader/ISP/配置区。 +//! - **私有 CSR `0xbc0`**:`_setup_interrupts` 多一条 `csrw 0xbc0, 0x1f` +//! (流水线控制 + 动态分支预测控制位,官方 `startup_CH583.S` 固定写法)。 +//! - **SysTick 是 64 位**(CH572 是 32 位),但寄存器字偏移与 V3A 一致: +//! CTLR@+0 SR@+4 CNT@+8(64 位,字 2/3) CMP@+16(64 位,字 4/5)。 +//! +//! 本口按**标准 RV32 机器模式**设计(与现有 RISC-V 口一致): +//! +//! - **HPE 关闭**:`_setup_interrupts` 显式写 `intsyscr(0x804)=0`(CH583 的 +//! HWSTKEN 在 bit0),上下文切换回到**纯软件全保存**模型(36 字帧)。 +//! - **mtvec = Direct 模式**:单入口 `_start_trap` 读 `mcause` 分发—— +//! 中断号 12 = SysTick;其余走 `DefaultHandler`。 +//! - **软中断(yield/抢占请求)复用 SysTick 入口**:`STK_CTLR.SWIE`(bit31) +//! 写 1 立即触发 SysTick 中断——ISR 读 `STK_SR.CNTIF` 区分真 tick 与 +//! 软中断请求。**与 ch32v103 完全同款**(CH572 的 SWIE 在 `SR` 而非 +//! `CTLR`,故 CH572 需要另写,本 CH583 口可直接复用)。 +//! - **SysTick@0xE000F000**(WCH 自有,非 CLINT mtime);`SR.CNTIF` 写 0 清零。 +//! - **无 A 扩展退路**:若真机实测无 `A` 扩展,改用 +//! `--target riscv32imc-unknown-none-elf`(CH57x 的退路 target)。 +//! +//! ⚠️ 真机核对点(构建级验证,板上行为待验): +//! ①`0x00000000` 是否可写 / 是否有 `0x08000000` 别名; +//! ②默认主频 = 60MHz(官方 `CH58x_common.h` 的 `FREQ_SYS` 默认值;env 常数按此配, +//! PLL 配好后须同步改); +//! ③`csrw 0xbc0, 0x1f` 是否在 CH583 上必须; +//! ④`0x804=0` 后异常/中断入口是否确实不再硬件压栈(决定 36 字帧成立); +//! ⑤`STK_CTLR.SWIE` 置 1 后是否即刻进 `SysTick`、`CNTIF` 是否仍为 0; +//! ⑥`mcycle` 是否实现(决定 `delay_us`,见下)。 +//! BootLoader 默认开启,量产配置里关掉(或接受代码从 0 起、向量表第 5 字保持 +//! `0xF3F9BDA9`)。 +//! +//! **零 PAC 依赖**:CH58x 无现成 `ch32-rs` PAC,本口全量直址访问 +//! (与 `qemu_riscv` 口的思路一致),`Cargo.toml` 只挂 `riscv-rt`。 + +mod port; + +use super::{CPU_CLOCK_HZ, SYSTICK_CLOCK_HZ, TICK_CLOCK_HZ}; +use crate::port::Portable; +use crate::prelude::CriticalSection; +use crate::task::Task; +use core::arch::asm; + +/// SysTick 寄存器基址(WCH 自有,非 CLINT;CH583 为 64 位计数器) +pub(crate) const STK_BASE: usize = 0xE000_F000; + +/// PFIC 寄存器基址(CH583 与 CH572/ch32v103/ch32v307 同址) +pub(crate) const PFIC_BASE: usize = 0xE000_E000; + +/// 配置 SysTick 与 PFIC(在 start_scheduler 里、restore_ctx 之前调用)。 +/// CH583 的 SysTick 与 V3A 同布局,只是计数器/比较值 64 位; +/// PFIC 无 PAC,直接访问 `IENR[0]`(offset 0x100)使能 SysTick。 +#[inline] +pub(crate) fn setup_intrrupt() { + let stk = STK_BASE as *mut u32; + unsafe { + // 1. 停计数(CTLR=0)、清 SR.CNTIF(写 0)、CNT/CMP 清零装初值(高字先写) + stk.write_volatile(0); + stk.add(1).write_volatile(0); // SR:CNTIF 写 0 清 + stk.add(2).write_volatile(0); // CNTL + stk.add(3).write_volatile(0); // CNTH + const TICKS: u32 = (SYSTICK_CLOCK_HZ / TICK_CLOCK_HZ) as u32 - 1; + stk.add(5).write_volatile(0); // CMPHR + stk.add(4).write_volatile(TICKS); // CMPLR + // 2. CTLR = SWIE(31)|INIT(5)|STRE(3)|STCLK(HCLK,2)|STIE(1)|STE(0) + // = 0x8000_002F(官方 SysTick_Config 惯用法 + SWIE 初始触发一次) + stk.write_volatile(0x8000_002F); + } + // 3. PFIC:使能 SysTick(IENR[0] @0xE000E100,bit12 = 核中断号 12) + let ienr0 = (PFIC_BASE + 0x100) as *mut u32; + unsafe { + ienr0.write_volatile(1 << 12); + } +} + +/// 清 SysTick 的 CNTIF(写 0)——真 tick 路径调用 +#[inline] +pub(crate) fn reset_systick() { + let stk = STK_BASE as *mut u32; + unsafe { + stk.add(1).write_volatile(0); // SR @ +4 + } +} + +/// CH583 芯片移植层实现 +pub struct Ch583Porting; + +// port.S 蹦床 `_task_entry_trampoline` 依赖的 Task 布局偏移(失配编译期炸) +const _: () = assert!(core::mem::offset_of!(Task, sp) == 0); +const _: () = assert!(core::mem::offset_of!(Task, entry) == 8); + +impl Portable for Ch583Porting { + /// 完全内存屏障 + #[inline] + fn barrier() { + unsafe { + // CH583 无 MMU/虚存,通用 fence(iorw) 即可 + core::arch::asm!("fence iorw, iorw"); + } + } + /// 临界区保护(本核 mstatus.MIE——单核语义与 gd32 同) + #[inline] + fn free(f: F) -> R + where + F: FnOnce(&CriticalSection) -> R, + { + riscv::interrupt::free(f) + } + + /// 开全局中断 + #[inline] + fn enable_interrupt() { + unsafe { + riscv::interrupt::enable(); + } + } + /// 关全局中断 + #[inline] + fn disable_interrupt() { + unsafe { + riscv::interrupt::disable(); + } + } + + /// 启动调度器:配置 SysTick/PFIC → 恢复第一个任务(汇编,不返回) + fn start_scheduler() -> ! { + setup_intrrupt(); + log::info!("Start scheduler"); + unsafe { asm!(include_str!("restore_ctx.S"), options(noreturn, raw)) }; + } + + /// 软中断(调度请求):写 STK_CTLR.SWIE(bit31)触发 SysTick 入口。 + /// ISR 读 SR.CNTIF 区分真 tick 与本请求(见 port.rs 的 SysTick 处理)。 + /// **与 ch32v103 同款**(CH572 的 SWIE 在 SR,本口不适用) + #[inline] + fn irq() { + let ctlr = STK_BASE as *mut u32; + unsafe { + ctlr.write_volatile(ctlr.read_volatile() | (1 << 31)); + } + } + /// 关闭软中断(SWIE 是触发位,自清;防御性清一下) + #[inline] + fn disable_irq() { + let ctlr = STK_BASE as *mut u32; + unsafe { + ctlr.write_volatile(ctlr.read_volatile() & !(1 << 31)); + } + } + + /// 读 SysTick 64 位计数器(高:低:高重读防翻转;CH583 是 64 位) + #[inline] + fn systick() -> u64 { + let stk = STK_BASE as *mut u32; + loop { + unsafe { + let hi = stk.add(3).read_volatile(); + let lo = stk.add(2).read_volatile(); + if hi == stk.add(3).read_volatile() { + return ((hi as u64) << 32) | lo as u64; + } + } + } + } + + /// 硬件延时,单位 us。 + /// + /// ⚠️ **暂沿 `ch32v103` 用 `mcycle` 实现**——CH583 是否实现 `mcycle` + /// (Zicntr)待上板核对:若未实现则读出恒为常量,本函数会死循环。届时 + /// **必须**改为 SysTick/TMR 计时(CH57x 无 DWT,不能照搬 ARM 的 DWT_CYCCNT + /// 或 ch32v307 的 DWT)。主频按 env 的 `CPU_CLOCK_HZ`(默认 60MHz)。 + #[inline] + fn delay_us(us: u64) { + let t0 = riscv::register::mcycle::read64(); + let clock = (us * (CPU_CLOCK_HZ as u64)) / 1_000_000; + while riscv::register::mcycle::read64().wrapping_sub(t0) <= clock {} + } + + /// 任务创建时为 CPU 准备任务现场(36 字帧,与 port.S 的保存宏互为镜像): + /// [35]=mcause(0x8000_000C)[34]=0(mcause 保留槽) + /// [33]=mepc=_task_entry_trampoline [32]=mstatus=0x1880(MPP=M|MPIE=1) + /// [10]=a0=args [1]=ra=task_exit,任务块 sp 指向帧底 + #[inline] + fn save_context(task: &mut Task) { + unsafe { + let sp = task.stack.add(task.stack_size - 1); + sp.offset(-1).write_volatile(0x8000_000C); // mcause:中断|12(SysTick) + sp.offset(-2).write_volatile(0); // 保留槽(无 msubm) + // mepc = 首调蹦床:经标准 jalr 进入 task.entry(mret 直入会被 + // 编译器 outlined 的入口 stub 坑到野跳,见 port.S 蹦床注) + unsafe extern "C" { + fn _task_entry_trampoline(); + } + sp.offset(-3) + .write_volatile((_task_entry_trampoline as *const ()).addr()); // mepc + sp.offset(-4).write_volatile(0x0000_1880); // mstatus:MPP=M, MPIE=1 + for i in 0..32usize { + if i != 1 && i != 10 { + sp.offset(i as isize - 36).write_volatile(0); + } + } + sp.offset(-26).write_volatile(task.args.addr()); // a0 + sp.offset(-35) + .write_volatile((port::task_exit as *const ()).addr()); // ra + task.sp = sp.offset(-36).addr(); + } + } +} diff --git a/src/chip/ch583/port.S b/src/chip/ch583/port.S new file mode 100644 index 0000000..6bbdb64 --- /dev/null +++ b/src/chip/ch583/port.S @@ -0,0 +1,202 @@ +/* + CH583(QingKe V4A)上下文切换与中断分发 —— ch32v103/port.S 的同架构变体: + 同为「PFIC + WCH 自有 SysTick + 纯软件全保存」模型,HPE 显式关闭 + (0x804=0),mtvec=Direct 单入口分发。相对 ch32v103 的增量只有: + ①`_setup_interrupts` 多一条 `csrw 0xbc0, 0x1f`(CH57x 私有流水线控制位); + ②代码基址 0 由 memory.x 决定(本文件无感)。 + + 帧布局(36 字,与 mod.rs 的 save_context 互为镜像): + [0] x0 保留 [1] ra [2..4] x2-x4 保留 + [5..31] x5-x31 [32] mstatus [33] mepc + [34] 保留(mcause 槽,Bumblebee msubm 位置) [35] mcause +*/ + +// 保存上文宏 +.macro SAVE_CONTEXT + addi sp, sp, -36 * 4 + sw x1, 1 * 4(sp) // ra + sw x5, 5 * 4(sp) // t0 + sw x6, 6 * 4(sp) // t1 + sw x7, 7 * 4(sp) // t2 + sw x8, 8 * 4(sp) // s0/fp + sw x9, 9 * 4(sp) // s1 + sw x10, 10 * 4(sp) // a0 + sw x11, 11 * 4(sp) // a1 + sw x12, 12 * 4(sp) // a2 + sw x13, 13 * 4(sp) // a3 + sw x14, 14 * 4(sp) // a4 + sw x15, 15 * 4(sp) // a5 + sw x16, 16 * 4(sp) // a6 + sw x17, 17 * 4(sp) // a7 + sw x18, 18 * 4(sp) // s2 + sw x19, 19 * 4(sp) // s3 + sw x20, 20 * 4(sp) // s4 + sw x21, 21 * 4(sp) // s5 + sw x22, 22 * 4(sp) // s6 + sw x23, 23 * 4(sp) // s7 + sw x24, 24 * 4(sp) // s8 + sw x25, 25 * 4(sp) // s9 + sw x26, 26 * 4(sp) // s10 + sw x27, 27 * 4(sp) // s11 + sw x28, 28 * 4(sp) // t3 + sw x29, 29 * 4(sp) // t4 + sw x30, 30 * 4(sp) // t5 + sw x31, 31 * 4(sp) // t6 + csrr t0, mstatus + sw t0, 32 * 4(sp) + csrr t0, mepc // 被打断处的下一条指令 + sw t0, 33 * 4(sp) + sw zero, 34 * 4(sp) // 保留槽(无 msubm) + csrr t0, mcause + sw t0, 35 * 4(sp) +.endm + +// 恢复下文宏(与保存严格对称) +.macro RESTORE_CONTEXT + // ★ CSR 装载必须先于通用寄存器:CSR 段用 t0 作草稿,若先恢复 x5(t0) + // 再装 CSR,最后一次 lw t0(=mcause 槽)会把恢复好的 t0 覆盖——任务在 + // 编译器 outlined 序言(`jr t0` 结尾)中被抢占时,恢复后 jr t0 野跳 + // (qemu_riscv 口实测:跳 _start+6 → 整机重启,详见该口 port.S 注) + lw t0, 32 * 4(sp) + csrw mstatus, t0 + lw t0, 33 * 4(sp) + csrw mepc, t0 + // 34 槽跳过(保留) + lw t0, 35 * 4(sp) + csrw mcause, t0 + // 通用寄存器最后装载——t0(x5)在所有草稿使用之后恢复 + lw x1, 1 * 4(sp) + lw x5, 5 * 4(sp) + lw x6, 6 * 4(sp) + lw x7, 7 * 4(sp) + lw x8, 8 * 4(sp) + lw x9, 9 * 4(sp) + lw x10, 10 * 4(sp) + lw x11, 11 * 4(sp) + lw x12, 12 * 4(sp) + lw x13, 13 * 4(sp) + lw x14, 14 * 4(sp) + lw x15, 15 * 4(sp) + lw x16, 16 * 4(sp) + lw x17, 17 * 4(sp) + lw x18, 18 * 4(sp) + lw x19, 19 * 4(sp) + lw x20, 20 * 4(sp) + lw x21, 21 * 4(sp) + lw x22, 22 * 4(sp) + lw x23, 23 * 4(sp) + lw x24, 24 * 4(sp) + lw x25, 25 * 4(sp) + lw x26, 26 * 4(sp) + lw x27, 27 * 4(sp) + lw x28, 28 * 4(sp) + lw x29, 29 * 4(sp) + lw x30, 30 * 4(sp) + lw x31, 31 * 4(sp) + addi sp, sp, 4 * 36 +.endm + +// 当前任务全局指针(RUST 代码维护) +.extern CURRENT_TASK_PTR +// RUST 侧 SysTick 处理(真 tick 与 SWIE 软中断二合一) +.extern SysTick +// 未处理异常/中断的默认处理(riscv-rt 提供,弱符号——应用可强定义覆盖) +.extern DefaultHandler + +/* + 统一 trap 入口(mtvec = Direct 模式指向这里): + 中断(mcause bit31=1)号 12 → SysTick(RUST); + 其余中断/全部异常 → DefaultHandler。 + SysTick 返回后(或 DefaultHandler 返回后)统一走切换/恢复路径。 +*/ +.section .trap, "ax" +.option push +.option norelax +.align 6 +.option pop +.global _start_trap +_start_trap: + // 保存上文到当前任务栈 + SAVE_CONTEXT + // 任务块首字段存 sp + lw t0, CURRENT_TASK_PTR + sw sp, 0x0(t0) + // 切到中断栈:mscratch 恒持中断栈地址(restore_ctx.S 启动时写一次, + // 之后永不改)——必须 csrr 只读,csrrw 交换会把任务栈地址写进 mscratch, + // 下一次中断就跑在陈旧/已释放的任务栈上(任务退出+内存复用后现场错乱) + csrr sp, mscratch + + // 分发:中断? (mcause < 0 即 bit31=1) + csrr t0, mcause + bltz t0, _int_dispatch + + // ---- 异常路径 ---- +_exc_dispatch: + // 所有异常进 DefaultHandler(构建级:无 FPU/无虚存,异常即致命) + jal ra, DefaultHandler + j _switch_and_restore + + // ---- 中断路径 ---- +_int_dispatch: + slli t0, t0, 0 // 保位 + li t1, 0x7fffffff + and t0, t0, t1 // 取中断号(去 bit31) + li t1, 12 // SysTick + beq t0, t1, _isr_systick + // 未接入的外设中断/其它核心中断 → DefaultHandler(BSP 扩展点) + jal ra, DefaultHandler + j _switch_and_restore + +_isr_systick: + jal ra, SysTick // RUST:清 CNTIF/时间账/必要时置 SWIE 请求切换 + + // ---- 切换与恢复(公共出口) ---- +_switch_and_restore: + // 调度器在中断栈上运行(mscratch 恒持中断栈——绝不在任务栈上跑: + // ①小栈任务会被调度器自身栈帧撑爆;②csrrw 交换会污染 mscratch, + // 修前 bug:任务退出后 mscratch 残留已释放的任务栈地址,下一次 + // 中断在已释放内存上运行 → 内存复用后现场错乱 → guest 复位) + jal ra, switch_context + // 装载新任务 sp(调度器已更新 CURRENT_TASK) + lw t0, CURRENT_TASK_PTR + lw sp, 0x0(t0) + RESTORE_CONTEXT + mret + +/* + riscv-rt link.x 在启动时调用:配 mtvec(Direct)与 HPE 关闭。 +*/ +.section .text +.global _setup_interrupts +_setup_interrupts: + // CH57x 私有 CSR 0xbc0:流水线控制位 + 动态分支预测控制位。 + // 官方 startup_CH583.S 复位序列固定写 0x1f(⚠️ CH572 是 0x25+0xbc1=1, + // CH583 只写 0xbc0=0x1f)。真机核对点:若 CH583 上不需要,可去掉这一条。 + li t0, 0x1f + csrw 0xbc0, t0 + // 显式关闭 HPE(QingKe 自定义 CSR 0x804:intsyscr;CH583 硬件压栈由 + // bit0 HWSTKEN 控制,写 0 = 纯软件全保存模型)—— + // 硬件序言是 WCH 私有工具链特性,与 riscv-rt/裸 Rust 不兼容 + csrw 0x804, zero + // mtvec = _start_trap,Direct 模式(低位 0) + la t0, _start_trap + andi t0, t0, -4 // 4 字节对齐(Direct 模式的最低要求) + csrw mtvec, t0 + ret + +/* + 任务首调蹦床:save_context 造的初始帧 mepc 指向这里(而非 task.entry)。 + mret 直入入口函数不可靠:编译器(LTO)会把闭包入口的序言/尾声 outlined + 成共享 stub(如 `sw..; jr t0` / `lw ra,0xc(sp); ret`),这些 stub 只在 + 调用方先设置好寄存器(jalr 调用模式)时成立,mret 路径跳过了调用方 → + stub 读到未初始化的帧槽(陈旧堆数据)→ 野跳(qemu_riscv 口实测曾跳进 + _start → 整机"重启")。task.entry 是地址已取的 fn 指针,经标准 jalr + 调用必然合法——蹦床恢复"调用方"角色,入口返回后接统一的退出路径。 +*/ +.section .text +.global _task_entry_trampoline +_task_entry_trampoline: + lw t0, CURRENT_TASK_PTR // CURRENT == 本任务(restore 前由 execute 设置) + lw t1, 8(t0) // Task.entry(offset 8,见 mod.rs 布局断言) + jalr t1 // 标准调用:a0=args(帧设置),ra=返回蹦床 + j task_exit // 入口跑完 → 退出路径(不返回) diff --git a/src/chip/ch583/port.rs b/src/chip/ch583/port.rs new file mode 100644 index 0000000..0f570ce --- /dev/null +++ b/src/chip/ch583/port.rs @@ -0,0 +1,52 @@ +//! CH583 中断胶水:SysTick 二合一处理(真 tick / SWIE 软中断)与切换入口。 +//! 与 ch32v103/port.rs 同构——CH583 的 `SWIE` 也在 `CTLR`(bit31),可直接复用语义。 + +use super::Ch583Porting; +use crate::port::Portable; +use crate::task::scheduler; +use core::arch::global_asm; +use riscv::register::mcause; + +global_asm!(include_str!("port.S"), options(raw)); + +/// SysTick 处理(port.S `_start_trap` 经 mcause=12 进入;工作在中断栈)。 +/// +/// 一个入口两种来源,靠 `STK_SR.CNTIF` 区分: +/// - **CNTIF=1(真 tick)**:计数到点——清标志(WCH 写 0 清)+ 时间账 +/// (`scheduler::systick`),返回 true 则请求切换; +/// - **CNTIF=0(SWIE 触发)**:纯调度请求(`Porting::irq()` 的软中断)—— +/// 不做时间账(否则 yield 一次时间就快进一格)。 +/// +/// 两条路径都在返回后经 port.S 的公共出口 `switch_context` 完成切换。 +#[no_mangle] +unsafe extern "C" fn SysTick() { + // CH583:SR 在 SYSTICK+4(直址;零 PAC 依赖) + let sr = (super::STK_BASE + 4) as *const u32; + if unsafe { sr.read_volatile() & 1 } != 0 { + // 真 tick:清标志 + 时间账 + super::reset_systick(); + if scheduler::systick() { + // 需要切换——不需要 SWIE,trap 出口统一调度 + } + } + // SWIE 路径:什么都不做,直接调度(port.S 公共出口) +} + +/// 软中断等价入口:port.S 在 SysTick 处理后调用,执行调度与切换 +#[no_mangle] +unsafe extern "C" fn switch_context() { + Ch583Porting::disable_irq(); // 防 SWIE 残留立即重入 + scheduler::schedule(); +} + +/// 所有任务的退出函数 +#[unsafe(no_mangle)] +pub(crate) unsafe extern "C" fn task_exit() { + scheduler::exit_current_task(); +} + +/// 供汇编引用的符号占位(避免未使用警告;真正实现在上面) +#[allow(dead_code)] +fn _mcause_helper() { + let _ = mcause::read(); +} diff --git a/src/chip/ch583/restore_ctx.S b/src/chip/ch583/restore_ctx.S new file mode 100644 index 0000000..79041e9 --- /dev/null +++ b/src/chip/ch583/restore_ctx.S @@ -0,0 +1,52 @@ +// 调度器启动:恢复第一个任务(gd32 restore_ctx.S 的无 msubm 变体) +// —— 与 ch32v103/restore_ctx.S 完全一致(CH583 帧布局同款 36 字) + +// 关全局中断 +csrc mstatus, 0x00000008 +// 栈顶下留 512 字节作中断栈(启动函数可能已占一部分;硬件启动栈 2k) +// 存入 mscratch,trap 入口经 csrr 只读换栈(mscratch 恒持中断栈,永不改写) +la t0, _stack_start +addi t0, t0, -512 +csrw mscratch, t0 +// 装载第一个任务 +lw t0, CURRENT_TASK_PTR +lw sp, 0x0(t0) +// 恢复通用寄存器 +lw x1, 1 * 4(sp) +lw x5, 5 * 4(sp) +lw x6, 6 * 4(sp) +lw x7, 7 * 4(sp) +lw x8, 8 * 4(sp) +lw x9, 9 * 4(sp) +lw x10, 10 * 4(sp) +lw x11, 11 * 4(sp) +lw x12, 12 * 4(sp) +lw x13, 13 * 4(sp) +lw x14, 14 * 4(sp) +lw x15, 15 * 4(sp) +lw x16, 16 * 4(sp) +lw x17, 17 * 4(sp) +lw x18, 18 * 4(sp) +lw x19, 19 * 4(sp) +lw x20, 20 * 4(sp) +lw x21, 21 * 4(sp) +lw x22, 22 * 4(sp) +lw x23, 23 * 4(sp) +lw x24, 24 * 4(sp) +lw x25, 25 * 4(sp) +lw x26, 26 * 4(sp) +lw x27, 27 * 4(sp) +lw x28, 28 * 4(sp) +lw x29, 29 * 4(sp) +lw x30, 30 * 4(sp) +lw x31, 31 * 4(sp) +// CSR 出栈(34 槽保留,跳过) +lw t0, 32 * 4(sp) +csrw mstatus, t0 +lw t0, 33 * 4(sp) +csrw mepc, t0 +lw t0, 35 * 4(sp) +csrw mcause, t0 +// 释放帧,跳进任务(mret:PC=mepc,mie=mpie) +addi sp, sp, 4 * 36 +mret diff --git a/src/chip/env.rs b/src/chip/env.rs index 0bd3113..074ff7d 100644 --- a/src/chip/env.rs +++ b/src/chip/env.rs @@ -34,6 +34,13 @@ pub const SYSTICK_CLOCK_HZ: usize = 8_000_000; pub const CPU_CLOCK_HZ: usize = 8_000_000; #[cfg(feature = "ch32v103")] pub const SYSTICK_CLOCK_HZ: usize = 8_000_000; +/// CH583(QingKe V4A):默认主频取官方 `CH58x_common.h` 的 `FREQ_SYS` 默认值 +/// 60MHz(⚠️ 真机核对点——本口无 BSP/时钟初始化,按官方默认配; +/// PLL 配置后须同步改这里与 STCLK 语义,见 memory.x/mod.rs 注) +#[cfg(feature = "ch583")] +pub const CPU_CLOCK_HZ: usize = 60_000_000; +#[cfg(feature = "ch583")] +pub const SYSTICK_CLOCK_HZ: usize = 60_000_000; // STCLK=1 → HCLK(=60M) // ESP32-C3:复位默认 CPU 80MHz(TRM;PLL 160M 配好后同步改); // SYSTICK 是独立 16MHz 时基(与 CPU 时钟无关—— 调研已核) #[cfg(feature = "esp32c3")] diff --git a/src/chip/mod.rs b/src/chip/mod.rs index c07c59c..b9486c4 100644 --- a/src/chip/mod.rs +++ b/src/chip/mod.rs @@ -9,6 +9,8 @@ pub mod ch32v307; pub mod ch32v203; #[cfg(feature = "ch32v103")] pub mod ch32v103; +#[cfg(feature = "ch583")] +pub mod ch583; #[cfg(feature = "esp32c3")] pub mod esp32c3; #[cfg(feature = "qemu_riscv")] From 1554aefb3a96d51493fa1838638d4c18af0a8818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=BA=86=E4=B8=B0?= Date: Fri, 18 Sep 2026 16:06:18 +0800 Subject: [PATCH 02/11] =?UTF-8?q?fix(chip):=20=E8=A1=A5=20CH583=20?= =?UTF-8?q?=E7=9A=84=20Porting=20=E6=8E=A5=E7=BA=BF(src/port.rs=20?= =?UTF-8?q?=E6=BC=8F=E6=8C=82=20ch583)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/port.rs 的芯片选择是「每口一条 pub use ...::XxxPorting as Porting」+ 「兜底 DefaultPorting 的 not(any(...)) 名单」两处成对维护;上一提交新增 ch583 特征时两处都漏了,导致开 ch583 特征时 Porting 落到 DefaultPorting (方法全是 unimplemented!())——内核 xtask::start() 走 Porting::start_scheduler, 即 CH583 例程能编过(port.S 的 global_asm 与 #[no_mangle] 符号仍在,构建门禁 是绿的)但实际未接上本口。 - 补 #[cfg(all(feature = "ch583", not(test)))] pub use Ch583Porting as Porting; - 兜底 DefaultPorting 的 any() 名单补 feature = "ch583" 取证(构建产物反汇编):修前 mscratch 在整份 disasm 里只出现一次 (_start_trap 的 csrr 读),restore_ctx.S 的 csrw mscratch 写缺失、且无 Ch583Porting 符号;修后 _...ch583..Ch583Porting..Portable..start_scheduler 出现,csrw mscratch 出现。本次改动仅补 cfg 接线,不动 ch583 口本身。 --- src/port.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/port.rs b/src/port.rs index 0227157..7a14b51 100644 --- a/src/port.rs +++ b/src/port.rs @@ -23,6 +23,8 @@ pub use crate::chip::ch32v307::Ch32v307Porting as Porting; pub use crate::chip::ch32v203::Ch32v203Porting as Porting; #[cfg(all(feature = "ch32v103", not(test)))] pub use crate::chip::ch32v103::Ch32v103Porting as Porting; +#[cfg(all(feature = "ch583", not(test)))] +pub use crate::chip::ch583::Ch583Porting as Porting; #[cfg(all(feature = "esp32c3", not(test)))] pub use crate::chip::esp32c3::Esp32c3Porting as Porting; #[cfg(all(feature = "qemu_riscv", not(test)))] @@ -43,6 +45,7 @@ pub use crate::chip::qemu_arm_r52::QemuArmR52Porting as Porting; feature = "ch32v307", feature = "ch32v203", feature = "ch32v103", + feature = "ch583", feature = "esp32c3", feature = "qemu_riscv", feature = "qemu_arm_r52" From 42f087f6eee0cf0c175cb3639e75155b54fd85a1 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 11:43:01 +0800 Subject: [PATCH 03/11] =?UTF-8?q?fix(port):=20=E8=8A=AF=E7=89=87=E6=8E=A5?= =?UTF-8?q?=E7=BA=BF=E6=94=B6=E6=88=90=E5=8D=95=E4=B8=80=E6=9D=A5=E6=BA=90?= =?UTF-8?q?=E6=B8=85=E5=8D=95=20+=20build.rs=20=E6=8E=A5=E7=BA=BF=E5=AE=88?= =?UTF-8?q?=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为什么改:正选别名与兜底桩名单原本两份手写,新增芯片只改一份时 Porting 静默落到 DefaultPorting(unimplemented!),构建与门禁全绿、运行期 xtask::start() 才 panic(ch583 骨架 PR 实测踩过,见 issue #14/PR #15)。 改法:chip_portings! 清单一次登记同时生成 正选别名/兜底 not(any(...))/RealPorting 标记实现+编译期断言;build.rs 再按 src/chip/<名字> 目录 ⇄ 启用 feature ⇄ 登记表 三方交叉,把漏登记变成构建期失败。 验证:删掉 ch583 登记行 → build.rs 报红(阳性对照);13 个移植口 --lib 全绿(riscv32imac 8 口/thumbv7em 2 口/thumbv7m/thumbv6m/armv7r)。 --- build.rs | 44 +++++++++++++++++++++ src/port.rs | 107 +++++++++++++++++++++++++++------------------------- 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/build.rs b/build.rs index ffa3933..16e018a 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,51 @@ use std::path::PathBuf; use std::{env, fs}; +/// 芯片接线守卫:`src/chip/<名字>/` 存在、且 `<名字>` feature 被启用时, +/// 该 feature 必须在 `src/port.rs` 的 `chip_portings!` 清单里登记。 +/// +/// 漏登记的后果不是编译错误而是**静默降级**:`Porting` 落到兜底桩 +/// `DefaultPorting`(各方法 `unimplemented!()`),构建与门禁全绿,直到运行期 +/// `xtask::start()` 才 panic(2026-09-18 ch583 骨架 PR 实测踩过,见 issue #14 / +/// PR #15)。这里用"目录 ⇄ feature ⇄ 登记表"三方交叉把它变成构建期失败。 +/// +/// 阳性对照:`Cargo.toml` 开 `--features ch583` 后删掉 `src/port.rs` 里 +/// `"ch583" => ...` 那一行,构建必须报本守卫(而不是编过)。 +fn check_chip_porting_wiring(manifest_dir: &str) { + let chip_dir = PathBuf::from(manifest_dir).join("src/chip"); + let port_rs = fs::read_to_string(PathBuf::from(manifest_dir).join("src/port.rs")) + .expect("读不到 src/port.rs(芯片接线守卫需要它)"); + let mut missing = Vec::new(); + for entry in fs::read_dir(&chip_dir).expect("读不到 src/chip/") { + let entry = entry.expect("src/chip/ 条目读取失败"); + if !entry.file_type().expect("条目类型读取失败").is_dir() { + continue; // env.rs / mod.rs 等文件跳过 + } + let name = entry.file_name().to_string_lossy().into_owned(); + // 该芯片 feature 是否启用(cargo 把启用的 feature 变成 CARGO_FEATURE_*) + let env_key = format!("CARGO_FEATURE_{}", name.to_uppercase().replace('-', "_")); + if env::var_os(&env_key).is_none() { + continue; + } + if !port_rs.contains(&format!("\"{name}\" =>")) { + missing.push(name); + } + } + if !missing.is_empty() { + panic!( + "芯片 feature {missing:?} 未在 src/port.rs 的 chip_portings! 清单登记 —— \ + Porting 会静默落到 DefaultPorting(unimplemented!),运行期才 panic" + ); + } +} + fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out_dir.display()); + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + check_chip_porting_wiring(&manifest_dir); + #[cfg(feature = "gd32vf103")] fs::copy("src/chip/gd32vf103/memory.x", out_dir.join("memory.x")).unwrap(); @@ -42,6 +83,9 @@ fn main() { fs::copy("src/chip/cm32m4/memory.x", out_dir.join("memory.x")).unwrap(); // rerun-if-changed 指向真实被复制的源文件(指错路径时改 memory.x 不触发重建) + // src/port.rs 与 src/chip/* 供上面的接线守卫:改动必须触发本脚本重跑 + println!("cargo:rerun-if-changed=src/port.rs"); + println!("cargo:rerun-if-changed=src/chip"); println!("cargo:rerun-if-changed=src/chip/gd32vf103/memory.x"); println!("cargo:rerun-if-changed=src/chip/rp2040/memory.x"); println!("cargo:rerun-if-changed=src/chip/stm32f4/memory.x"); diff --git a/src/port.rs b/src/port.rs index 7a14b51..8b007bc 100644 --- a/src/port.rs +++ b/src/port.rs @@ -1,57 +1,60 @@ //! 移植层定义&配置 -#[cfg(all(feature = "gd32vf103", not(test)))] -pub use crate::chip::gd32vf103::Gd32vf103Porting as Porting; - -#[cfg(all(feature = "stm32f4", not(test)))] -pub use crate::chip::stm32f4::STM32F4Porting as Porting; - -#[cfg(all(feature = "stm32f1", not(test)))] -pub use crate::chip::stm32f1::STM32F1Porting as Porting; - -#[cfg(all(feature = "rp2040", not(test)))] -pub use crate::chip::rp2040::RP2040Porting as Porting; - -#[cfg(all(feature = "stm32h7", not(test)))] -pub use crate::chip::stm32h7::STM32H7Porting as Porting; - -#[cfg(all(feature = "cm32m4", not(test)))] -pub use crate::chip::cm32m4::CM32M4Porting as Porting; -#[cfg(all(feature = "ch32v307", not(test)))] -pub use crate::chip::ch32v307::Ch32v307Porting as Porting; -#[cfg(all(feature = "ch32v203", not(test)))] -pub use crate::chip::ch32v203::Ch32v203Porting as Porting; -#[cfg(all(feature = "ch32v103", not(test)))] -pub use crate::chip::ch32v103::Ch32v103Porting as Porting; -#[cfg(all(feature = "ch583", not(test)))] -pub use crate::chip::ch583::Ch583Porting as Porting; -#[cfg(all(feature = "esp32c3", not(test)))] -pub use crate::chip::esp32c3::Esp32c3Porting as Porting; -#[cfg(all(feature = "qemu_riscv", not(test)))] -pub use crate::chip::qemu_riscv::QemuRiscvPorting as Porting; - -#[cfg(all(feature = "qemu_arm_r52", not(test)))] -pub use crate::chip::qemu_arm_r52::QemuArmR52Porting as Porting; - -#[cfg(all( - not(test), - not(any( - feature = "gd32vf103", - feature = "stm32f4", - feature = "stm32f1", - feature = "rp2040", - feature = "stm32h7", - feature = "cm32m4", - feature = "ch32v307", - feature = "ch32v203", - feature = "ch32v103", - feature = "ch583", - feature = "esp32c3", - feature = "qemu_riscv", - feature = "qemu_arm_r52" - )) -))] -pub use DefaultPorting as Porting; +// ---- 芯片移植实现的**单一来源清单** ---------------------------------------- +// 每个芯片在这里登记一次(feature 名 => 实现类型),下面由宏一次性生成: +// ① 正选别名 `pub use <实现类型> as Porting`; +// ② 兜底桩的 feature 名单(`not(any(...))` → `DefaultPorting`); +// ③ 编译期守卫:`RealPorting` 标记实现 + "选了芯片就不许落兜底桩"断言。 +// +// 为什么收成一处:①与②原本是两份手写名单,新增芯片时只改一份,`Porting` +// 就静默落到 `DefaultPorting`(各方法 `unimplemented!()`),构建与门禁全绿、 +// 直到运行期 `xtask::start()` 才 panic——2026-09-18 的 ch583 骨架 PR 实测 +// 踩过此坑(见 PR #15 自审节 / issue #14)。 +macro_rules! chip_portings { + ($($feat:literal => $ty:path),* $(,)?) => { + $( + #[cfg(all(feature = $feat, not(test)))] + pub use $ty as Porting; + )* + + #[cfg(all(not(test), not(any($(feature = $feat),*))))] + pub use DefaultPorting as Porting; + + /// 真实芯片移植实现的标记 trait:兜底桩(`DefaultPorting`)与 host + /// mock(`HostPorting`)故意不实现——配合下面的编译期断言,把"选了 + /// 芯片 feature 却落到兜底桩"钉成编译错误(否则要等运行期 + /// `unimplemented!()` 才暴露)。 + pub trait RealPorting {} + + $( + #[cfg(all(feature = $feat, not(test)))] + impl RealPorting for $ty {} + )* + + // 编译期守卫:选了任一芯片 feature 时,`Porting` 必须是真实实现 + #[cfg(all(not(test), any($(feature = $feat),*)))] + const _: fn() = { + fn assert_real_porting() {} + assert_real_porting:: + }; + }; +} + +chip_portings! { + "gd32vf103" => crate::chip::gd32vf103::Gd32vf103Porting, + "stm32f4" => crate::chip::stm32f4::STM32F4Porting, + "stm32f1" => crate::chip::stm32f1::STM32F1Porting, + "rp2040" => crate::chip::rp2040::RP2040Porting, + "stm32h7" => crate::chip::stm32h7::STM32H7Porting, + "cm32m4" => crate::chip::cm32m4::CM32M4Porting, + "ch32v307" => crate::chip::ch32v307::Ch32v307Porting, + "ch32v203" => crate::chip::ch32v203::Ch32v203Porting, + "ch32v103" => crate::chip::ch32v103::Ch32v103Porting, + "ch583" => crate::chip::ch583::Ch583Porting, + "esp32c3" => crate::chip::esp32c3::Esp32c3Porting, + "qemu_riscv" => crate::chip::qemu_riscv::QemuRiscvPorting, + "qemu_arm_r52" => crate::chip::qemu_arm_r52::QemuArmR52Porting, +} // host 测试环境:提供一个可运行的 Porting mock,让纯逻辑(信号量、队列、总线、延时队列) // 能在 `cargo test` 下被驱动。单线程语义下临界区只是一个标记,无需真实关中断。 From 7385bf4f6beaa6ffb9546ff5a0db30c0382ef5be 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 11:43:08 +0800 Subject: [PATCH 04/11] =?UTF-8?q?fix(chip):=20CH583=20=E5=A4=8D=E5=88=BB?= =?UTF-8?q?=20ROM=20=E5=90=AF=E5=8A=A8=E5=A4=B4(.bootvec),=E5=B9=B6?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E5=AE=98=E6=96=B9=20SysTick/CSR=20=E5=86=99?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为什么改(按官方 openwch/ch583 EVT 源码核对,替换此前的推断): 1) ROM 启动头缺失:官方 Startup/startup_CH583.S 的 .vector 段第 5 字固定 0xF3F9BDA9(注释 boot option, can't modify),官方 Ld/Link.ld 把 .vector 紧跟在 .init 的 j handle_reset 之后 → 魔数落在 flash 0x14。此前本口 flash 0x14 是 riscv-rt 的启动代码,出厂(BootLoader 默认开)芯片不会进用户程序。现加 .bootvec 段复刻同一布局:0x00 入口跳转 + 向量表前 5 字,memory.x 用 SECTIONS 摆位并把 _stext 顶到 0x18,链接期 ASSERT 钉住 0x0/0x14。 2) SysTick CTLR:官方 RVMSIS/core_riscv.h 的 SysTick_Config() 写 INIT|STRE|STCLK|STIE|STE = 0x2F,不带 SWIE。原 0x8000_002F 的 SWIE 会让第一个任务 mret 后立刻多进一次 trap,且该 trap 早于 restore_ctx.S 写 mscratch(隐含依赖 MIE=0);去掉即无此依赖。 3) csrw 0xbc0,0x1f 与官方 reset 序列逐字一致(已按源码核对),保留并把注释换成出处引用。 验证:构建过;产物级检查 ci/check_ch583_boot.py PASS(入口跳转 + boot option@0x14);阳性对照——魔数改 0xF3F9BDAA → 脚本报红;.bootvec 挪到 0x4 → 链接期 ASSERT 报红。 --- src/chip/ch583/memory.x | 28 ++++++++++++++++++++++-- src/chip/ch583/mod.rs | 21 ++++++++++++------ src/chip/ch583/port.S | 48 +++++++++++++++++++++++++++++++++++------ 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/chip/ch583/memory.x b/src/chip/ch583/memory.x index 37c79f9..0360623 100644 --- a/src/chip/ch583/memory.x +++ b/src/chip/ch583/memory.x @@ -1,8 +1,14 @@ -/* CH583(QingKe V4A)内存布局 —— 照抄官方 EVT `Link.ld`(SRC/Ld/Link.ld): +/* CH583(QingKe V4A)内存布局 —— 对齐官方 EVT `Ld/Link.ld`(SRC/Ld/Link.ld): FLASH @0x00000000,LENGTH 448K(⚠️ 基址是 0,不是 0x08000000; 尾部 64K(0x70000-0x7FFFF)留给 BootLoader/ISP/配置区,故只用 448K); RAM @0x20000000,LENGTH 32K。 - 真机核对点:0x00000000 是否可写、是否存在 0x08000000 别名。 */ + 真机核对点:0x00000000 是否可写、是否存在 0x08000000 别名。 + + 前 0x18 字节是**启动头**(`.bootvec`,见 port.S):WCH ROM 从 0x0 取指, + 并按"向量表第 5 个字 = boot option 0xF3F9BDA9"识别有效用户程序(官方 + startup_CH583.S 的 `.vector` 段,注释 "boot option, can't modify"; + 官方 Link.ld 里该字落在 flash 0x14)。故 `.text` 顶到 0x18 起(见下面 + `_stext` 赋值),启动头用下面的 SECTIONS 显式摆到 flash 0x0。 */ MEMORY { FLASH : ORIGIN = 0x00000000, LENGTH = 448K @@ -15,3 +21,21 @@ REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); REGION_ALIAS("REGION_HEAP", RAM); REGION_ALIAS("REGION_STACK", RAM); + +SECTIONS +{ + /* 启动头:flash 0x00..0x17(ROM 认的启动头字节,内容见 port.S) */ + .bootvec 0x00000000 : + { + KEEP(*(.bootvec)); + } > FLASH +} + +/* riscv-rt 的 link.x 用 `PROVIDE(_stext = ORIGIN(REGION_TEXT))` 兜底;本文件 + 先于 link.x 处理,这里先定义 `_stext`,把 `.text` 顶到启动头之后(0x18), + 避免与 `.bootvec` 抢 0x0(PROVIDE 不会覆盖已定义符号)。 */ +_stext = 0x00000018; + +/* 链接期守卫:启动头与 boot option 必须落在 ROM 认的地址上(符号见 port.S) */ +ASSERT(_bootvec == 0x00000000, "ERROR(ch583): 启动头 .bootvec 必须落在 flash 0x0"); +ASSERT(_boot_magic == 0x00000014, "ERROR(ch583): boot option 0xF3F9BDA9 必须落在 flash 0x14"); diff --git a/src/chip/ch583/mod.rs b/src/chip/ch583/mod.rs index 43d4baf..3944edd 100644 --- a/src/chip/ch583/mod.rs +++ b/src/chip/ch583/mod.rs @@ -28,12 +28,15 @@ //! ①`0x00000000` 是否可写 / 是否有 `0x08000000` 别名; //! ②默认主频 = 60MHz(官方 `CH58x_common.h` 的 `FREQ_SYS` 默认值;env 常数按此配, //! PLL 配好后须同步改); -//! ③`csrw 0xbc0, 0x1f` 是否在 CH583 上必须; +//! ③`csrw 0xbc0, 0x1f`(官方 `startup_CH583.S` 的 reset 序列同款,已按 SDK +//! 源码核对;上板只需确认不触发 Illegal Instruction); //! ④`0x804=0` 后异常/中断入口是否确实不再硬件压栈(决定 36 字帧成立); //! ⑤`STK_CTLR.SWIE` 置 1 后是否即刻进 `SysTick`、`CNTIF` 是否仍为 0; //! ⑥`mcycle` 是否实现(决定 `delay_us`,见下)。 -//! BootLoader 默认开启,量产配置里关掉(或接受代码从 0 起、向量表第 5 字保持 -//! `0xF3F9BDA9`)。 +//! BootLoader 默认开启:本口已在 flash `0x0` 复刻官方启动头(`.bootvec` = +//! 入口跳转 + 向量表前 5 字,第 5 字为 boot option `0xF3F9BDA9`;见 `port.S` +//! 与 `memory.x` 的链接期 ASSERT)。若上板仍停在 ISP,则改用关闭 BootLoader 的 +//! 配置字。 //! //! **零 PAC 依赖**:CH58x 无现成 `ch32-rs` PAC,本口全量直址访问 //! (与 `qemu_riscv` 口的思路一致),`Cargo.toml` 只挂 `riscv-rt`。 @@ -67,9 +70,15 @@ pub(crate) fn setup_intrrupt() { const TICKS: u32 = (SYSTICK_CLOCK_HZ / TICK_CLOCK_HZ) as u32 - 1; stk.add(5).write_volatile(0); // CMPHR stk.add(4).write_volatile(TICKS); // CMPLR - // 2. CTLR = SWIE(31)|INIT(5)|STRE(3)|STCLK(HCLK,2)|STIE(1)|STE(0) - // = 0x8000_002F(官方 SysTick_Config 惯用法 + SWIE 初始触发一次) - stk.write_volatile(0x8000_002F); + // 2. CTLR = INIT(5)|STRE(3)|STCLK(HCLK,2)|STIE(1)|STE(0) = 0x2F + // ——逐位对齐官方 `EVT/EXAM/SRC/RVMSIS/core_riscv.h` 的 + // `SysTick_Config()`:`SysTick->CMP = ticks - 1;` 然后 + // `CTLR = INIT|STRE|STCLK|STIE|STE`。 + // **官方不带 SWIE(bit31)**:SWIE 只在运行期用于请求切换(见 `irq()`)。 + // 若初始化时置 SWIE,第一个任务 `mret`(MIE←MPIE=1)后会立刻多进 + // 一次 trap,而该 trap 早于 restore_ctx.S 写 mscratch → 隐含依赖 + // "进入 setup_intrrupt 时 mstatus.MIE=0";去掉后无此依赖。 + stk.write_volatile(0x0000_002F); } // 3. PFIC:使能 SysTick(IENR[0] @0xE000E100,bit12 = 核中断号 12) let ienr0 = (PFIC_BASE + 0x100) as *mut u32; diff --git a/src/chip/ch583/port.S b/src/chip/ch583/port.S index 6bbdb64..489c1db 100644 --- a/src/chip/ch583/port.S +++ b/src/chip/ch583/port.S @@ -169,14 +169,17 @@ _switch_and_restore: .section .text .global _setup_interrupts _setup_interrupts: - // CH57x 私有 CSR 0xbc0:流水线控制位 + 动态分支预测控制位。 - // 官方 startup_CH583.S 复位序列固定写 0x1f(⚠️ CH572 是 0x25+0xbc1=1, - // CH583 只写 0xbc0=0x1f)。真机核对点:若 CH583 上不需要,可去掉这一条。 + // 私有 CSR 0xbc0(流水线控制 + 动态分支预测控制位),取值与官方 + // `EVT/EXAM/SRC/Startup/startup_CH583.S` 的 reset 序列逐字一致: + // li t0, 0x1f + // csrw 0xbc0, t0 + // (CH572 为 0x25 且另有 0xbc1=1,本口不适用) li t0, 0x1f csrw 0xbc0, t0 - // 显式关闭 HPE(QingKe 自定义 CSR 0x804:intsyscr;CH583 硬件压栈由 - // bit0 HWSTKEN 控制,写 0 = 纯软件全保存模型)—— - // 硬件序言是 WCH 私有工具链特性,与 riscv-rt/裸 Rust 不兼容 + // 关闭 HPE:CH583 的 INTSYSCR(自定义 CSR 0x804)bit0=HWSTKEN、 + // bit1=INESTEN。官方 startup 写 0x3(开硬件压栈 + 中断嵌套,配 HPE 序言), + // 本口写 0 = 纯软件全保存模型(硬件序言是 WCH 私有工具链特性,与 + // riscv-rt/裸 Rust 不兼容),这也是本口 36 字帧成立的前提。 csrw 0x804, zero // mtvec = _start_trap,Direct 模式(低位 0) la t0, _start_trap @@ -200,3 +203,36 @@ _task_entry_trampoline: lw t1, 8(t0) // Task.entry(offset 8,见 mod.rs 布局断言) jalr t1 // 标准调用:a0=args(帧设置),ra=返回蹦床 j task_exit // 入口跑完 → 退出路径(不返回) + +/* + 启动头(flash 0x00..0x17,由 memory.x 的 SECTIONS 显式摆位 + 链接期 ASSERT): + + WCH ROM 从 flash 0x0 取指,并按"向量表第 5 个字 = boot option"识别有效用户 + 程序。官方 `EVT/EXAM/SRC/Startup/startup_CH583.S` 的 `.vector` 段: + + .word 0 + .word 0 + .word NMI_Handler + .word HardFault_Handler + .word 0xF3F9BDA9 // boot option, can't modify + + 官方 `Ld/Link.ld` 把 `.vector` 的 flash LMA 紧跟在 `.init` 的 + `_start: j handle_reset` 之后(即 flash 0x04),所以魔数落在 flash **0x14**。 + 本口保持 Direct mtvec(CPU 不读这张表),这里只为对齐 ROM 的启动头布局: + 入口跳转接 riscv-rt 的 `_start`,随后 5 字复刻官方向量表前 5 项。 +*/ +.section .bootvec, "ax", @progbits +.option push +.option norvc // 4 字节定长(官方 .vector 亦 .option norvc) +.align 2 +.global _bootvec +_bootvec: + j _start // 0x00:ROM 入口(官方 .init 的 `j handle_reset` 同位) + .word 0 // 0x04:向量表[0](官方为 0) + .word 0 // 0x08:向量表[1](官方为 0) + .word _start_trap // 0x0c:向量表[2] NMI + .word _start_trap // 0x10:向量表[3] HardFault + .global _boot_magic +_boot_magic: + .word 0xF3F9BDA9 // 0x14:向量表[4] boot option, can't modify +.option pop From 3e0d82cfda760ac0ee20a9fa9393fc8445b2a6b9 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 11:43:13 +0800 Subject: [PATCH 05/11] =?UTF-8?q?fix(chip):=20CH583=20=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E5=A0=86=2016K=E2=86=9224K,=E9=81=BF=E5=85=8D=E4=B8=8A?= =?UTF-8?q?=E6=9D=BF=20OOM=20=E8=A2=AB=E8=AF=AF=E5=88=A4=E6=88=90=E5=AF=84?= =?UTF-8?q?=E5=AD=98=E5=99=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本示例(ch583,+timer)要起 15 个默认任务(256 字栈 ≈1.03K/个)+ 软件定时器任务(1024 字 ≈4.1K),加 Task 结构与同步对象 ≈21.5K,全部走堆;原 16K 必 OOM。32K SRAM 里 _sheap(0x20001188) 到 _stack_start(0x20008000) 有 ~27.6K,取 24K 后堆顶 0x20007188,与中断栈(_stack_start-512)仍留 1.6K 余量。 --- examples/multitask_ch583.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/multitask_ch583.rs b/examples/multitask_ch583.rs index 144f364..7f12de4 100644 --- a/examples/multitask_ch583.rs +++ b/examples/multitask_ch583.rs @@ -31,7 +31,13 @@ fn main() -> ! { static _sheap: u8; } let start_addr = unsafe { &_sheap as *const u8 as usize }; - xtask::init_heap(start_addr, 16 * 1024); // 32K SRAM:静态+栈后保守 16 堆 + // 32K SRAM:静态(data 4.1K + bss 0.3K)后到 _stack_start 还有 ~27.6K。 + // 本示例要起的任务栈(全部从堆上分配):15 个默认任务 ×256 字(≈1.03K) + // + 软件定时器任务 1024 字(≈4.1K),加 Task 结构与同步对象 ≈21.5K, + // 故取 24K(堆顶 0x20007188,距 _stack_start 0x20008000 仍留 3.6K)。 + // ⚠️ 若把堆改回 16K(本 PR 之前的值),上板第一次创建任务就会 + // `panic!("memory out")`——会被误判成移植层的寄存器问题。 + xtask::init_heap(start_addr, 24 * 1024); //example_notify(); //example_broadcast(); From 805304a229b0dc4e2dd4892b71a9c312c765c381 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 11:43:13 +0800 Subject: [PATCH 06/11] =?UTF-8?q?ci(chip):=20ch583=20=E6=9E=84=E5=BB=BA=20?= =?UTF-8?q?+=20=E5=90=AF=E5=8A=A8=E5=A4=B4=E4=BA=A7=E7=89=A9=E7=BA=A7?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E8=BF=9B=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ch583(QingKe V4A)没有 QEMU 机器可执行,门禁补两件事:①cargo build --example multitask_ch583 --features ch583,timer;②新增 ci/check_ch583_boot.py 读 ELF 校验 .bootvec 回到 flash 0x0..0x17、入口是 4 字节跳转、boot option 0xF3F9BDA9 落在 0x14。缺了它,ROM 不引导而构建/门禁全绿(假绿)。 --- ci/check_ch583_boot.py | 89 ++++++++++++++++++++++++++++++++++++++++++ ci/gate.sh | 11 ++++++ 2 files changed, 100 insertions(+) create mode 100644 ci/check_ch583_boot.py diff --git a/ci/check_ch583_boot.py b/ci/check_ch583_boot.py new file mode 100644 index 0000000..000c90b --- /dev/null +++ b/ci/check_ch583_boot.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""校验 CH583 启动头字节布局(WCH ROM 认的硬要求)。 + +WCH ROM 从 flash 0x0 取指,并按"向量表第 5 个字 = boot option"识别有效用户 +程序:官方 EVT/EXAM/SRC/Startup/startup_CH583.S 的 `.vector` 段第 5 个字固定 +0xF3F9BDA9(注释 "boot option, can't modify"),官方 Ld/Link.ld 把 `.vector` +的 flash LMA 紧跟在 `.init` 的 `j handle_reset` 之后 → 魔数落在 flash 0x14。 + +本口用 `.bootvec` 段复刻同一布局(src/chip/ch583/port.S 内容 + memory.x 摆位, +另有链接期 ASSERT);本脚本做**产物级**终检:段地址/大小、入口跳转、魔数字节。 +少了它,板上表现是"停在 ISP、不进用户程序",而构建与门禁全绿。 + +用法: python3 ci/check_ch583_boot.py +""" +import struct +import sys + +MAGIC = 0xF3F9BDA9 +BOOT_LEN = 0x18 + + +def main(): + path = sys.argv[1] if len(sys.argv) > 1 else \ + "target/riscv32imac-unknown-none-elf/release/examples/multitask_ch583" + data = open(path, "rb").read() + assert data[:4] == b"\x7fELF", "不是 ELF 文件" + + e_shoff = struct.unpack_from(" seg[1]: + print(f"FAIL: flash 0x{a:X} 超出 LOAD 段(0x{seg[1]:X} 字节)") + sys.exit(1) + return struct.unpack_from(" Date: Sat, 19 Sep 2026 11:43:17 +0800 Subject: [PATCH 07/11] =?UTF-8?q?docs:=20ch583=20=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E8=BF=9B=E8=8A=AF=E7=89=87=E6=B8=85=E5=8D=95/=E6=9D=90?= =?UTF-8?q?=E6=96=99=E8=A1=A8,=E5=AE=BF=E4=B8=BB=E5=9B=9E=E5=BD=92?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=2080=E2=86=9282?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README 芯片清单补 CH58x: ch583(构建级验证 2026-09-19,真机待验,注明启动头已复刻);docs/README.md 补 ch583 的官方 EVT 素材来源(Startup/Ld/RVMSIS/StdPeriphDriver),把审查提的'寄存器常量依据不可复核'落成可追溯链接;CLAUDE.md 的 chip feature 清单补 ch583;README 默认 features 计数按今日实测 82 修正(旧记 80)。 --- CLAUDE.md | 2 +- README.md | 5 +++-- docs/README.md | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 89fb0f8..7354202 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ cargo run --example multitask_rp_pico --features rp2040 --target thumbv6m-none-e A default target (`thumbv7em-none-eabihf`) and per-target `runner` (probe-run / gdb+openocd / elf2uf2-rs) are set in `.cargo/config.toml`; `cargo run` uses the runner to flash. OpenOCD configs and GDB scripts live in `debug//`. Chip HALs are mostly crates, but `gd32vf103xx-hal` is a local path dep at `hal2/gd32vf103xx-hal`. -Chip features: `gd32vf103`, `stm32f1`, `stm32f4`, `stm32h7`, `cm32m4`, `rp2040`, `ch32v103`/`ch32v203`/`ch32v307`, `esp32c3`, plus `qemu_riscv` (QEMU virt; **execution-verified** — `ci/gate.sh` runs it; others build-verified 2026-08-23; real-board verification pending for f4/f1 constants, h7 timeline, cm32m4/rp2040). Non-chip features: `timer` (software timers), `tlsf` (global allocator backend swap to the hand-written mini TLSF, ch28), `debug_task`, `fs` (fatfs), `net` (smoltcp), `usb`, `ble`, `rtt_log` / `stdout_log`, board BSPs (`longan_nano`, `bluepill`, `greenpill`, `rp_pico`). +Chip features: `gd32vf103`, `stm32f1`, `stm32f4`, `stm32h7`, `cm32m4`, `rp2040`, `ch32v103`/`ch32v203`/`ch32v307`, `ch583` (CH58x / QingKe V4A; build-verified 2026-09-19, ROM boot header checked by `ci/check_ch583_boot.py`), `esp32c3`, plus `qemu_riscv` (QEMU virt; **execution-verified** — `ci/gate.sh` runs it; others build-verified 2026-08-23; real-board verification pending for f4/f1 constants, h7 timeline, cm32m4/rp2040). Non-chip features: `timer` (software timers), `tlsf` (global allocator backend swap to the hand-written mini TLSF, ch28), `debug_task`, `fs` (fatfs), `net` (smoltcp), `usb`, `ble`, `rtt_log` / `stdout_log`, board BSPs (`longan_nano`, `bluepill`, `greenpill`, `rp_pico`). ## Architecture diff --git a/README.md b/README.md index 1899297..ae5bdea 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ ### 验证体系 -- [x] 宿主回归:`cargo test --lib` 默认 features 实测 **80 passed**;门禁全特征组合(见 `ci/gate.sh`,含 fs/net/usb/ble)实测 **159 passed**(2026-09-03,rustc 1.97;旧计数 148 已按实测修正,历史见 issue #11;计数随新增测试浮动,以 `ci/gate.sh` 实测为准) +- [x] 宿主回归:`cargo test --lib` 默认 features 实测 **82 passed**(2026-09-19 实测,旧记 80 已修正);门禁全特征组合(见 `ci/gate.sh`,含 fs/net/usb/ble)实测 **159 passed**(2026-09-03,rustc 1.97;旧计数 148 已按实测修正,历史见 issue #11;计数随新增测试浮动,以 `ci/gate.sh` 实测为准) - [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 项多核调度门禁 -- [ ] 真机验证:gd32vf103 已验;f4/f1 常数、h7 时序、cm32m4、rp2040、ch32 系、esp32c3 待上板 +- [ ] 真机验证:gd32vf103 已验;f4/f1 常数、h7 时序、cm32m4、rp2040、ch32 系、ch58x、esp32c3 待上板 ### 移植的芯片 @@ -39,6 +39,7 @@ - [x] CH32V3: ch32v307(QingKe V4F;构建级验证 2026-08-23,真机待验) - [x] CH32V2: ch32v203(QingKe V4B;构建级验证 2026-08-23,真机待验) - [x] CH32V1: ch32v103(QingKe V3A;构建级验证 2026-08-23,真机待验) + - [x] CH58x: ch583(WCH CH583,QingKe V4A;构建级验证 2026-09-19,真机待验——ROM 启动头已按官方 `.vector` 复刻:入口跳转 + boot option `0xF3F9BDA9` @flash 0x14,见 `ci/check_ch583_boot.py`) - CM7F - [x] STM32H7B0VBT6 - CM4F diff --git a/docs/README.md b/docs/README.md index 2346975..3ca52af 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,6 +39,7 @@ DDI0403(v7-M) 当前网络无法直连 developer.arm.com 获取,上表两份 S | Nuclei N200 ISA 架构手册 CN / Bumblebee ISA 架构手册 CN | gd32vf103 与 cm32m4 的 ECLIC、嵌套深度 CSR(0x7ED)、msubm(0x7C4) —— `src/chip/{gd32vf103,cm32m4}/port.S` | [nucleisys.com 文档页](https://www.nucleisys.com/)(导航 Developer→Document;Nuclei 官网) | | Nuclei N200 Brief Datasheet CN / Bumblebee Brief Datasheet CN(+EN 架构手册) | 同上两口的内核数据面 | 同上(Nuclei 官网文档页) | | WCH 青稞 V4 处理器手册 CN | ch32v203/ch32v307 的 PFIC/SysTick/Stk 模型 —— `src/chip/ch32v20{3,7}/port*` | [ch32-riscv-ug GitHub 组织](https://github.com/ch32-riscv-ug)(镜像收全套装;wch.cn 官网为 SPA 拿不到直链) | +| 青稞 V4A 启动/中断实装(源码即手册) | ch583 的 PFIC/SysTick/`0xbc0`/`0x804` 与 ROM 启动头(`.vector` 第 5 字 boot option `0xF3F9BDA9`) —— `src/chip/ch583/port.{S,rs}` | [openwch/ch583](https://github.com/openwch/ch583)(官方 EVT:`EXAM/SRC/{Startup,Ld,RVMSIS,StdPeriphDriver}`;本地核对版已随本仓注释标注文件路径) | ## 芯片手册 @@ -48,6 +49,7 @@ DDI0403(v7-M) 当前网络无法直连 developer.arm.com 获取,上表两份 S | ch32v103 | CH32V103 DS0 CN + CH32xRM 参考手册 CN/EN | `ch32v103`(青稞 V3A) | [ch32-riscv-ug/CH32V103 镜像](https://github.com/ch32-riscv-ug/CH32V103) | | ch32v20x | CH32V20x DS0 CN | `ch32v203` | [ch32-riscv-ug 组织](https://github.com/ch32-riscv-ug) | | ch32v30x | CH32V30x DS0 EN | `ch32v307` | [ch32-riscv-ug 组织](https://github.com/ch32-riscv-ug) | +| ch58x | 官方 EVT 源码包(本仓按源码核对:`Startup/startup_CH583.S`、`Ld/Link.ld`、`RVMSIS/core_riscv.h`、`StdPeriphDriver/inc/CH583SFR.h、CH58x_common.h`) | `ch583`(青稞 V4A;DS/PM 正式手册未入库,寄存器语义以官方 EVT 源码为准) | [openwch/ch583](https://github.com/openwch/ch583) | | stm32f1 | RM0008(参考手册)+ DS5319(F103C8 数据手册) | `stm32f1`(bluepill) | [st.com RM0008](https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) / [DS5319](https://www.st.com/resource/en/datasheet/stm32f103c8.pdf) | | stm32f4 | RM0368(F401 参考手册)+ DS8626(F401CC 数据手册) | `stm32f4`(greenpill) | [st.com RM0368](https://www.st.com/resource/en/reference_manual/rm0368-stm32f401xbc-and-stm32f401xde-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) / [DS8626](https://www.st.com/resource/en/datasheet/stm32f401cc.pdf) | | stm32h7 | RM0455(H7B0 参考手册)+ H7B0 DS | `stm32h7`(注意是 H7B0, 非常见 H743) | [st.com RM0455](https://www.st.com/resource/en/reference_manual/rm0455-stm32h7b0xx-and-stm32h7b1xx-arm-cortex-m7-32bit-mcu-stmicroelectronics.pdf) | From e0d9b3dd934ea2272d60a2f637acf07d2f38235a 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 11:47:05 +0800 Subject: [PATCH 08/11] =?UTF-8?q?docs:=20=E9=AA=8C=E8=AF=81=E4=BD=93?= =?UTF-8?q?=E7=B3=BB/=E4=B8=80=E9=94=AE=E5=A4=8D=E7=8E=B0=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=20ch583=20=E5=90=AF=E5=8A=A8=E5=A4=B4=E4=BA=A7?= =?UTF-8?q?=E7=89=A9=E7=BA=A7=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README 验证体系补一条'产物级':ci/gate.sh 除 host 回归与 QEMU 执行级外,还校验 ch583 的 ROM 启动头(flash 0x00..0x17,向量表第 5 字 boot option 0xF3F9BDA9 @0x14),并说明该口没有 QEMU 机器可跑;一键复现的命令注释同步。 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae5bdea..3bfdb52 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - [x] 宿主回归:`cargo test --lib` 默认 features 实测 **82 passed**(2026-09-19 实测,旧记 80 已修正);门禁全特征组合(见 `ci/gate.sh`,含 fs/net/usb/ble)实测 **159 passed**(2026-09-03,rustc 1.97;旧计数 148 已按实测修正,历史见 issue #11;计数随新增测试浮动,以 `ci/gate.sh` 实测为准) - [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_ch583_boot.py` 钉产物字节,缺了它板上停在 ISP 而构建/门禁全绿 - [ ] 真机验证:gd32vf103 已验;f4/f1 常数、h7 时序、cm32m4、rp2040、ch32 系、ch58x、esp32c3 待上板 ### 移植的芯片 @@ -57,7 +58,7 @@ - **通道 A · 本机一条命令**(首次自动下载静态 QEMU 到 `.tools/`,约 90MB): ```bash - bash ci/gate.sh # host 回归 + 示例链接 + QEMU 执行级(24/24 等) + bash ci/gate.sh # host 回归 + 示例链接 + ch583 启动头校验 + QEMU 执行级(24/24 等) ``` - **通道 B · GitHub Codespaces**:仓库根有 `.devcontainer.json`,云端打开即跑,本机什么都不装 - **通道 C · 只看结果**:每次 push 的 CI 门禁(含 QEMU 执行级)全绿记录: From fcb4275e27dce2664fa0605469839455ee81411e 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 12:05:27 +0800 Subject: [PATCH 09/11] =?UTF-8?q?fix(chip):=20CH583=20=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E6=8C=89=E5=AE=9E=E6=B5=8B=E5=A0=86=E9=9C=80=E6=B1=82=E6=94=B6?= =?UTF-8?q?=E7=BC=A9=E5=88=B0=2012=20=E4=B8=AA=E4=BB=BB=E5=8A=A1(=E5=A0=86?= =?UTF-8?q?=2024K)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复审实测(QEMU 探针:同一 32 位分配器 / 同一 Task 布局;WCH 侧无法仿真,只能这样取数): 15 个示例任务 = 17768 B + 软件定时器任务(1024 字) + idle(512 字) + 计表任务 ≈ 24900 B(计表任务占 1.2K) 即原 15 任务组合启动就需 ~24.4K 堆;24K 会在 start() 里 panic(memory out)(已实测复现), 而 32K SRAM 里 _sheap 到 _stack_start 只有 28.3K,给不出 26K+ 堆。 故把示例任务 15 → 12(仅 example_task 的 5 个纯打印任务留 2 个;semaphore/queue 各 5 个不动, 同优先级时间片与 IPC 语义不变):12 任务 + timer + idle ≈ 20.9K,24K 堆余量 ≈3.6K, 堆顶 0x20007188 距中断栈基 0x20007E00 仍有 3.2K。注释写清实测数与不要再加任务的告警。 --- examples/multitask_ch583.rs | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/examples/multitask_ch583.rs b/examples/multitask_ch583.rs index 7f12de4..6b4c24e 100644 --- a/examples/multitask_ch583.rs +++ b/examples/multitask_ch583.rs @@ -32,11 +32,15 @@ fn main() -> ! { } let start_addr = unsafe { &_sheap as *const u8 as usize }; // 32K SRAM:静态(data 4.1K + bss 0.3K)后到 _stack_start 还有 ~27.6K。 - // 本示例要起的任务栈(全部从堆上分配):15 个默认任务 ×256 字(≈1.03K) - // + 软件定时器任务 1024 字(≈4.1K),加 Task 结构与同步对象 ≈21.5K, - // 故取 24K(堆顶 0x20007188,距 _stack_start 0x20008000 仍留 3.6K)。 - // ⚠️ 若把堆改回 16K(本 PR 之前的值),上板第一次创建任务就会 - // `panic!("memory out")`——会被误判成移植层的寄存器问题。 + // 任务栈全部从堆上分配,实测(QEMU 探针:同一 32 位分配器/同一 Task 布局, + // 方法见 PR #15 评论——WCH 侧无法仿真,只能这样取数): + // 默认任务(256 字栈 + Task 112B + 名字)≈ 1.19K + // 软件定时器任务(1024 字,timer 特征)≈ 4.4K + // idle 任务(512 字)≈ 2.2K + // 12 个示例任务 + timer + idle ≈ 20.9K,故取 24K(堆顶 0x20007188, + // 距中断栈基 0x20007E00 还有 3.2K,首次上板余量约 3.6K)。 + // ⚠️ 不要再往本示例加任务:15 个任务时实测需 24.4K,24K 堆会在 start() + // 里 `panic!("memory out")`——会被误判成移植层的寄存器问题。 xtask::init_heap(start_addr, 24 * 1024); //example_notify(); @@ -192,33 +196,14 @@ fn example_semaphore() { } fn example_task() { + // 只保留 2 个(原 ch32v103 示例是 5 个):32K SRAM 上 12 个示例任务 + + // timer + idle 实测 20.9K,再加任务会在 24K 堆上 OOM(见 main 里的实测注) xtask::spawn(|| { for i in 0..10 { log::info!("{} 循环测试任务0", i + 1); xtask::sleep_ms(1000); } }); - xtask::spawn(|| { - for i in 0..50 { - log::info!("{} 循环测试任务1", i + 1); - xtask::sleep_ms(1000); - } - }); - - xtask::spawn(|| { - for i in 0..100 { - log::info!("{} 循环测试任务2", i + 1); - xtask::sleep_ms(1000); - } - }); - - xtask::spawn(|| { - for i in 0..500 { - log::info!("{} 循环测试任务4", i + 1); - xtask::sleep_ms(1000); - } - }); - xtask::spawn(|| loop { log::info!("死循环测试任务 {}", tick()); xtask::sleep_ms(10000); From f7e0cfb659b340f08e01796939b7444e580eca8c 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 12:05:31 +0800 Subject: [PATCH 10/11] =?UTF-8?q?ci(chip):=20=E5=90=AF=E5=8A=A8=E5=A4=B4?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E8=84=9A=E6=9C=AC=E8=A1=A5=20ELF32=20?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=B9=B6=E6=8A=8A=E5=85=A5=E5=8F=A3=E6=94=BE?= =?UTF-8?q?=E5=AE=BD=E5=88=B0=20JAL/c.j?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复审发现两处: 1) 用 assert 做文件类型判断,-O 下会被关掉(校验不能依赖 assert);改为显式判断并补 ELF32/小端检查——否则指向 64 位 ELF 会以解析异常结束,而不是给出清晰 FAIL。 2) 只认 4 字节 JAL,而 2 字节 c.j 同样合法;真正钉死布局的是 .bootvec 段地址/长度与 魔数偏移,故放宽为 JAL/c.j 都接受,避免误报。 两项均实测:合成 ELF64 → 期望 32 位小端 ELF 报红;整改前 ch583 ELF → 没有 .bootvec 段报红。 --- ci/check_ch583_boot.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ci/check_ch583_boot.py b/ci/check_ch583_boot.py index 000c90b..bec2c4e 100644 --- a/ci/check_ch583_boot.py +++ b/ci/check_ch583_boot.py @@ -24,7 +24,13 @@ def main(): path = sys.argv[1] if len(sys.argv) > 1 else \ "target/riscv32imac-unknown-none-elf/release/examples/multitask_ch583" data = open(path, "rb").read() - assert data[:4] == b"\x7fELF", "不是 ELF 文件" + # 显式判断而非 assert:-O 下 assert 会被关掉,校验不能依赖它 + if len(data) < 0x34 or data[:4] != b"\x7fELF": + print("FAIL: 不是 ELF 文件") + return 1 + if data[4] != 1 or data[5] != 1: + print(f"FAIL: 期望 32 位小端 ELF,实测 class={data[4]} endian={data[5]}") + return 1 e_shoff = struct.unpack_from(" Date: Sat, 19 Sep 2026 12:27:19 +0800 Subject: [PATCH 11/11] =?UTF-8?q?fix(chip):=20=E6=8C=89=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E6=95=B4=E6=94=B9(=E4=B8=8A=E6=9D=BF?= =?UTF-8?q?=E6=B8=85=E5=8D=95=20CTLR=20=E6=97=A7=E5=80=BC=20/=20=E6=97=A0A?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E8=AF=B4=E6=98=8E=20/=20CTLR=20=E8=AF=BB?= =?UTF-8?q?=E6=94=B9=E5=86=99=E6=8E=A9=20INIT)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 独立审查(全新会话,只给任务书与一手资料)结论:需修改(2 处文档级)+ 1 条设计观察,逐条处理: 1) examples/multitask_ch583.rs 头部的上板核对清单第③条仍是旧值 CTLR=0x8000_002F, 而 7385bf4 已把代码改成 0x2F —— 照它验会验到固件根本没写的值。改为 0x2F,并把 第④项升级为"官方三套移植走 SWI_IRQn=14,本口 SWIE 路线待上板确认,备选改 IRQ 14"。 2) src/chip/ch583/mod.rs 模块文档仍写"无 A 扩展退路 = 换 riscv32imc target",与实测 (该 target 无 target_has_atomic,内核 fetch_add/swap 编不过)自相矛盾。改为 "需先做关中断/自旋锁原子垫片"。 3) 硬化:irq()/disable_irq() 对 CTLR 的读改写显式掩掉 INIT(bit5)。INIT 是否自清没有 一手证据(官方 SysTick_Config 只在初始化写一次 CTLR、之后从不读改写);若它是电平位, 回写会让每次 yield 重新装载 64 位计数器 → tick 账漂移。掩掉后两种情况都安全。 反汇编核对:irq() = lw / or SWIE / andi -0x21 / sw;disable_irq() = lw / and 0x7FFFFFDF / sw。 审查同时复现(未被推翻、无需改代码):官方常量与 flash 0x14 布局、产物级启动头、 三条守卫的阳性对照、门禁全绿(host 159 / ch583 启动头 / 乒乓 / kernel 24-24×3 / smp 9-9)、 13 口矩阵、宿主默认 82 passed。 --- examples/multitask_ch583.rs | 13 +++++++++---- src/chip/ch583/mod.rs | 28 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/examples/multitask_ch583.rs b/examples/multitask_ch583.rs index 6b4c24e..a017a47 100644 --- a/examples/multitask_ch583.rs +++ b/examples/multitask_ch583.rs @@ -9,10 +9,15 @@ use alloc::vec::Vec; // CH583(QingKe V4A,RISC-V RV32IMAC softfp)多任务示例 —— 以 ch32v103 为模板。 // ⚠️ 真机核对点(构建级验证已过,板上行为待验):①HPE 关闭路径 // (intsyscr 0x804 写 0 + 私有 CSR 0xbc0=0x1f);②mtvec Direct 分发与 -// mcause=12(SysTick);③SysTick CTLR=0x8000_002F + SR.CNTIF 写 0 清零; -// ④SWIE(bit31)触发软中断语义;⑤flash 基址 0x00000000(尾部 64K 留给 -// BootLoader);⑥默认主频 60MHz(env 按此配);⑦`mcycle` 是否实现(决定 -// `delay_us`,未实现则 `delay_us` 会死循环——见 chip/ch583/mod.rs 注)。 +// mcause=12(SysTick);③SysTick CTLR=0x2F(INIT|STRE|STCLK|STIE|STE, +// **不含 SWIE**——与官方 SysTick_Config 同值) + SR.CNTIF 写 0 清零; +// ④运行期 CTLR.SWIE(bit31)请求切换的语义(**官方 CH583 的 FreeRTOS/ +// RT-Thread/HarmonyOS 三套移植都不用它,一律走 SWI_IRQn=14 + SW_Handler; +// 本口沿用 ch32v103 模板的 SWIE,待上板确认,备选即改 IRQ 14**); +// ⑤flash 基址 0x00000000(尾部 64K 留给 BootLoader);⑥默认主频 60MHz +// (env 按此配);⑦`mcycle` 是否实现(决定 `delay_us`,未实现则 `delay_us` +// 会死循环——见 chip/ch583/mod.rs 注);⑧栈账:12 任务+timer+idle 实测 +// ≈20.9K 堆(见 main 注释,别再往上加任务)。 // // TODO(CH583 外设,待上板核对后补): // - GPIO:R32_PA_DIR/R32_PA_OUT @0x400010A0..(与 CH572 窗口大小不同), diff --git a/src/chip/ch583/mod.rs b/src/chip/ch583/mod.rs index 3944edd..9b6fd08 100644 --- a/src/chip/ch583/mod.rs +++ b/src/chip/ch583/mod.rs @@ -20,9 +20,16 @@ //! 写 1 立即触发 SysTick 中断——ISR 读 `STK_SR.CNTIF` 区分真 tick 与 //! 软中断请求。**与 ch32v103 完全同款**(CH572 的 SWIE 在 `SR` 而非 //! `CTLR`,故 CH572 需要另写,本 CH583 口可直接复用)。 +//! ⚠️ 但官方 CH583 的 FreeRTOS / RT-Thread / HarmonyOS 三套移植**都不走 +//! `CTLR.SWIE`**(SDK 里只定义、零使用):它们一律用 QingKe 专用 +//! `SWI_IRQn=14` + `SW_Handler`(FreeRTOS `portYIELD()` = +//! `PFIC_SetPendingIRQ(SWI_IRQn)`)。本口沿用 ch32v103 模板的 SWIE 路线, +//! 上板须确认(示例头部核对点④);若 SWIE 无效果,备选即改走 IRQ 14。 //! - **SysTick@0xE000F000**(WCH 自有,非 CLINT mtime);`SR.CNTIF` 写 0 清零。 -//! - **无 A 扩展退路**:若真机实测无 `A` 扩展,改用 -//! `--target riscv32imc-unknown-none-elf`(CH57x 的退路 target)。 +//! - **无 A 扩展(如 CH572)不是换个 target 的事**:`riscv32imc-unknown-none-elf` +//! 实测只有 `target_has_atomic_load_store`、没有 `target_has_atomic`,内核里 +//! `fetch_add`/`swap` 这类 RMW 编不过——需要先做"关中断/自旋锁"原子垫片; +//! CH583(V4A)有 A 扩展,本口不受影响。 //! //! ⚠️ 真机核对点(构建级验证,板上行为待验): //! ①`0x00000000` 是否可写 / 是否有 `0x08000000` 别名; @@ -145,20 +152,29 @@ impl Portable for Ch583Porting { /// 软中断(调度请求):写 STK_CTLR.SWIE(bit31)触发 SysTick 入口。 /// ISR 读 SR.CNTIF 区分真 tick 与本请求(见 port.rs 的 SysTick 处理)。 - /// **与 ch32v103 同款**(CH572 的 SWIE 在 SR,本口不适用) + /// 官方 CH583 三套移植走 `SWI_IRQn=14`(见模块头注),本口沿用模板的 SWIE。 + /// + /// 读改写时**显式掩掉 INIT(bit5)**:INIT 与 SWIE 同寄存器,而它是否为 + /// 自清触发位没有一手证据(官方 `SysTick_Config` 只在初始化写一次 CTLR、 + /// 之后从不读改写,SDK 给不出答案)。若它是电平位,回写会让每次 yield 都 + /// 重新装载 64 位计数器 → tick 账漂移;掩掉后两种情况都安全。 #[inline] fn irq() { + const SWIE: u32 = 1 << 31; + const INIT: u32 = 1 << 5; let ctlr = STK_BASE as *mut u32; unsafe { - ctlr.write_volatile(ctlr.read_volatile() | (1 << 31)); + ctlr.write_volatile((ctlr.read_volatile() & !INIT) | SWIE); } } - /// 关闭软中断(SWIE 是触发位,自清;防御性清一下) + /// 关闭软中断(SWIE 是触发位,自清;防御性清一下——同样掩掉 INIT) #[inline] fn disable_irq() { + const SWIE: u32 = 1 << 31; + const INIT: u32 = 1 << 5; let ctlr = STK_BASE as *mut u32; unsafe { - ctlr.write_volatile(ctlr.read_volatile() & !(1 << 31)); + ctlr.write_volatile(ctlr.read_volatile() & !(SWIE | INIT)); } }