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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>/`. 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

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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=[]
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@

### 验证体系

- [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 待上板
- [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 待上板

### 移植的芯片

Expand All @@ -39,6 +40,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
Expand All @@ -56,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 执行级)全绿记录:
Expand Down
47 changes: 47 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -26,6 +67,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")]
Expand All @@ -40,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");
Expand All @@ -49,6 +95,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");
Expand Down
99 changes: 99 additions & 0 deletions ci/check_ch583_boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/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 <target/.../examples/multitask_ch583>
"""
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:-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("<I", data, 0x20)[0]
e_shentsize = struct.unpack_from("<H", data, 0x2E)[0]
e_shnum = struct.unpack_from("<H", data, 0x30)[0]
e_shstrndx = struct.unpack_from("<H", data, 0x32)[0]
secs = [struct.unpack_from("<IIIIIIIIII", data, e_shoff + i * e_shentsize) for i in range(e_shnum)]
shstr_off = secs[e_shstrndx][4]

def sname(i):
n = secs[i][0]
end = data.index(b"\x00", shstr_off + n)
return data[shstr_off + n:end].decode("latin-1")

boot = [s for i, s in enumerate(secs) if sname(i) == ".bootvec"]
if not boot:
print("FAIL: 没有 .bootvec 段——ROM 认的启动头缺失(见 src/chip/ch583/port.S)")
return 1
addr, size = boot[0][3], boot[0][5]
if addr != 0 or size != BOOT_LEN:
print(f"FAIL: .bootvec 应落在 flash 0x0 起 0x{BOOT_LEN:X} 字节,实测 0x{addr:X} + 0x{size:X}")
return 1

e_phoff = struct.unpack_from("<I", data, 0x1C)[0]
e_phentsize = struct.unpack_from("<H", data, 0x2A)[0]
e_phnum = struct.unpack_from("<H", data, 0x2C)[0]
seg = None
for i in range(e_phnum):
p_type, p_offset, p_vaddr, _pa, p_filesz, _ms, _fl, _al = struct.unpack_from(
"<IIIIIIII", data, e_phoff + i * e_phentsize)
if p_type == 1 and p_vaddr == 0:
seg = (p_offset, p_filesz)
if seg is None:
print("FAIL: 没有 vaddr=0 的 LOAD 段——启动头不会被烧进 flash")
return 1

def word(a):
if a + 4 > seg[1]:
print(f"FAIL: flash 0x{a:X} 超出 LOAD 段(0x{seg[1]:X} 字节)")
sys.exit(1)
return struct.unpack_from("<I", data, seg[0] + a)[0]

w0 = word(0)
# ROM 从 0x0 取指:首字必须是跳转(JAL,或 2 字节 c.j —— 两种都合法;
# 真正钉死布局的是上面的 .bootvec 段地址/长度与下面的魔数偏移)
is_jal = w0 & 0x7F == 0x6F
is_cj = w0 & 0xE003 == 0xA001
if not (is_jal or is_cj):
print(f"FAIL: flash 0x0 不是跳转指令(JAL/c.j),实测 0x{w0:08X}——ROM 取指入口失效")
return 1
for a in (0x04, 0x08):
if word(a) != 0:
print(f"FAIL: 向量表[{a // 4}] 应为 0(官方同款),实测 0x{word(a):08X}")
return 1
for a, what in ((0x0C, "NMI"), (0x10, "HardFault")):
if word(a) == 0:
print(f"FAIL: 向量表[{a // 4}]({what}) 是空槽")
return 1
if word(0x14) != MAGIC:
print(f"FAIL: boot option 应为 0x{MAGIC:08X}(flash 0x14),实测 0x{word(0x14):08X}")
return 1
print(f"PASS: 启动头 @0x0..0x{BOOT_LEN:02X} — 入口跳转 + boot option 0x{MAGIC:08X} @0x14")
return 0


if __name__ == "__main__":
sys.exit(main())
11 changes: 11 additions & 0 deletions ci/gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example usb_cdc \
--features gd32vf103,usb --target riscv32imac-unknown-none-elf --release
cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example ble_gatt \
--features gd32vf103,ble --target riscv32imac-unknown-none-elf --release
# CH583(QingKe V4A)零 PAC 口:QEMU 没有对应的机器可跑(见第 3 步),门禁做
# 构建级 + 启动头产物级终检——WCH ROM 按向量表第 5 字 boot option 0xF3F9BDA9
# 识别有效用户程序,缺了它板上停在 ISP 而构建/门禁全绿(见 check_ch583_boot.py)
cargo build --manifest-path "$ROOT_DIR/Cargo.toml" --example multitask_ch583 \
--features ch583,timer --target riscv32imac-unknown-none-elf --release
CH583_ELF="$ROOT_DIR/target/riscv32imac-unknown-none-elf/release/examples/multitask_ch583"
if [ -n "${PY:-}" ]; then
"$PY" "$ROOT_DIR/ci/check_ch583_boot.py" "$CH583_ELF"
else
echo "python 未安装,跳过 CH583 启动头校验(仅保留构建门禁)"
fi

echo "== [3/3] QEMU 执行门禁(virt 机跑真内核——调度/切换/节拍执行级验证)=="
QEMU_BIN="$(command -v qemu-system-riscv32 || true)"
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;本地核对版已随本仓注释标注文件路径) |

## 芯片手册

Expand All @@ -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) |
Expand Down
Loading
Loading