Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 待上板

### 移植的芯片
Expand Down
7 changes: 7 additions & 0 deletions ci/gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
170 changes: 170 additions & 0 deletions examples/board_check_ch57x.rs
Original file line number Diff line number Diff line change
@@ -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()
}
35 changes: 35 additions & 0 deletions src/bsp/ch57x/mod.rs
Original file line number Diff line number Diff line change
@@ -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) }
}
67 changes: 67 additions & 0 deletions src/bsp/ch57x/uart.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
5 changes: 5 additions & 0 deletions src/bsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading