SG2002 TPU 设备的Rust驱动。
使用 Rust 最小化设备层实现,去除操作系统内核依赖,保留硬件操作逻辑,便于在裸机或自研RustOS上集成运行。
- KernelFns 抽象层:隔离系统相关能力(日志、时间、时钟控制、缓存同步)
- 硬件操作:TDMA/TIU 寄存器访问与控制流程
- DMA 缓冲区执行:支持描述符模式和 PIO 模式
- 挂起/恢复:支持电源管理
- 结构对齐:IOCTL 结构体使用
#[repr(C)]保证 ABI 兼容
src/
├── lib.rs # 库入口,类型导出,TpuError 定义
├── types.rs # 数据类型 (DmaHeader, CpuSyncDesc, TpuConfig, TpuTdmaPioInfo)
├── device.rs # TPU 设备核心实现 (TpuDevice)
├── kernel_fns.rs # 内核函数抽象 trait (KernelFns)
├── platform.rs # 平台相关结构 (TdmaReg, TpuRegBackup, 状态解析)
├── pmu.rs # PMU 性能监控 (TpuPmu, PmuSummary)
├── registers.rs # TDMA/TIU 硬件寄存器常量
└── ioctrl.rs # IOCTL 参数结构定义
本库的 API 设计参照 Linux 驱动模块的生命周期:
| 维度 | Linux 驱动实现 | Rust 库实现 | StarryOS 适配层 |
|---|---|---|---|
| 设备生命周期 | cvi_tpu_probe/open/remove + platform_tpu_* |
TpuDevice::new/open/platform_init/platform_deinit |
在 /dev/cvi-tpu0 设备对象中持有 TpuDevice |
| 提交入口 | CVITPU_SUBMIT_DMABUF -> cvi_tpu_submit() -> worker -> platform_run_dmabuf() |
直接 run_dmabuf() |
ioctl(CVITPU_SUBMIT_DMABUF) 同步调用 run_dmabuf() |
| 等待入口 | CVITPU_WAIT_DMABUF -> wait_event_interruptible(done_list) |
库不管理 OS 等待队列 | wait_dmabuf 在适配层按 seq_no 查询 done_list |
| 缓存同步 | CVITPU_DMABUF_FLUSH/INVLD(_FD) |
由 KernelFns::dma_sync_for_* 抽象 |
StarryOS 用 fence/ion 信息做同步 |
| 描述符解析 | 内核直接按 C 结构体解析 dma_hdr_t + cvi_cpu_sync_desc_t[] |
parse_dmabuf_view() 统一解析与边界检查 |
适配层调用 parse_dmabuf_view(),不再手写解析 |
| TDMA 完成等待 | 中断完成 + completion 等待 | wait_tdma_done() 紧凑轮询 + 超时 |
由库内部处理,适配层仅接收结果 |
| 超时恢复 | platform_tpu_reset() |
reset() |
run_dmabuf 超时后可由上层执行 reset |
| 挂起恢复 | platform_tpu_suspend/resume |
suspend()/resume() |
可由系统电源管理路径调用 |
常用 ioctl 对照(用户态视角):
| ioctl | Linux 语义 | StarryOS 当前语义 |
|---|---|---|
CVITPU_SUBMIT_DMABUF (_IOC(_IOC_WRITE, 0x70, 0x1, 0x8)) |
提交任务入队 | 同步执行一次 run_dmabuf 并记录 seq_no 结果 |
CVITPU_WAIT_DMABUF (`_IOC(_IOC_READ |
_IOC_WRITE, 0x70, 0x6, 0x8)`) | 按 seq_no 阻塞等待完成 |
CVITPU_DMABUF_FLUSH (nr=0x4) |
CPU -> Device cache sync | 调用 dma_sync_for_device |
CVITPU_DMABUF_INVLD (nr=0x5) |
Device -> CPU cache sync | 调用 dma_sync_for_cpu |
基于 YOLOv8 on StarryOS 实际运行于SG2002硬件平台的建议集成流程:
-
系统初始化阶段
- 创建
TpuDevice::new(...) - 调用
probe_setting() - 在设备初始化阶段调用一次
platform_init()
- 创建
-
用户态准备阶段(
/dev/ion+/dev/cvi-tpu0)ION_IOC_ALLOC分配 dmabufmmap映射 ion buffer 到用户态CVITPU_DMABUF_INVLD/CVITPU_DMABUF_FLUSH做缓存同步
-
提交执行阶段(核心热路径)
CVITPU_SUBMIT_DMABUF- 驱动侧通过
parse_dmabuf_view()解析 header/descs - 调用
run_dmabuf(paddr, header, descs)
-
等待完成阶段
CVITPU_WAIT_DMABUF- 使用与 submit 一致的
seq_no获取结果
-
可选收尾
- 继续下一帧提交(推荐)
- 设备长时间空闲时再
platform_deinit()
// 1. 实现 KernelFns trait
struct MyKernelFns {
// 可包含时钟控制器、复位控制器的引用
}
impl KernelFns for MyKernelFns {
fn sleep_ms(&self, ms: u32) {
// 实现毫秒级延时
}
fn now_us(&self) -> TimeStamp {
// 返回微秒级单调时间戳
0
}
fn log(&self, level: LogLevel, msg: &str) {
// 日志输出
}
fn enable_clocks(&self) {
// 使能 clk_tpu_axi, clk_tpu_fab
}
fn disable_clocks(&self) {
// 关闭 TPU 时钟
}
fn reset(&self) {
// 触发 res_tdma, res_tpu, res_tpusys 复位
}
fn dma_sync_for_device(&self, paddr: PhysAddr, size: usize) {
// 刷新 CPU cache 到内存 (DMA_TO_DEVICE)
}
fn dma_sync_for_cpu(&self, paddr: PhysAddr, size: usize) {
// 使 CPU cache 无效化 (DMA_FROM_DEVICE)
}
}
// 2. 设备探测阶段 (probe)
fn tpu_probe() -> Result<TpuDevice<MyKernelFns>, TpuError> {
// 从设备树获取寄存器地址
let tdma_base = NonNull::new(0x0C00_0000 as *mut u8).unwrap();
let tiu_base = NonNull::new(0x0C01_0000 as *mut u8).unwrap();
let config = TpuConfig {
pmu_buf_size: 0, // PMU 缓冲区大小,0 表示禁用
pmu_buf_paddr: 0, // PMU 缓冲区物理地址
};
let kfns = MyKernelFns { /* ... */ };
let mut tpu = TpuDevice::new(tdma_base, tiu_base, config, kfns);
tpu.probe_setting();
Ok(tpu)
}
// 3. 中断处理函数
fn tpu_irq_handler(tpu: &mut TpuDevice<MyKernelFns>) {
let status = tpu.irq_handle();
// 根据 status 处理中断
}
// 4. 执行一次 dmabuf 提交(StarryOS 驱动热路径)
fn submit_one_dmabuf(
tpu: &mut TpuDevice<MyKernelFns>,
dmabuf_vaddr: *const u8,
dmabuf_len: usize,
dmabuf_paddr: PhysAddr,
) -> Result<(), TpuError> {
// dmabuf 结构解析已下沉到 tpu_sg2002
let parsed = unsafe { parse_dmabuf_view(dmabuf_vaddr, dmabuf_len) }?;
// 直接执行,header/descs 为零拷贝借用
let result = tpu.run_dmabuf(dmabuf_paddr, parsed.header, parsed.descs);
if let Err(TpuError::Timeout) = result {
tpu.reset()?;
}
result
}
// 5. 电源管理(可选)
fn tpu_suspend(tpu: &mut TpuDevice<MyKernelFns>) -> Result<(), TpuError> {
tpu.suspend()
}
fn tpu_resume(tpu: &mut TpuDevice<MyKernelFns>) -> Result<(), TpuError> {
tpu.resume()
}
// 6. 用户态 ioctl 的典型顺序(伪代码)
fn user_ioctl_sequence_example() {
// open("/dev/cvi-tpu0")
// open("/dev/ion")
// ION_IOC_ALLOC + mmap
// ioctl(CVITPU_DMABUF_INVLD)
// fill command buffer
// ioctl(CVITPU_DMABUF_FLUSH)
// ioctl(CVITPU_SUBMIT_DMABUF)
// ioctl(CVITPU_WAIT_DMABUF)
}实现此 trait 以适配不同运行环境:
精简评估结论:
now_us是硬性必需(超时与轮询逻辑核心依赖)。enable_clocks/disable_clocks/reset保留为可选钩子,符合不同 SoC 的电源域差异。dma_sync_for_device/dma_sync_for_cpu保留为可选钩子,用于兼容有 cache 维护要求的平台。sleep_ms在当前 SG2002 快路径不是必需;已调整为默认空实现,用于降低接入方实现负担。
pub trait KernelFns {
/// 毫秒级睡眠
fn sleep_ms(&self, _ms: u32) {}
/// 获取微秒级单调时间戳
fn now_us(&self) -> TimeStamp;
/// 日志输出
fn log(&self, level: LogLevel, msg: &str);
/// 使能 TPU 时钟(可选)
fn enable_clocks(&self) {}
/// 关闭 TPU 时钟(可选)
fn disable_clocks(&self) {}
/// 复位 TPU 硬件(可选)
fn reset(&self) {}
// DMA 内存申请相关的函数
/// DMA 缓存同步(可选)
fn dma_sync_for_device(&self, _paddr: PhysAddr, _size: usize) {}
fn dma_sync_for_cpu(&self, _paddr: PhysAddr, _size: usize) {}
}cargo build --target=riscv64gc-unknown-none-elfSG2002 硬件板使用了 XuanTie C906/C910 CPU, 并支持一套丰富的 cache 维护指令。正确使用这些指令对于 DMA 数据一致性和性能至关重要。
- Clean / Writeback:把脏 cache line 回写到下一级存储,不一定无效该行
- Invalidate:无效 cache line,不回写脏数据
- Clean + Invalidate:先回写再无效
- 全量操作:对整级 cache 生效(例如 dcache.call/dcache.ciall/dcache.iall)
- 范围操作:对某段地址按 cache line 执行(例如 dcache.cpa/cipa/ipa)
| 场景 | 推荐操作 | 指令语义 | 本库 API |
|---|---|---|---|
| CPU 写完 DMA 缓冲,设备马上读(DMA_TO_DEVICE) | 按物理地址 clean | dcache.cpa | cache::dcache_clean_range_by_pa(paddr, size) |
| 设备写完 DMA 缓冲,CPU 马上读(DMA_FROM_DEVICE) | 按物理地址 invalidate(或保守用 clean+invalidate) | dcache.ipa / dcache.cipa | cache::dcache_invalidate_range_by_pa(paddr, size) 或 cache::dcache_clean_invalidate_range_by_pa(paddr, size) |
| 双向 DMA 或方向不确定 | 按物理地址 clean+invalidate | dcache.cipa | cache::dcache_clean_invalidate_range_by_pa(paddr, size) |
| 用户态 VA 与内核 VA 映射同一 PA(VA alias) | 避免只做 VA 操作,优先 PA 或全量 | dcache.cpa/cipa 或 dcache.call/ciall | cache::dcache_clean_range_by_pa/cache::dcache_clean_invalidate_range_by_pa 或 cache::dcache_clean_all/cache::dcache_clean_invalidate_all |
| 调试或异常恢复,需强制一次全局整理 | 全量 clean / clean+inv / inv | dcache.call / dcache.ciall / dcache.iall | cache::dcache_clean_all / cache::dcache_clean_invalidate_all / cache::dcache_invalidate_all |
fence:内存访问顺序屏障。它保证前后的读写顺序,不负责回写或无效 cache line。fence iorw, iorw:更强顺序约束,常用于设备寄存器访问与 DMA 同步边界。fence.i:指令流同步,保证后续取指看到新写入的代码;它不等价于 dcache clean/invalidate。sync.is(Xuantie C910/C906 扩展):用于 cache 维护后的同步收敛,常与dcache.cpa/cipa/ipa配合使用。sfence.vma:TLB 刷新相关,不是 cache 维护指令;通常在页表修改后使用。
实践建议:
- 先做 cache 维护(
cpa/cipa/ipa或全量call/ciall/iall),再做sync.is/fence。 - 不要把
fence当作 cache flush 的替代;fence只管顺序,不管脏行回写。
- 原始 C 驱动的Linux 内核版本:5.10.4
- HAL 实现:
hal/mars(Mars 平台) - 设备树兼容字符串:
cvitek,tpu
| 资源 | 描述 |
|---|---|
| TDMA 寄存器 | 设备树 tdma 资源 |
| TIU 寄存器 | 设备树 tiu 资源 |
| 中断 | 平台中断 1 (TDMA) |
| 时钟 | clk_tpu_axi, clk_tpu_fab |
| 复位 | res_tdma, res_tpu, res_tpusys |
- 需要实现
KernelFnstrait 以提供平台相关功能 run_dmabuf实现了hal/mars的核心提交流程- 该库不包含 Linux 设备模型与中断注册逻辑,由 RustOS 操作系统驱动框架提供
- 不支持 TEE (安全执行环境)