From c1c01ff7eec1b369d95f5a7154820853ea9a43bd Mon Sep 17 00:00:00 2001 From: shuoshuo Date: Wed, 27 May 2026 20:42:58 +0800 Subject: [PATCH 1/2] feat(starryos): add K230 KPU QEMU support --- Cargo.lock | 29 + Cargo.toml | 2 + docs/k230-kpu-qemu-adaptation.md | 804 ++++++++++++++++++ drivers/npu/k230-kpu/Cargo.toml | 15 + drivers/npu/k230-kpu/src/lib.rs | 161 ++++ os/StarryOS/configs/board/k230-canmv.dtb | Bin 0 -> 7002 bytes os/StarryOS/configs/board/k230-canmv.dts | 372 ++++++++ os/StarryOS/configs/board/k230-canmv.toml | 7 + os/StarryOS/configs/qemu/qemu-k230.toml | 17 + os/StarryOS/kernel/Cargo.toml | 2 + os/StarryOS/kernel/src/pseudofs/dev/kpu.rs | 176 ++++ os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 25 + os/StarryOS/starryos/Cargo.toml | 4 + os/arceos/modules/axhal/Cargo.toml | 7 + os/arceos/modules/axhal/build.rs | 5 + os/arceos/modules/axhal/src/lib.rs | 1 + platforms/ax-plat-riscv64-k230/CHANGELOG.md | 5 + platforms/ax-plat-riscv64-k230/Cargo.toml | 47 + platforms/ax-plat-riscv64-k230/README.md | 8 + platforms/ax-plat-riscv64-k230/README_CN.md | 7 + platforms/ax-plat-riscv64-k230/axconfig.toml | 79 ++ platforms/ax-plat-riscv64-k230/build.rs | 6 + platforms/ax-plat-riscv64-k230/src/boot.rs | 112 +++ platforms/ax-plat-riscv64-k230/src/console.rs | 67 ++ platforms/ax-plat-riscv64-k230/src/drivers.rs | 435 ++++++++++ platforms/ax-plat-riscv64-k230/src/init.rs | 40 + platforms/ax-plat-riscv64-k230/src/irq.rs | 292 +++++++ platforms/ax-plat-riscv64-k230/src/lib.rs | 39 + platforms/ax-plat-riscv64-k230/src/mem.rs | 58 ++ platforms/ax-plat-riscv64-k230/src/power.rs | 37 + platforms/ax-plat-riscv64-k230/src/time.rs | 52 ++ scripts/axbuild/src/build.rs | 1 + scripts/axbuild/src/rootfs/qemu.rs | 69 +- scripts/axbuild/src/starry/build.rs | 6 +- 34 files changed, 2985 insertions(+), 2 deletions(-) create mode 100644 docs/k230-kpu-qemu-adaptation.md create mode 100644 drivers/npu/k230-kpu/Cargo.toml create mode 100644 drivers/npu/k230-kpu/src/lib.rs create mode 100644 os/StarryOS/configs/board/k230-canmv.dtb create mode 100644 os/StarryOS/configs/board/k230-canmv.dts create mode 100644 os/StarryOS/configs/board/k230-canmv.toml create mode 100644 os/StarryOS/configs/qemu/qemu-k230.toml create mode 100644 os/StarryOS/kernel/src/pseudofs/dev/kpu.rs create mode 100644 platforms/ax-plat-riscv64-k230/CHANGELOG.md create mode 100644 platforms/ax-plat-riscv64-k230/Cargo.toml create mode 100644 platforms/ax-plat-riscv64-k230/README.md create mode 100644 platforms/ax-plat-riscv64-k230/README_CN.md create mode 100644 platforms/ax-plat-riscv64-k230/axconfig.toml create mode 100644 platforms/ax-plat-riscv64-k230/build.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/boot.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/console.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/drivers.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/init.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/irq.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/lib.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/mem.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/power.rs create mode 100644 platforms/ax-plat-riscv64-k230/src/time.rs diff --git a/Cargo.lock b/Cargo.lock index 269c2698fa..1dce8b7e78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,6 +1034,7 @@ dependencies = [ "ax-plat-aarch64-qemu-virt", "ax-plat-aarch64-raspi", "ax-plat-loongarch64-qemu-virt", + "ax-plat-riscv64-k230", "ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-sg2002", "ax-plat-riscv64-visionfive2", @@ -1424,6 +1425,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ax-plat-riscv64-k230" +version = "0.5.9" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-kspin", + "ax-lazyinit", + "ax-plat", + "ax-riscv-plic", + "axklib", + "log", + "mmio-api", + "rd-block", + "riscv 0.16.0", + "sbi-rt 0.0.3", + "sdhci-host", + "sdmmc-protocol", + "some-serial", + "spin 0.12.0", +] + [[package]] name = "ax-plat-riscv64-qemu-virt" version = "0.5.9" @@ -4889,6 +4913,10 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k230-kpu" +version = "0.1.0" + [[package]] name = "kasm-aarch64" version = "0.2.1" @@ -7922,6 +7950,7 @@ dependencies = [ "hashbrown 0.16.1", "indoc", "inherit-methods-macro", + "k230-kpu", "kernel-elf-parser", "kprobe", "ksym", diff --git a/Cargo.toml b/Cargo.toml index f1467fc54f..24c31715c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -222,6 +222,7 @@ ax-plat-aarch64-raspi = { version = "0.5.11", path = "platforms/ax-plat-aarch64- ax-plat-loongarch64-qemu-virt = { version = "0.5.10", path = "platforms/ax-plat-loongarch64-qemu-virt", default-features = false } ax-plat-macros = { version = "0.3.6", path = "platforms/ax-plat-macros" } ax-plat-riscv64-qemu-virt = { version = "0.5.9", path = "platforms/ax-plat-riscv64-qemu-virt" } +ax-plat-riscv64-k230 = { version = "0.5.9", path = "platforms/ax-plat-riscv64-k230" } ax-plat-riscv64-sg2002 = { version = "0.3.6", path = "platforms/ax-plat-riscv64-sg2002" } ax-plat-x86-pc = { version = "0.5.10", path = "platforms/ax-plat-x86-pc" } ax-posix-api = { version = "0.5.16", path = "os/arceos/api/arceos_posix_api" } @@ -291,6 +292,7 @@ rdif-timer = { version = "0.6.1", path = "drivers/interface/rdif-timer" } rdif-vsock = { version = "0.1.0", path = "drivers/interface/rdif-vsock" } rk3588-pci = { version = "0.2.0", path = "drivers/pci/rk3588-pci" } rockchip-npu = { version = "0.2.1", path = "drivers/npu/rockchip-npu" } +k230-kpu = { version = "0.1.0", path = "drivers/npu/k230-kpu" } realtek-rtl8125 = { version = "0.2.0", path = "drivers/net/realtek-rtl8125" } rockchip-pm = { version = "0.4.3", path = "drivers/soc/rockchip/rockchip-pm" } rockchip-soc = { version = "0.3.0", path = "drivers/soc/rockchip/rockchip-soc" } diff --git a/docs/k230-kpu-qemu-adaptation.md b/docs/k230-kpu-qemu-adaptation.md new file mode 100644 index 0000000000..eb0cfcc5cc --- /dev/null +++ b/docs/k230-kpu-qemu-adaptation.md @@ -0,0 +1,804 @@ +# StarryOS K230 KPU/NPU QEMU 适配阶段一报告 + +最后更新:2026-05-27 + +## 摘要 + +本报告记录 StarryOS 基于 QEMU K230 平台完成 KPU/NPU 第一阶段适配的背景、设计依据、实现路径、调试过程和验证结果。K230 芯片中的 NPU 设备在官方命名中称为 KPU,因此本文统一使用“KPU”指代该 NPU 设备。 + +阶段一的目标不是完成完整 AI 推理应用栈,而是先建立可在 QEMU K230 上启动 StarryOS、识别 SD rootfs、暴露 KPU 设备节点,并提供用户态访问 KPU 模型所需的最小内核接口。当前结果已经达到以下状态: + +- StarryOS 能通过 QEMU K230 direct boot 启动。 +- K230 SD1/SDHCI rootfs 路径可用,系统能从 SD raw ext4 镜像挂载根文件系统。 +- StarryOS 能进入用户态 shell。 +- `/dev/kpu` 与 `/dev/kpu0` 已创建,用户态可读取 KPU CFG 寄存器。 +- 内核提供 KPU 命令流编程、启动、清 done、查询状态、等待完成和 mmap 的基础 ABI。 +- K230 平台、设备树、QEMU 参数和 axbuild rootfs patch 均已纳入仓库配置。 +- 关键构建、单元测试、clippy 和 QEMU smoke 均已完成验证。 + +## 背景与输入资料 + +本任务围绕以下两个外部参考仓库展开: + +- `https://github.com/zevorn/qemu/tree/chao-k230-dev`:QEMU K230 平台与 KPU 模型来源。用户已将该仓库 clone 到 `/Users/joshua/tmp/qemu`。 +- `https://github.com/zevorn/kunos`:已能在 QEMU K230 上启动的发行版参考,提供 K230 QEMU 启动参数、设备树和 rootfs 接入方式。 + +StarryOS 适配工作目录: + +```sh +/Users/joshua/tmp/tgoskits/target/worktrees/tgoskits-k230-kpu +``` + +开发分支: + +```sh +codex/k230-kpu-starry +``` + +目标 PR 基线: + +```sh +upstream/dev +``` + +默认实验环境为 Docker/Linux,使用项目已有镜像: + +```sh +starryos-dev:ubuntu-qemu10.2.1 +``` + +本阶段记录对应的版本基线: + +| 项目 | 分支或版本 | commit/说明 | +| --- | --- | --- | +| StarryOS 基线 | `upstream/dev` | `567de5162` | +| StarryOS 开发分支 | `codex/k230-kpu-starry` | 基于 `upstream/dev` 创建,用于提交阶段一适配 | +| QEMU K230 模型 | `chao-k230-dev` | `/Users/joshua/tmp/qemu`,commit `539bd41` | +| Docker 镜像 | `starryos-dev:ubuntu-qemu10.2.1` | StarryOS 构建、QEMU 构建和 smoke 验证环境 | +| rootfs 镜像 | `rootfs-riscv64-alpine.img` | raw ext4 filesystem,不带分区表 | + +## 阶段一目标与边界 + +### 已完成目标 + +阶段一重点解决“StarryOS 能不能在 QEMU K230 上启动,并把 QEMU KPU 模型以稳定内核接口暴露给用户态”的问题。具体目标如下: + +1. 增加 K230 平台支持,使 StarryOS 可选择 `riscv64-k230` 平台构建。 +2. 对齐 QEMU K230 direct boot 的内存布局、UART、timer、PLIC、CLINT、SDHCI 和 KPU 地址。 +3. 接入 QEMU `-drive if=sd,...` 对应的 SD1 rootfs,使 StarryOS 能挂载根文件系统。 +4. 新增可复用的 no_std KPU 驱动 crate,封装 KPU MMIO 寄存器与命令流编程逻辑。 +5. 在 StarryOS `/dev` 下提供 `/dev/kpu` 和 `/dev/kpu0`,支持 read/write/ioctl/mmap。 +6. 增加 board/qemu 配置,使 K230 构建和手动 QEMU smoke 可复现。 +7. 记录适配过程中的关键问题、根因、修复方式和验证命令。 + +### 暂未纳入阶段一的内容 + +以下内容属于后续阶段工作,不作为本阶段完成标准: + +- 使用真实 KPU command stream 完成端到端模型推理。 +- 将 KPU IRQ completion 接入 StarryOS 用户态事件通知。本阶段已记录 KPU IRQ,并提供轮询等待路径。 +- 固化面向应用的高级 KPU runtime 或 SDK API。本阶段只提供低层设备 ABI。 +- 在物理 K230 板卡上验证。本阶段目标为 QEMU K230。 +- 验证多核启动。本阶段 QEMU 参数与平台配置均采用 `-smp 1`。 + +## QEMU K230 与 KPU 事实整理 + +### QEMU 启动模型 + +QEMU K230 使用机器型号: + +```sh +-machine k230 +``` + +当前验证路径采用 direct boot,核心参数包括: + +- `-smp 1` +- `-m 2G` +- `-nographic` +- `-dtb os/StarryOS/configs/board/k230-canmv.dtb` +- `-drive if=sd,format=raw,file=...` + +StarryOS 构建配置中设置 `to_bin = true`,使 `cargo xtask starry build` 生成可被 QEMU direct boot 加载的 `starryos.bin`。运行 QEMU 时使用 `-kernel target/riscv64gc-unknown-none-elf/release/starryos.bin` 加载内核,并用 `-L /mnt/tmp/qemu/pc-bios` 指向 QEMU 仓库中的 firmware/BIOS 资源目录。 + +direct boot 相关地址: + +| 项目 | 地址或值 | 说明 | +| --- | --- | --- | +| OpenSBI 加载地址 | `0x0800_0000` | QEMU K230 固定布局 | +| kernel 加载地址 | `0x0820_0000` | StarryOS kernel direct boot 起点 | +| DTB 加载地址 | `0x0a00_0000` | QEMU K230 direct boot 传入设备树 | +| timebase | `27_000_000` | 设备树和平台配置保持一致 | +| UART0 | `0x9140_0000`, IRQ `16` | 控制台使用 | +| SD0 | `0x9158_0000`, IRQ `142` | 当前未作为 rootfs 使用 | +| SD1 | `0x9158_1000`, IRQ `144` | QEMU `-drive if=sd` 对应路径 | +| PLIC | `0xf000_00000`, size `0x0040_0000` | 外部中断控制器 | +| CLINT | `0xf040_00000`, size `0x0040_0000` | timer/IPI | + +### KPU 模型 + +QEMU `chao-k230-dev` 分支中 KPU 模型的关键信息: + +| 项目 | 值 | 说明 | +| --- | --- | --- | +| KPU L2 base | `0x8000_0000` | KPU L2/cache 窗口 | +| KPU L2 size | `0x0020_0000` | 2 MiB | +| KPU CFG base | `0x8040_0000` | KPU 控制寄存器窗口 | +| KPU CFG size | `0x800` | 2 KiB | +| KPU IRQ | `189` | QEMU 模型完成后触发 | +| `COMMAND_START` | `0x100` | 命令流起始物理地址低 32 位 | +| `COMMAND_END` | `0x104` | 命令流结束物理地址低 32 位 | +| `COMMAND_HI` | `0x108` | 命令流物理地址高 32 位 | +| `CONTROL` | `0x128` | 控制寄存器 | +| `STATUS_LO` | `0x130` | 状态低 32 位 | +| `STATUS_HI` | `0x134` | 状态高 32 位 | +| clear done | `0x4` 写入 `CONTROL` | 清完成状态 | +| start | `0x9` 写入 `CONTROL` | 启动命令流执行 | +| done status | `0x0000_0004_0000_0004` | QEMU 模型完成后的 done mask | + +QEMU 模型的执行路径是:guest 将命令流物理地址写入 `COMMAND_START`、`COMMAND_END` 和 `COMMAND_HI`,随后向 `CONTROL` 写入 start 值。QEMU 读取该物理地址范围内的命令流,执行 GNNE 模型逻辑,完成后设置 done 状态并触发 KPU IRQ。 + +## 总体设计 + +本次适配按“可复用驱动 crate、平台层、StarryOS 设备节点、构建配置”四层拆分,避免把 QEMU/K230 专用逻辑散落在 StarryOS 通用路径中。 + +```mermaid +flowchart TD + A["QEMU K230 machine"] --> B["K230 MMIO map"] + B --> C["ax-plat-riscv64-k230"] + C --> D["ax-hal platform selection"] + D --> E["StarryOS kernel"] + E --> F["/dev/kpu and /dev/kpu0"] + F --> G["user program"] + E --> H["k230-kpu no_std crate"] + H --> B + A --> I["SD1 from -drive if=sd"] + I --> J["K230 SDHCI1 block device"] + J --> K["ax-fs-ng rootfs mount"] +``` + +设计原则: + +- KPU 寄存器常量和命令流编程逻辑放入 `drivers/npu/k230-kpu`,便于未来被其他 OS glue 或测试复用。 +- K230 地址空间、启动页表、UART、PLIC、CLINT 和 SDHCI1 block 设备放入 `platforms/ax-plat-riscv64-k230`。 +- StarryOS 只负责将 KPU 作为 `/dev` 字符设备暴露给用户态,并完成 user pointer copy、VFS error 映射和 mmap 策略。 +- axbuild 只处理 QEMU 参数 patch,不把 K230 rootfs 错误地转换成 VirtIO 设备。 + +## 代码改动清单 + +| 路径 | 类型 | 作用 | +| --- | --- | --- | +| `drivers/npu/k230-kpu/` | 新增 crate | KPU no_std 驱动核心,定义寄存器、ioctl、mmap offset 和命令流编程接口 | +| `platforms/ax-plat-riscv64-k230/` | 新增平台 crate | K230 平台配置、启动页表、UART、timer、PLIC、SDHCI1 block 注册 | +| `os/StarryOS/kernel/src/pseudofs/dev/kpu.rs` | 新增设备节点实现 | `/dev/kpu` 与 `/dev/kpu0` 的 read/write/ioctl/mmap | +| `os/StarryOS/kernel/src/pseudofs/dev/mod.rs` | 修改 | 在启用 `k230-kpu` feature 时创建 KPU 设备节点 | +| `os/StarryOS/kernel/Cargo.toml` | 修改 | 增加 `k230-kpu` feature 和依赖 | +| `os/StarryOS/starryos/Cargo.toml` | 修改 | 增加 `k230` feature,联动 `ax-hal/riscv64-k230` 与 `starry-kernel/k230-kpu` | +| `os/arceos/modules/axhal/Cargo.toml` | 修改 | 接入 `ax-plat-riscv64-k230` optional dependency 和 feature | +| `os/arceos/modules/axhal/build.rs` | 修改 | 增加 `riscv64-k230 -> ax_plat_riscv64_k230` 平台选择 | +| `os/arceos/modules/axhal/src/lib.rs` | 修改 | 更新平台支持说明 | +| `scripts/axbuild/src/build.rs` | 修改 | 识别 `riscv64-k230` 平台 feature | +| `scripts/axbuild/src/starry/build.rs` | 修改 | Starry build 中将 `k230` 转换为 K230 平台 feature | +| `scripts/axbuild/src/rootfs/qemu.rs` | 修改 | 保留并 patch `-drive if=sd,...`,避免注入 VirtIO rootfs | +| `os/StarryOS/configs/board/k230-canmv.*` | 新增 | K230 board 配置和 DTB | +| `os/StarryOS/configs/qemu/qemu-k230.toml` | 新增 | QEMU K230 参数 | +| `Cargo.toml`, `Cargo.lock` | 修改 | 纳入新增 crate | + +## KPU 驱动 crate 设计 + +新增 crate: + +```text +drivers/npu/k230-kpu +``` + +该 crate 为 `#![no_std]`,只封装与 KPU 模型直接相关的硬件语义,不依赖 StarryOS VFS 或用户态 ABI。核心类型和常量包括: + +- `KPU_CFG_PADDR = 0x8040_0000` +- `KPU_CFG_SIZE = 0x800` +- `KPU_L2_PADDR = 0x8000_0000` +- `KPU_L2_SIZE = 0x20_0000` +- `KPU_IRQ = 189` +- `CommandRange { start_paddr: u64, end_paddr: u64 }` +- `Kpu { base_vaddr }` + +`CommandRange` 使用物理地址表示命令流范围。驱动会把命令流地址拆成 KPU 模型需要的三个 32 位寄存器字段: + +```text +COMMAND_START = start_paddr[31:0] +COMMAND_END = end_paddr[31:0] +COMMAND_HI = start_paddr[63:32] +``` + +当前校验规则: + +- `start_paddr < end_paddr`,空范围视为无效。 +- `start_paddr` 与 `end_paddr` 必须位于同一个 4 GiB window 内,否则无法用一个 `COMMAND_HI` 表达。 + +主要方法: + +| 方法 | 作用 | +| --- | --- | +| `program_command(range)` | 写入命令流起止地址 | +| `run_command(range)` | clear done、program command、start 三步合并 | +| `clear_done()` | 向 `CONTROL` 写入 `0x4` | +| `start()` | 向 `CONTROL` 写入 `0x9` | +| `status()` | 读取 `STATUS_HI:STATUS_LO` 组成 64 位状态 | +| `is_done()` | 使用 `DONE_STATUS` mask 判断完成 | +| `wait_done(poll_limit)` | 轮询 done 状态,超限返回 timeout | +| `read_reg(offset)` | 32 位 volatile MMIO read | +| `write_reg(offset, value)` | 32 位 volatile MMIO write | + +该 crate 自带单元测试,覆盖命令流地址拆分、空范围拒绝和跨 4 GiB window 拒绝。 + +## StarryOS `/dev/kpu` ABI + +StarryOS 在启用 `k230-kpu` feature 时创建两个设备节点: + +```text +/dev/kpu +/dev/kpu0 +``` + +两个节点指向同一个底层 KPU 设备,设备号为: + +```text +major = 240 +minor = 1 +``` + +节点 flags: + +```text +NodeFlags::NON_CACHEABLE +``` + +### read/write 语义 + +`read_at` 和 `write_at` 用于低层调试 KPU CFG 寄存器: + +- 访问单位为 32 bit。 +- offset 必须 4 字节对齐。 +- offset 必须位于 KPU CFG window 内。 +- read 返回 native endian 的 4 字节寄存器值。 +- write 使用输入 buffer 前 4 字节写寄存器。 + +该接口主要用于 smoke 和调试,例如: + +```sh +od -An -tx4 -N4 /dev/kpu +``` + +### ioctl 语义 + +当前 ioctl 编号在 `k230-kpu` crate 中集中定义: + +| ioctl | 值 | arg | 语义 | +| --- | --- | --- | --- | +| `KPU_IOC_GET_STATUS` | `0x4b00` | `u64 *status` | 复制 64 位状态到用户态 | +| `KPU_IOC_CLEAR` | `0x4b01` | ignored | 清 done 状态 | +| `KPU_IOC_PROGRAM_COMMAND` | `0x4b02` | `CommandRange *range` | 编程命令流地址 | +| `KPU_IOC_START` | `0x4b03` | ignored | 启动已编程命令流 | +| `KPU_IOC_RUN` | `0x4b04` | `CommandRange *range` | clear、program、start 合并操作 | +| `KPU_IOC_WAIT_DONE` | `0x4b05` | `usize poll_limit` | 轮询等待 done;arg 为 0 时使用默认 `1_000_000` | + +用户态传入的 `CommandRange` C ABI 布局: + +```c +struct CommandRange { + uint64_t start_paddr; + uint64_t end_paddr; +}; +``` + +StarryOS 设备层使用 `user_copy` 在内核态和用户态之间复制 ioctl 参数。空指针或 copy 失败会转换为 VFS error。 + +### mmap 语义 + +`/dev/kpu` 支持将 KPU CFG 和 KPU L2 映射给用户态: + +| mmap offset | 等价物理地址 offset | 映射目标 | 最大长度 | +| --- | --- | --- | --- | +| `0` | `0x8040_0000` | KPU CFG | `0x800` | +| `0x1000` | `0x8000_0000` | KPU L2 | `0x20_0000` | + +设计上同时接受短 offset 和物理地址 offset,是为了兼容两类用户态写法: + +- 以设备内部 offset 表达资源,例如 offset `0x1000` 表示 KPU L2。 +- 以物理地址表达资源,例如 offset `0x8000_0000` 表示 KPU L2。 + +## K230 平台 crate 设计 + +新增平台 crate: + +```text +platforms/ax-plat-riscv64-k230 +``` + +该平台基于现有 RISC-V QEMU virt 平台结构调整,但移除了 K230 不使用的 VirtIO/PCI 默认路径,改为静态注册 K230 外设。 + +### 地址空间配置 + +核心配置位于 `platforms/ax-plat-riscv64-k230/axconfig.toml`: + +| 项目 | 值 | +| --- | --- | +| `arch` | `riscv64` | +| `platform` | `riscv64-k230` | +| `package` | `ax-plat-riscv64-k230` | +| `max-cpu-num` | `1` | +| `phys-memory-base` | `0x0820_0000` | +| `phys-memory-size` | `0x77e0_0000` | +| `kernel-base-paddr` | `0x0820_0000` | +| `kernel-base-vaddr` | `0xffff_ffc0_0820_0000` | +| `phys-virt-offset` | `0xffff_ffc0_0000_0000` | +| `timer-frequency` | `27_000_000` | +| `uart-paddr` | `0x9140_0000` | +| `uart-irq` | `16` | +| `uart-clock` | `50_000_000` | +| `sd1-paddr` | `0x9158_1000` | +| `sd1-irq` | `144` | +| `kpu-cfg-paddr` | `0x8040_0000` | +| `kpu-l2-paddr` | `0x8000_0000` | +| `kpu-irq` | `189` | + +### MMIO 范围 + +平台声明的 MMIO ranges 包含: + +- KPU L2:`[0x8000_0000, 0x0020_0000]` +- SRAM:`[0x8020_0000, 0x0020_0000]` +- KPU CFG + FFT + AI2D:`[0x8040_0000, 0x0000_2000]` +- RTC:`[0x9100_0c00, 0x0000_0400]` +- STC:`[0x9110_8000, 0x0000_0800]` +- UART0:`[0x9140_0000, 0x0000_1000]` +- SD0:`[0x9158_0000, 0x0000_1000]` +- SD1:`[0x9158_1000, 0x0000_1000]` +- PLIC:`[0xf000_00000, 0x0040_0000]` +- CLINT:`[0xf040_00000, 0x0040_0000]` + +这里将 KPU CFG、FFT、AI2D 合并成一段 `0x2000` 范围,是为了解决页粒度映射时的重复映射问题,详见“问题与修复记录”。 + +### 启动页表 + +K230 direct boot 的 kernel base 为 `0x0820_0000`。平台启动页表需要覆盖: + +- kernel 物理加载区。 +- high-half kernel 映射。 +- KPU/L2/外设所需的 direct map 区间。 +- PLIC/CLINT 所在的高地址 MMIO 区间。 + +这保证进入 Rust 代码后,`phys_to_virt` 与早期 MMIO 访问都能使用一致地址规则。 + +### UART + +K230 UART0 使用 DW APB UART 路径,初始化参数: + +- baud rate:`115200` +- UART clock:`50_000_000` +- MMIO base:`0x9140_0000` + +设备树中 `stdout-path` 也指向 `serial0:115200n8`,与 StarryOS 控制台保持一致。 + +## SDHCI1 与 rootfs 路径 + +QEMU K230 的 rootfs 接入方式与常规 QEMU virt 不同。常规 virt 平台常用 VirtIO block;K230 参考启动路径使用: + +```sh +-drive if=sd,format=raw,file=... +``` + +该参数会把镜像挂到 K230 SD1,而不是 VirtIO block。为此阶段一做了两部分适配。 + +### 平台侧 SDHCI1 block 注册 + +`ax-plat-riscv64-k230` 新增静态 SDHCI1 probe: + +- probe level:`PostKernel` +- 设备名:`k230-sdhci1` +- MMIO:`0x9158_1000` +- size:`0x1000` +- IRQ:`144` + +初始化流程: + +1. 使用 `axklib::mmio::ioremap_raw` 映射 SD1 MMIO。 +2. 创建 `sdhci_host::Sdhci`。 +3. 执行 `reset_all()`。 +4. 设置 3.3V power。 +5. 开启 controller interrupts。 +6. 使用 `sdmmc_protocol::SdioSdmmc` 初始化卡。 +7. 初始化策略设置为 `CardInitPreference::SdFirst`。 +8. 关闭 UHS 和高速模式选择,优先保证 QEMU SD card 基础路径稳定。 +9. 使用 `rd-block` 封装成 StarryOS 可注册 block device。 + +当前 block queue 使用 FIFO 传输模式: + +```text +BlockTransferMode::Fifo +``` + +这样可以避免第一阶段引入额外 DMA/IOMMU 变量,先确保 rootfs 可读写路径稳定。 + +### axbuild QEMU rootfs patch + +`scripts/axbuild/src/rootfs/qemu.rs` 原本在 `EnsureDiskBootNet` 模式下会补充标准 VirtIO disk/net 参数。该逻辑对 QEMU virt 合理,但对 K230 会破坏预期路径,因为 K230 已经显式配置了 `-drive if=sd,...`。 + +阶段一调整为: + +- 如果 QEMU args 中已存在 `-drive` 且参数包含 `if=sd`,只替换其中的 `file=`。 +- 不额外注入 VirtIO block device。 +- 不额外注入 VirtIO net device。 +- 如果不存在 SD drive,则保留原有 VirtIO fallback 逻辑。 + +新增测试覆盖: + +```sh +cargo test -p axbuild ensure_disk_boot_net_patches_sd_drive_without_adding_virtio +``` + +## Board 与 QEMU 配置 + +### Board 配置 + +新增: + +```text +os/StarryOS/configs/board/k230-canmv.toml +``` + +内容要点: + +```toml +features = [ + "k230", +] +log = "Info" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" +``` + +`k230` feature 会联动: + +- `ax-hal/riscv64-k230` +- `starry-kernel/k230-kpu` + +### 设备树 + +新增: + +```text +os/StarryOS/configs/board/k230-canmv.dts +os/StarryOS/configs/board/k230-canmv.dtb +``` + +DTS 基于 Kunos 的 K230 CANMV 设备树整理,并补充: + +- `timebase-frequency = <27000000>` +- memory 起点与 direct boot kernel 地址对齐。 +- UART0 stdout。 +- SD1 `compatible = "snps,dwcmshc-sdhci"`。 +- KPU node: + +```dts +kpu: kpu@80400000 { + compatible = "canaan,k230-kpu"; + reg = <0x0 0x80400000 0x0 0x800>, + <0x0 0x80000000 0x0 0x200000>; + interrupts = <189>; +}; +``` + +### QEMU 配置 + +新增: + +```text +os/StarryOS/configs/qemu/qemu-k230.toml +``` + +关键参数: + +```toml +args = [ + "-machine", "k230", + "-smp", "1", + "-m", "2G", + "-nographic", + "-dtb", "${workspace}/os/StarryOS/configs/board/k230-canmv.dtb", + "-drive", "if=sd,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", +] +uefi = false +to_bin = true +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] +``` + +`to_bin = true` 用于生成 QEMU direct boot 可加载的 `starryos.bin`。 + +## 问题与修复记录 + +### QEMU 源码获取 + +最初尝试从远端直接 clone 或下载 `zevorn/qemu` 的 `chao-k230-dev` 分支时,受网络和沙箱限制影响,出现过 HTTP/2 cancel、early EOF、archive 下载中断等现象。后续用户已将仓库 clone 到: + +```sh +/Users/joshua/tmp/qemu +``` + +因此后续 QEMU 构建和 smoke 验证均使用该本地路径。 + +### Docker QEMU 构建依赖 + +项目镜像可用于 StarryOS 构建与 QEMU K230 验证: + +```sh +starryos-dev:ubuntu-qemu10.2.1 +``` + +构建 QEMU K230 时需要额外安装: + +```sh +apt-get update +apt-get install -y libfdt-dev device-tree-compiler +``` + +运行已构建的 QEMU K230 binary 时需要 `libfdt1`。如果在同一容器内安装 `libfdt-dev`,运行时依赖通常会一并满足;如果把 QEMU binary 复制到其他运行环境,需要确认 `libfdt1` 是否存在。 + +### KPU/FFT/AI2D MMIO overlap + +早期 Starry K230 smoke 进入内核后,在 `axmm` 初始化阶段 panic: + +```text +failed to initialize kernel address space: AxErrorKind::AlreadyExists +``` + +根因是 KPU CFG、FFT、AI2D 三段 MMIO 在 QEMU 地址图中相邻,且每段小于 4 KiB。平台配置按子页粒度分别声明后,页表映射时会按 4 KiB 对齐,导致同一页或相邻页被重复映射。 + +修复方式: + +```text +[0x8040_0000, 0x0000_2000] +``` + +将 KPU CFG、FFT、AI2D 合并为一段 MMIO range 后,`axmm` 初始化通过。 + +### rootfs block device 缺失 + +修复 MMIO overlap 后,系统继续进入平台设备和文件系统初始化,但出现: + +```text +failed to determine root device from available block devices +``` + +panic 位置: + +```text +os/arceos/modules/axfs-ng/src/lib.rs:39:13 +``` + +根因是 K230 平台当时还没有把 QEMU K230 的 SDHCI1 注册成 StarryOS block device。QEMU 参数和 axbuild rootfs patch 已经把 rootfs 指向 `-drive if=sd,...`,但内核侧没有 block 设备可供 `ax-fs-ng` 挂载。 + +修复方式: + +- 在 `ax-plat-riscv64-k230` 中增加静态 SDHCI1 注册。 +- 使用 `sdhci-host` + `sdmmc-protocol` 初始化 SD card。 +- 使用 `rd-block` 适配 StarryOS block device interface。 +- 保持 rootfs QEMU 参数为 `if=sd`,不切换到 VirtIO。 + +修复后 QEMU smoke 日志确认: + +```text +k230-sdhci: probe SD1 at [PA:0x91581000, SZ:0x1000) +k230-sdhci: card kind=... capacity_blocks=Some(2097152) +k230-sdhci: block device registered irq=144 +``` + +随后 `ax-fs-ng` 能识别并挂载 ext4 rootfs。 + +### raw ext4 镜像识别 + +当前测试使用的 rootfs: + +```sh +/Users/joshua/tmp/tgoskits/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img +``` + +该镜像是 raw ext4,而不是带分区表的整盘镜像。因此 `ax-fs-ng` 会输出类似: + +```text +no usable partition table; treating the whole disk as a candidate +``` + +这是镜像形态导致的预期结果,不是 SDHCI 注册失败,也不是 rootfs mount 异常。 + +当前 QEMU smoke 的 `-append` 中仍保留 `root=/dev/mmcblk0p2 rootwait rw`,用于对齐 Kunos/Linux 风格启动参数。但 StarryOS 当前 rootfs 识别实际依赖 `ax-fs-ng` 对 block device 的探测:由于测试镜像是 whole-disk raw ext4,最终会按整盘 ext4 candidate 挂载,而不是按 `p2` 分区挂载。后续若切换为带分区表镜像,需要重新确认 `root=`、SD1 设备命名和分区选择策略。 + +### clippy 目标选择 + +默认实验环境为 Docker/Linux。通用命令: + +```sh +cargo xtask clippy --package starry-kernel +``` + +不会读取 `k230-canmv.toml`,也不会自动切换到 `riscv64gc-unknown-none-elf`。从 `scripts/axbuild/src/clippy.rs` 的展开逻辑看,如果 crate 没有 docs.rs target 元数据,它会生成不带 `--target` 的 `cargo clippy -p starry-kernel` 以及 feature 矩阵检查。因此在 Docker 中该命令对应 Linux host target 检查,不等价于 K230 board/target 检查。 + +为覆盖本次真实启动路径,阶段一按 `cargo xtask starry build` 打印出的 K230 target 参数执行 clippy,目标是: + +```text +target = scripts/targets/no-pie/riscv64gc-unknown-none-elf.json +features = ax-hal/riscv64-k230,k230 +binary = starryos +build-std = core,alloc +``` + +该 K230 target clippy 已通过。 + +## 验证记录 + +### 验证矩阵 + +| 验证项 | 命令或方式 | 结果 | +| --- | --- | --- | +| 格式化 | `cargo fmt` | 通过 | +| KPU crate 单元测试 | `cargo test -p k230-kpu` | 通过 | +| KPU crate clippy | `cargo xtask clippy --package k230-kpu` | 通过 | +| K230 platform clippy | `cargo xtask clippy --package ax-plat-riscv64-k230` | 通过 | +| axbuild SD drive patch 测试 | `cargo test -p axbuild ensure_disk_boot_net_patches_sd_drive_without_adding_virtio` | 通过 | +| axbuild clippy | `cargo xtask clippy --package axbuild` | 通过 | +| StarryOS K230 build | `cargo xtask starry build -c os/StarryOS/configs/board/k230-canmv.toml --arch riscv64` | 通过 | +| StarryOS K230 target clippy | K230 target 参数下 `cargo clippy -p starryos ...` | 通过 | +| QEMU K230 smoke | Docker 中运行 QEMU K230 direct boot | 通过,进入用户态 shell | +| `/dev/kpu` 节点 | guest 中 `ls -l /dev/kpu /dev/kpu0` | 通过,设备号 `240:1` | +| KPU CFG 读 | guest 中 `od -An -tx4 -N4 /dev/kpu` | 通过,读数 `00000000` | +| whitespace | `git diff --check` | 通过 | + +### StarryOS K230 构建 + +```sh +cd /Users/joshua/tmp/tgoskits/target/worktrees/tgoskits-k230-kpu +cargo xtask starry build -c os/StarryOS/configs/board/k230-canmv.toml --arch riscv64 +``` + +构建产物: + +```text +target/riscv64gc-unknown-none-elf/release/starryos.bin +``` + +### K230 target clippy + +```sh +AX_CONFIG_PATH=/Users/joshua/tmp/tgoskits/target/worktrees/tgoskits-k230-kpu/tmp/axbuild/axconfig/starryos/riscv64gc-unknown-none-elf/.axconfig.toml \ +CARGO_UNSTABLE_JSON_TARGET_SPEC=true \ +AX_PLATFORM=riscv64-k230 \ +AX_ARCH=riscv64 \ +AX_LOG=info \ +AX_TARGET=riscv64gc-unknown-none-elf \ +cargo clippy -p starryos \ + --target scripts/targets/no-pie/riscv64gc-unknown-none-elf.json \ + -Z unstable-options \ + --target-dir /Users/joshua/tmp/tgoskits/target/worktrees/tgoskits-k230-kpu/target \ + --features ax-hal/riscv64-k230,k230 \ + -Z json-target-spec \ + -Z build-std=core,alloc \ + --bin starryos \ + --release \ + -- -D warnings +``` + +### QEMU K230 构建 + +使用用户提供的 QEMU 源码目录: + +```sh +/Users/joshua/tmp/qemu +``` + +推荐 Docker 构建方式: + +```sh +docker run --rm -it \ + -v /Users/joshua/tmp/qemu:/qemu \ + -w /qemu \ + starryos-dev:ubuntu-qemu10.2.1 \ + bash -lc 'apt-get update && \ + apt-get install -y libfdt-dev device-tree-compiler && \ + mkdir -p build-k230 && \ + cd build-k230 && \ + ../configure --target-list=riscv64-softmmu --disable-werror && \ + ninja -j$(nproc)' +``` + +当前验证使用的 QEMU K230 binary: + +```text +/Users/joshua/tmp/tgoskits/target/qemu-k230-docker-build/qemu-system-riscv64 +``` + +该路径是为了便于在 StarryOS worktree 侧复用 QEMU K230 binary;源码和构建依据仍来自 `/Users/joshua/tmp/qemu` 的 `chao-k230-dev` 分支。 + +### QEMU smoke 命令 + +```sh +docker run --rm -it \ + -v /Users/joshua/tmp:/mnt/tmp \ + -w /mnt/tmp/tgoskits/target/worktrees/tgoskits-k230-kpu \ + starryos-dev:ubuntu-qemu10.2.1 \ + bash -lc '/mnt/tmp/tgoskits/target/qemu-k230-docker-build/qemu-system-riscv64 \ + -L /mnt/tmp/qemu/pc-bios \ + -machine k230 \ + -smp 1 \ + -m 2G \ + -nographic \ + -kernel target/riscv64gc-unknown-none-elf/release/starryos.bin \ + -dtb os/StarryOS/configs/board/k230-canmv.dtb \ + -append "console=ttyS0,115200 earlycon=sbi root=/dev/mmcblk0p2 rootwait rw" \ + -drive if=sd,file=/mnt/tmp/tgoskits/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img,format=raw' +``` + +关键通过点: + +- early boot 正常进入 StarryOS。 +- `axmm` 初始化通过。 +- K230 UART 控制台输出正常。 +- SDHCI1 probe 成功。 +- SD card 初始化成功。 +- block device 注册成功。 +- ext4 rootfs 挂载成功。 +- 进入用户态 shell。 + +### guest 内 KPU smoke + +进入 StarryOS shell 后执行: + +```sh +ls -l /dev/kpu /dev/kpu0 +od -An -tx4 -N4 /dev/kpu +``` + +已确认: + +```text +/dev/kpu -> 240:1 +/dev/kpu0 -> 240:1 +od 读数 -> 00000000 +``` + +这说明 StarryOS `/dev` 节点创建、KPU CFG MMIO 映射和 read path 已经打通。 + +## 当前阶段结论 + +阶段一已经完成 QEMU K230 上 StarryOS 启动与 KPU 基础设备暴露。最关键的系统路径已经闭环: + +```text +QEMU K230 direct boot + -> StarryOS riscv64-k230 platform + -> UART console + -> SDHCI1 block device + -> ext4 rootfs mount + -> user shell + -> /dev/kpu basic MMIO access +``` + +KPU 侧已经完成以下底座能力: + +- KPU CFG/L2 地址与寄存器常量固化。 +- 命令流物理地址编程接口就绪。 +- start/clear/status/wait_done 接口就绪。 +- `/dev/kpu` ioctl ABI 初步成型。 +- KPU CFG 与 KPU L2 mmap 路径就绪。 + +这意味着后续可以在用户态构造或移植 KPU command stream,然后通过 `/dev/kpu` 完成更接近真实推理的验证。 + +## 后续计划 + +建议后续阶段按以下顺序推进: + +1. 固化 `/dev/kpu` 用户态头文件或 Rust 绑定,避免 ioctl 编号和结构体布局在用户程序中重复手写。 +2. 编写最小 KPU smoke 用户程序,覆盖 `mmap KPU L2 -> 写 command stream -> KPU_IOC_RUN -> KPU_IOC_WAIT_DONE -> KPU_IOC_GET_STATUS`。 +3. 引入真实 KPU command stream 样例,验证 QEMU KPU GNNE 执行路径。 +4. 根据 QEMU 模型行为决定是否把 KPU IRQ 接入等待队列或 event/poll 语义,减少纯轮询。 +5. 补充 qemu-k230 自动化测试配置,使 smoke 能进入常规 Starry test flow。 +6. 在 rootfs 镜像切换为带分区表镜像时,重新确认 `root=`、SD1 block 命名和 `ax-fs-ng` 分区选择策略。 +7. 如果后续需要支持真实硬件,再对照 K230 手册检查 QEMU 模型和真实 KPU 寄存器行为差异。 diff --git a/drivers/npu/k230-kpu/Cargo.toml b/drivers/npu/k230-kpu/Cargo.toml new file mode 100644 index 0000000000..b58e0e10b9 --- /dev/null +++ b/drivers/npu/k230-kpu/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "k230-kpu" +version = "0.1.0" +repository = "https://github.com/rcore-os/tgoskits" +description = "Register-level driver for the Kendryte K230 KPU." +keywords = ["arceos", "starryos", "k230", "kpu", "npu"] +categories = ["embedded", "no-std", "hardware-support"] +edition.workspace = true +authors = ["rcore-os developers"] +license.workspace = true + +[dependencies] + +[package.metadata.docs.rs] +targets = ["riscv64gc-unknown-none-elf"] diff --git a/drivers/npu/k230-kpu/src/lib.rs b/drivers/npu/k230-kpu/src/lib.rs new file mode 100644 index 0000000000..4b626620a6 --- /dev/null +++ b/drivers/npu/k230-kpu/src/lib.rs @@ -0,0 +1,161 @@ +#![no_std] + +use core::{hint::spin_loop, ptr}; + +pub const KPU_CFG_PADDR: usize = 0x8040_0000; +pub const KPU_CFG_SIZE: usize = 0x800; +pub const KPU_L2_PADDR: usize = 0x8000_0000; +pub const KPU_L2_SIZE: usize = 0x20_0000; +pub const KPU_IRQ: usize = 189; + +pub const COMMAND_START: usize = 0x100; +pub const COMMAND_END: usize = 0x104; +pub const COMMAND_HI: usize = 0x108; +pub const CONTROL: usize = 0x128; +pub const STATUS_LO: usize = 0x130; +pub const STATUS_HI: usize = 0x134; + +pub const CONTROL_CLEAR: u32 = 0x4; +pub const CONTROL_START: u32 = 0x9; +pub const DONE_STATUS: u64 = 0x0000_0004_0000_0004; + +pub const KPU_IOC_GET_STATUS: u32 = 0x4b00; +pub const KPU_IOC_CLEAR: u32 = 0x4b01; +pub const KPU_IOC_PROGRAM_COMMAND: u32 = 0x4b02; +pub const KPU_IOC_START: u32 = 0x4b03; +pub const KPU_IOC_RUN: u32 = 0x4b04; +pub const KPU_IOC_WAIT_DONE: u32 = 0x4b05; + +pub const KPU_MMAP_CFG_OFFSET: u64 = 0; +pub const KPU_MMAP_L2_OFFSET: u64 = 0x1000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + CommandRangeCrosses4G, + CommandRangeEmpty, + TimedOut, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CommandRange { + pub start_paddr: u64, + pub end_paddr: u64, +} + +#[derive(Debug, Clone, Copy)] +pub struct Kpu { + base_vaddr: usize, +} + +impl Kpu { + /// # Safety + /// + /// `base_vaddr` must point to a valid mapped K230 KPU CFG MMIO window. + pub const unsafe fn new(base_vaddr: usize) -> Self { + Self { base_vaddr } + } + + pub fn program_command(&self, range: CommandRange) -> Result<(), Error> { + let (start, end, hi) = command_words(range)?; + self.write_reg(COMMAND_START, start); + self.write_reg(COMMAND_END, end); + self.write_reg(COMMAND_HI, hi); + Ok(()) + } + + pub fn run_command(&self, range: CommandRange) -> Result<(), Error> { + self.clear_done(); + self.program_command(range)?; + self.start(); + Ok(()) + } + + pub fn clear_done(&self) { + self.write_reg(CONTROL, CONTROL_CLEAR); + } + + pub fn start(&self) { + self.write_reg(CONTROL, CONTROL_START); + } + + pub fn status(&self) -> u64 { + let lo = self.read_reg(STATUS_LO) as u64; + let hi = self.read_reg(STATUS_HI) as u64; + (hi << 32) | lo + } + + pub fn is_done(&self) -> bool { + self.status() & DONE_STATUS == DONE_STATUS + } + + pub fn wait_done(&self, poll_limit: usize) -> Result<(), Error> { + for _ in 0..poll_limit { + if self.is_done() { + return Ok(()); + } + spin_loop(); + } + Err(Error::TimedOut) + } + + pub fn read_reg(&self, offset: usize) -> u32 { + unsafe { ptr::read_volatile((self.base_vaddr + offset) as *const u32) } + } + + pub fn write_reg(&self, offset: usize, value: u32) { + unsafe { ptr::write_volatile((self.base_vaddr + offset) as *mut u32, value) } + } +} + +pub fn command_words(range: CommandRange) -> Result<(u32, u32, u32), Error> { + if range.start_paddr >= range.end_paddr { + return Err(Error::CommandRangeEmpty); + } + if range.start_paddr >> 32 != range.end_paddr >> 32 { + return Err(Error::CommandRangeCrosses4G); + } + Ok(( + range.start_paddr as u32, + range.end_paddr as u32, + (range.start_paddr >> 32) as u32, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn command_words_accept_same_4g_window() { + assert_eq!( + command_words(CommandRange { + start_paddr: 0x1_0000_1000, + end_paddr: 0x1_0000_2000, + }), + Ok((0x1000, 0x2000, 0x1)) + ); + } + + #[test] + fn command_words_reject_empty_range() { + assert_eq!( + command_words(CommandRange { + start_paddr: 0x1000, + end_paddr: 0x1000, + }), + Err(Error::CommandRangeEmpty) + ); + } + + #[test] + fn command_words_reject_crossing_4g_window() { + assert_eq!( + command_words(CommandRange { + start_paddr: 0xffff_ff00, + end_paddr: 0x1_0000_0100, + }), + Err(Error::CommandRangeCrosses4G) + ); + } +} diff --git a/os/StarryOS/configs/board/k230-canmv.dtb b/os/StarryOS/configs/board/k230-canmv.dtb new file mode 100644 index 0000000000000000000000000000000000000000..58cce83eab52185a30eec85fc2e607f24cb556af GIT binary patch literal 7002 zcmd5>J!~XJ7Opn`a2(73&q{Z;jDWzsJ7aryy(d;0aTgtjw1^)80ZUJJji=?F?xw59 zvqm5-2_iZH*=Z98gs|ZSC*27_Lda$p-h~K|NJtPueBbLTPtA-C9wT5&s;-~+>b>_> zz51CqpWXibS4tg!Nh!6V)PoPBd<^+cRPKAME*Y@FU3M%*#ad*eXy;(o`yGwvNSTG|%cR&tIl_)@FJBHqAqe8gs-R&c(CBldi^M z@Tolp+DrP26ZF$F^p_{-XJ+V_56p>^zcEAaPtecK&<8>{WeWRFZHxgp`>F~0c%mGA zG(jIvlB2&a^ud7mPtiQj7l{AO3Ho@k=ubi1FO1GnYu1;(`c||2tv|Kfr(jJEUzS9D zPawY+7r7}ieXg!|o@uQ#T#GuYvK>&)nQppR6-9mSWTLlE!mMt#)h<`1Zxp5VVl`^y zHTuh;I3T(x-}yfGgKN|GnOysFaQVBB&<^5q*zvG(>=pE(*yH^d8%P{WY)f@tN6AWj zwzZ+E)Wq8)B9ZLu}o*V5Qj`1W)j>y%s#-)$&AmBA}!d+qFA{|%^J zEw-W^>4AM|UhAqFmUW1EsjDK(bY*D{%YV^!SV{UEnKWWvYIFcm2uU$|8X9qakIeb= zb1=|@qT1jf`U`Wo~X>WS3H-|4AWhQfpIp=qHU3%2;AHX)oQ@ zDC*LPq+YTfDx#yhxsO*MuR>WJ40?POu<@eHyIvG=( zZ2B0JYd^a1?bnyQ_8G|Y=(5h7FTr9wfp*MXx^DR9|4uga2WL#ern5zK(T5vdMeNg9 zXTP;u{L{6$AK)82K1%Kry&fveDUaPrHWXFvGKpaoB(bhfahzq8zT|G^BKz|qL(8R_gOvosFdCt4@O$|?Oaf}f7m+*};A zhv}OdoSz|2+sZkkjkj1Dp>a=>Pi(KW(f+xCv%TFKpWRm8P%uondWg_@g2}^jjvU)5qoS>x8>#7DgEc!heUNkJ5b*! zs^flAY>L9kds_V5fj(W6@(!{#ZUUoS9rtO_RFFpV-*UIt#6Lk;I z#C;@d>e=nQX->PvUZ|O|o7kNl({}swj0FmIyU{SQQ|-pggN;7?i3&c3%0sV{ds)qi zcab`fX7KXdaJFBIc->2JPP{EayAR&GuW`KYWj!a}&Ovy2#$9vk>wbppcOEzAjyCCL zK8h+Gf|uBcfzE5so%8HN^8Hyl+8_6k%m&5i9o405*q*k0Zr+l>!hvg3ae+xX|@yx2;pV454-{TTGC zU>pB5o)_CC0z;Y`+XEQ%t6&@dte+R#JA&*8*dF|)vAK|hyBMuLp7Trg4HJjPq&m6b}4~EU) zddJG5Ex!~ls94~i^L4PR~E|FPuQA*8eSepnq7qB-~;(9nP+2547vb9CqzDS6nmZTb83FL2Sc>vCcB1 zmQDJaw)H`g=uE}Mpp5Diu?S`3(6&U#QT~=#wdI;E*A?E4Eq>bktn#zQ&$_~akJx0H zVrZGJD(ssUKWNrTF|0!<>MO<`qiWNrIvwaP{tyX!m40=Ipzn@C1UrrOg?gvds?wV_ zK7~*T#0*2}Hg!}Z=(X$xdj*--9tVHRev~Ib23dosRESF=%A>ZUY^c?;gc0prH1)9| zO&d0sFguS1_}8Y2@=X+bdQ7M<);doR!)XG?sBWue8%Ub8$s2}AsB~T{(O{awY0#UY z>8HJ#_J(N@j?x4Z77r_q2u-P>D<2NR7}i`2O(Y>eEMl}1=7y)89WjV5dXpMQB~IM1 dpKkUW5jOS|j!KmmAr^>*Nzy_k)dhNm`T?_w#Ge2F literal 0 HcmV?d00001 diff --git a/os/StarryOS/configs/board/k230-canmv.dts b/os/StarryOS/configs/board/k230-canmv.dts new file mode 100644 index 0000000000..17203fe720 --- /dev/null +++ b/os/StarryOS/configs/board/k230-canmv.dts @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +/dts-v1/; + +/ { + #address-cells = <2>; + #size-cells = <2>; + model = "Canaan CanMV-K230"; + compatible = "canaan,k230-canmv", "canaan,k230"; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; + serial3 = &uart3; + serial4 = &uart4; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c2 = &i2c2; + i2c3 = &i2c3; + i2c4 = &i2c4; + spi0 = &spi0; + spi1 = &spi1; + spi2 = &spi2; + mmc0 = &sd0; + mmc1 = &sd1; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs = "console=ttyS0,115200 earlycon=sbi root=/dev/mmcblk1p2 rootwait rw"; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + timebase-frequency = <27000000>; + + cpu0: cpu@0 { + device_type = "cpu"; + reg = <0>; + compatible = "thead,c908", "riscv"; + riscv,isa = "rv64imafdc_zicsr_zifencei"; + mmu-type = "riscv,sv39"; + status = "okay"; + + cpu0_intc: interrupt-controller { + #interrupt-cells = <1>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + }; + }; + }; + + memory@8200000 { + device_type = "memory"; + /* + * Match K230 SDK memory layout: + * 0x00000000-0x001fffff reserved (bootrom, 2 MiB) + * 0x00200000-0x07ffffff RTT big-core firmware (126 MiB) + * 0x08000000-0x081fffff OpenSBI (2 MiB, runtime-reserved) + * 0x08200000-0x7fffffff Linux (1918 MiB) + */ + reg = <0x0 0x08200000 0x0 0x77e00000>; + }; + + qemu_finisher: test@100000 { + compatible = "sifive,test1", "sifive,test0", "syscon"; + reg = <0x0 0x00100000 0x0 0x1000>; + }; + + reboot { + compatible = "syscon-reboot"; + regmap = <&qemu_finisher>; + offset = <0x0>; + value = <0x7777>; + }; + + poweroff { + compatible = "syscon-poweroff"; + regmap = <&qemu_finisher>; + offset = <0x0>; + value = <0x5555>; + }; + + apb_clk: apb-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <50000000>; + clock-output-names = "apb"; + }; + + sd_clk: sd-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "sd"; + }; + + soc { + #address-cells = <2>; + #size-cells = <2>; + compatible = "simple-bus"; + ranges; + + clint: clint@f04000000 { + compatible = "sifive,clint0", "riscv,clint0"; + reg = <0xf 0x04000000 0x0 0x400000>; + interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>; + }; + + plic: interrupt-controller@f00000000 { + compatible = "sifive,plic-1.0.0"; + reg = <0xf 0x00000000 0x0 0x400000>; + #interrupt-cells = <1>; + interrupt-controller; + riscv,ndev = <208>; + interrupts-extended = <&cpu0_intc 11>, <&cpu0_intc 9>; + }; + + reset: reset-controller@91101000 { + compatible = "canaan,k230-rst"; + reg = <0x0 0x91101000 0x0 0x1000>; + #reset-cells = <1>; + status = "disabled"; + }; + + pinctrl: pinctrl@91105000 { + compatible = "canaan,k230-pinctrl"; + reg = <0x0 0x91105000 0x0 0x100>; + status = "disabled"; + }; + + watchdog0: watchdog@91106000 { + compatible = "snps,dw-wdt"; + reg = <0x0 0x91106000 0x0 0x800>; + interrupts = <107>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "tclk"; + status = "disabled"; + }; + + uart0: serial@91400000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0x91400000 0x0 0x1000>; + interrupts = <16>; + interrupt-parent = <&plic>; + clock-frequency = <50000000>; + reg-shift = <2>; + reg-io-width = <4>; + status = "okay"; + }; + + uart1: serial@91401000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0x91401000 0x0 0x1000>; + interrupts = <17>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "baudclk"; + clock-frequency = <50000000>; + reg-shift = <2>; + reg-io-width = <4>; + current-speed = <115200>; + status = "disabled"; + }; + + uart2: serial@91402000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0x91402000 0x0 0x1000>; + interrupts = <18>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "baudclk"; + clock-frequency = <50000000>; + reg-shift = <2>; + reg-io-width = <4>; + current-speed = <115200>; + status = "disabled"; + }; + + uart3: serial@91403000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0x91403000 0x0 0x1000>; + interrupts = <19>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "baudclk"; + clock-frequency = <50000000>; + reg-shift = <2>; + reg-io-width = <4>; + current-speed = <115200>; + status = "disabled"; + }; + + uart4: serial@91404000 { + compatible = "snps,dw-apb-uart"; + reg = <0x0 0x91404000 0x0 0x1000>; + interrupts = <20>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "baudclk"; + clock-frequency = <50000000>; + reg-shift = <2>; + reg-io-width = <4>; + current-speed = <115200>; + status = "disabled"; + }; + + i2c0: i2c@91405000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x91405000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <21>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ref"; + clock-frequency = <100000>; + status = "disabled"; + }; + + i2c1: i2c@91406000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x91406000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <22>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ref"; + clock-frequency = <400000>; + status = "disabled"; + }; + + i2c2: i2c@91407000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x91407000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <23>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ref"; + clock-frequency = <400000>; + status = "disabled"; + }; + + i2c3: i2c@91408000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x91408000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <24>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ref"; + clock-frequency = <400000>; + status = "disabled"; + }; + + i2c4: i2c@91409000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x91409000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <25>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ref"; + clock-frequency = <400000>; + status = "disabled"; + }; + + spi1: spi@91582000 { + compatible = "snps,dwc-ssi-1.01a"; + reg = <0x0 0x91582000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <155>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ssi_clk"; + num-cs = <1>; + reg-io-width = <4>; + status = "disabled"; + }; + + spi2: spi@91583000 { + compatible = "snps,dwc-ssi-1.01a"; + reg = <0x0 0x91583000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <164>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ssi_clk"; + num-cs = <1>; + reg-io-width = <4>; + status = "disabled"; + }; + + spi0: spi@91584000 { + compatible = "snps,dwc-ssi-1.01a"; + reg = <0x0 0x91584000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <146>; + interrupt-parent = <&plic>; + clocks = <&apb_clk>; + clock-names = "ssi_clk"; + rx-sample-delay-ns = <5>; + num-cs = <1>; + reg-io-width = <4>; + status = "disabled"; + }; + + sd0: mmc@91580000 { + compatible = "snps,dwcmshc-sdhci"; + reg = <0x0 0x91580000 0x0 0x1000>; + interrupts = <142>; + interrupt-parent = <&plic>; + clocks = <&sd_clk>, <&apb_clk>; + clock-names = "core", "bus"; + bus-width = <4>; + max-frequency = <50000000>; + status = "disabled"; + }; + + sd1: mmc@91581000 { + compatible = "snps,dwcmshc-sdhci"; + reg = <0x0 0x91581000 0x0 0x1000>; + interrupts = <144>; + interrupt-parent = <&plic>; + clocks = <&sd_clk>, <&apb_clk>; + clock-names = "core", "bus"; + bus-width = <4>; + max-frequency = <50000000>; + cap-sd-highspeed; + disable-wp; + no-mmc; + no-sdio; + status = "okay"; + }; + + kpu: kpu@80400000 { + compatible = "canaan,k230-kpu"; + reg = <0x0 0x80400000 0x0 0x800>; + interrupts = <189>; + interrupt-parent = <&plic>; + status = "okay"; + }; + + usb0: usb@91500000 { + compatible = "snps,dwc2"; + reg = <0x0 0x91500000 0x0 0x10000>; + interrupts = <173>; + interrupt-parent = <&plic>; + dr_mode = "host"; + status = "disabled"; + }; + + usb1: usb@91540000 { + compatible = "snps,dwc2"; + reg = <0x0 0x91540000 0x0 0x10000>; + interrupts = <174>; + interrupt-parent = <&plic>; + dr_mode = "host"; + status = "okay"; + }; + }; +}; diff --git a/os/StarryOS/configs/board/k230-canmv.toml b/os/StarryOS/configs/board/k230-canmv.toml new file mode 100644 index 0000000000..620d961dfd --- /dev/null +++ b/os/StarryOS/configs/board/k230-canmv.toml @@ -0,0 +1,7 @@ +env = {} +features = [ + "k230", +] +log = "Info" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/os/StarryOS/configs/qemu/qemu-k230.toml b/os/StarryOS/configs/qemu/qemu-k230.toml new file mode 100644 index 0000000000..15921aba29 --- /dev/null +++ b/os/StarryOS/configs/qemu/qemu-k230.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", + "k230", + "-smp", + "1", + "-m", + "2G", + "-nographic", + "-dtb", + "${workspace}/os/StarryOS/configs/board/k230-canmv.dtb", + "-drive", + "if=sd,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", +] +uefi = false +to_bin = true +success_regex = [] +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index b3e594d8de..fee68b025d 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -21,6 +21,7 @@ input = ["dep:ax-input", "ax-feat/input"] ebpf = [] memtrack = ["ax-feat/dwarf", "ax-alloc/tracking", "dep:gimli"] rknpu = ["dep:ax-driver", "ax-driver/rknpu"] +k230-kpu = ["dep:k230-kpu"] plat-dyn = [ "dep:ax-driver", "ax-driver/usb", @@ -74,6 +75,7 @@ ax-sync.workspace = true ax-task.workspace = true ax-dma = { workspace = true, optional = true } sg2002-tpu = { workspace = true, optional = true } +k230-kpu = { workspace = true, optional = true } crab-usb = { workspace = true, optional = true } rdrive = { workspace = true, optional = true } diff --git a/os/StarryOS/kernel/src/pseudofs/dev/kpu.rs b/os/StarryOS/kernel/src/pseudofs/dev/kpu.rs new file mode 100644 index 0000000000..0a740b9fee --- /dev/null +++ b/os/StarryOS/kernel/src/pseudofs/dev/kpu.rs @@ -0,0 +1,176 @@ +use core::{ + any::Any, + mem::{MaybeUninit, size_of}, +}; + +use ax_memory_addr::{PhysAddr, PhysAddrRange}; +use ax_runtime::hal::{cpu::asm::user_copy, mem::phys_to_virt}; +use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; +use k230_kpu::{ + CommandRange, KPU_CFG_PADDR, KPU_CFG_SIZE, KPU_IOC_CLEAR, KPU_IOC_GET_STATUS, + KPU_IOC_PROGRAM_COMMAND, KPU_IOC_RUN, KPU_IOC_START, KPU_IOC_WAIT_DONE, KPU_L2_PADDR, + KPU_L2_SIZE, KPU_MMAP_CFG_OFFSET, KPU_MMAP_L2_OFFSET, Kpu, +}; + +use crate::pseudofs::{DeviceMmap, DeviceOps}; + +pub const KPU_DEVICE_ID: DeviceId = DeviceId::new(240, 1); +const KPU_CFG_MMAP_PADDR_OFFSET: u64 = KPU_CFG_PADDR as u64; +const KPU_L2_MMAP_PADDR_OFFSET: u64 = KPU_L2_PADDR as u64; + +pub struct KpuDevice { + hw: Kpu, +} + +impl KpuDevice { + pub fn new() -> Self { + let base_vaddr = phys_to_virt(PhysAddr::from(KPU_CFG_PADDR)).as_usize(); + Self { + hw: unsafe { Kpu::new(base_vaddr) }, + } + } + + fn copy_command_range(arg: usize) -> VfsResult { + copy_from_user(arg) + } +} + +impl Default for KpuDevice { + fn default() -> Self { + Self::new() + } +} + +impl DeviceOps for KpuDevice { + fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult { + if buf.len() < size_of::() || !offset.is_multiple_of(size_of::() as u64) { + return Err(VfsError::InvalidInput); + } + let offset = usize::try_from(offset).map_err(|_| VfsError::InvalidInput)?; + if offset + size_of::() > KPU_CFG_SIZE { + return Err(VfsError::InvalidInput); + } + let value = self.hw.read_reg(offset).to_ne_bytes(); + let len = buf.len().min(value.len()); + buf[..len].copy_from_slice(&value[..len]); + Ok(len) + } + + fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult { + if buf.len() < size_of::() || !offset.is_multiple_of(size_of::() as u64) { + return Err(VfsError::InvalidInput); + } + let offset = usize::try_from(offset).map_err(|_| VfsError::InvalidInput)?; + if offset + size_of::() > KPU_CFG_SIZE { + return Err(VfsError::InvalidInput); + } + let mut bytes = [0_u8; size_of::()]; + bytes.copy_from_slice(&buf[..size_of::()]); + self.hw.write_reg(offset, u32::from_ne_bytes(bytes)); + Ok(size_of::()) + } + + fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult { + match cmd { + KPU_IOC_GET_STATUS => { + let status = self.hw.status(); + copy_to_user(arg, &status)?; + Ok(0) + } + KPU_IOC_CLEAR => { + self.hw.clear_done(); + Ok(0) + } + KPU_IOC_PROGRAM_COMMAND => { + let range = Self::copy_command_range(arg)?; + self.hw + .program_command(range) + .map_err(|_| VfsError::InvalidInput)?; + Ok(0) + } + KPU_IOC_START => { + self.hw.start(); + Ok(0) + } + KPU_IOC_RUN => { + let range = Self::copy_command_range(arg)?; + self.hw + .run_command(range) + .map_err(|_| VfsError::InvalidInput)?; + Ok(0) + } + KPU_IOC_WAIT_DONE => { + let poll_limit = if arg == 0 { 1_000_000 } else { arg }; + self.hw + .wait_done(poll_limit) + .map_err(|_| VfsError::TimedOut)?; + Ok(0) + } + _ => Err(VfsError::OperationNotSupported), + } + } + + fn mmap(&self, offset: u64, length: u64) -> DeviceMmap { + let Some(length) = usize::try_from(length).ok() else { + return DeviceMmap::None; + }; + match offset { + KPU_MMAP_CFG_OFFSET | KPU_CFG_MMAP_PADDR_OFFSET => { + DeviceMmap::Physical(PhysAddrRange::from_start_size( + PhysAddr::from(KPU_CFG_PADDR), + length.min(KPU_CFG_SIZE), + )) + } + KPU_MMAP_L2_OFFSET | KPU_L2_MMAP_PADDR_OFFSET => { + DeviceMmap::Physical(PhysAddrRange::from_start_size( + PhysAddr::from(KPU_L2_PADDR), + length.min(KPU_L2_SIZE), + )) + } + _ => DeviceMmap::None, + } + } + + fn flags(&self) -> NodeFlags { + NodeFlags::NON_CACHEABLE + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +fn copy_from_user(arg: usize) -> VfsResult { + if arg == 0 { + return Err(VfsError::InvalidInput); + } + let mut value = MaybeUninit::::uninit(); + let ret = unsafe { + user_copy( + value.as_mut_ptr().cast::(), + arg as *const u8, + size_of::(), + ) + }; + if ret != 0 { + return Err(VfsError::InvalidData); + } + Ok(unsafe { value.assume_init() }) +} + +fn copy_to_user(arg: usize, value: &T) -> VfsResult<()> { + if arg == 0 { + return Err(VfsError::InvalidInput); + } + let ret = unsafe { + user_copy( + arg as *mut u8, + (value as *const T).cast::(), + size_of::(), + ) + }; + if ret != 0 { + return Err(VfsError::InvalidData); + } + Ok(()) +} diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index fa482e1c1c..3f5c2b17b9 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -9,6 +9,8 @@ mod drm; #[cfg(feature = "input")] pub mod event; mod fb; +#[cfg(feature = "k230-kpu")] +mod kpu; #[cfg(feature = "dev-log")] mod log; mod r#loop; @@ -452,5 +454,28 @@ fn builder(fs: Arc) -> DirMaker { ); } + #[cfg(feature = "k230-kpu")] + { + let kpu = Arc::new(kpu::KpuDevice::new()); + root.add( + "kpu", + Device::new( + fs.clone(), + NodeType::CharacterDevice, + kpu::KPU_DEVICE_ID, + kpu.clone(), + ), + ); + root.add( + "kpu0", + Device::new( + fs.clone(), + NodeType::CharacterDevice, + kpu::KPU_DEVICE_ID, + kpu, + ), + ); + } + SimpleDir::new_maker(fs, Arc::new(root)) } diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index c9fb4538aa..52b5733ac9 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -30,6 +30,10 @@ sg2002 = [ "ax-hal/riscv64-sg2002", "starry-kernel/sg2002", ] +k230 = [ + "ax-hal/riscv64-k230", + "starry-kernel/k230-kpu", +] # Enable the GICv3 distributor + redistributor + system-register # CPU interface on aarch64. Only meaningful when the target is the diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 9e4fb56fd9..e1e78cc82a 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -15,6 +15,7 @@ smp = [ "ax-plat-aarch64-bsta1000b?/smp", "ax-plat-aarch64-phytium-pi?/smp", "ax-plat-riscv64-qemu-virt?/smp", + "ax-plat-riscv64-k230?/smp", "ax-plat-riscv64-sg2002?/smp", "ax-plat-riscv64-visionfive2?/smp", "ax-plat-loongarch64-qemu-virt?/smp", @@ -29,6 +30,7 @@ irq = [ "ax-plat-aarch64-bsta1000b?/irq", "ax-plat-aarch64-phytium-pi?/irq", "ax-plat-riscv64-qemu-virt?/irq", + "ax-plat-riscv64-k230?/irq", "ax-plat-riscv64-sg2002?/irq", "ax-plat-riscv64-visionfive2?/irq", "ax-plat-loongarch64-qemu-virt?/irq", @@ -44,6 +46,7 @@ fp-simd = [ "ax-plat-aarch64-bsta1000b?/fp-simd", "ax-plat-aarch64-phytium-pi?/fp-simd", "ax-plat-riscv64-qemu-virt?/fp-simd", + "ax-plat-riscv64-k230?/fp-simd", "ax-plat-riscv64-sg2002?/fp-simd", "ax-plat-riscv64-visionfive2?/fp-simd", "ax-plat-loongarch64-qemu-virt?/fp-simd", @@ -57,6 +60,7 @@ rtc = [ "ax-plat-aarch64-bsta1000b?/rtc", "ax-plat-aarch64-phytium-pi?/rtc", "ax-plat-riscv64-qemu-virt?/rtc", + "ax-plat-riscv64-k230?/rtc", "ax-plat-riscv64-sg2002?/rtc", "ax-plat-riscv64-visionfive2?/rtc", "ax-plat-loongarch64-qemu-virt?/rtc", @@ -79,6 +83,7 @@ paging = [ "ax-plat-aarch64-qemu-virt?/paging", "ax-plat-loongarch64-qemu-virt?/paging", "ax-plat-riscv64-qemu-virt?/paging", + "ax-plat-riscv64-k230?/paging", ] tls = ["ax-cpu/tls"] uspace = ["paging", "ax-cpu/uspace", "axplat-dyn?/uspace"] @@ -100,6 +105,7 @@ aarch64-raspi = ["dep:ax-plat-aarch64-raspi"] aarch64-bsta1000b = ["dep:ax-plat-aarch64-bsta1000b"] aarch64-phytium-pi = ["dep:ax-plat-aarch64-phytium-pi"] riscv64-qemu-virt = ["dep:ax-plat-riscv64-qemu-virt"] +riscv64-k230 = ["dep:ax-plat-riscv64-k230"] riscv64-sg2002 = ["dep:ax-plat-riscv64-sg2002"] riscv64-visionfive2 = ["dep:ax-plat-riscv64-visionfive2"] riscv64-qemu-virt-hv = [ @@ -149,6 +155,7 @@ ax-plat-aarch64-phytium-pi = { workspace = true, optional = true } [target.'cfg(target_arch = "riscv64")'.dependencies] ax-plat-riscv64-qemu-virt = { workspace = true, optional = true } +ax-plat-riscv64-k230 = { workspace = true, optional = true } ax-plat-riscv64-sg2002 = { workspace = true, optional = true } ax-plat-riscv64-visionfive2 = { workspace = true, optional = true } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index 116ca0125c..454286e9d9 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -55,6 +55,11 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ target_arch: Some("riscv64"), crate_name: "ax_plat_riscv64_qemu_virt", }, + PlatformFeature { + feature: "riscv64-k230", + target_arch: Some("riscv64"), + crate_name: "ax_plat_riscv64_k230", + }, PlatformFeature { feature: "riscv64-sg2002", target_arch: Some("riscv64"), diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index 6fdaf7753c..68c2c38e63 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -8,6 +8,7 @@ //! //! - `x86-pc`: Standard PC with x86_64 ISA. //! - `riscv64-qemu-virt`: QEMU virt machine with RISC-V ISA. +//! - `riscv64-k230`: QEMU Kendryte K230 machine with RISC-V ISA. //! - `aarch64-qemu-virt`: QEMU virt machine with AArch64 ISA. //! - `aarch64-raspi`: Raspberry Pi with AArch64 ISA. //! - `dummy`: If none of the above platform is selected, the dummy platform diff --git a/platforms/ax-plat-riscv64-k230/CHANGELOG.md b/platforms/ax-plat-riscv64-k230/CHANGELOG.md new file mode 100644 index 0000000000..e474e6cd33 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.5.9 + +- Add the initial QEMU Kendryte K230 platform package. diff --git a/platforms/ax-plat-riscv64-k230/Cargo.toml b/platforms/ax-plat-riscv64-k230/Cargo.toml new file mode 100644 index 0000000000..24bf0d7df5 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "ax-plat-riscv64-k230" +version = "0.5.9" +repository = "https://github.com/rcore-os/tgoskits" +description = "Implementation of `axplat` hardware abstraction layer for the QEMU Kendryte K230 board." +keywords = ["arceos", "os", "hal", "embedded", "riscv"] +categories = ["embedded", "no-std", "hardware-support", "os"] +edition.workspace = true +authors = ["Yuekai Jia ", "Youjie Zheng ", "yanjuguang ", "Su Mingxian ", "RobertYuan <634954435@qq.com>", "hky1999 "] +license = "Apache-2.0" + +[features] +fp-simd = ["ax-cpu/fp-simd"] +hypervisor = ["irq", "smp"] +irq = ["ax-plat/irq", "dep:ax-riscv-plic"] +paging = ["dep:axklib"] +rtc = [] +smp = ["ax-plat/smp"] + +[dependencies] +ax-kspin = { workspace = true } +ax-lazyinit = { workspace = true } +log = "0.4" +riscv = "0.16" +ax-riscv-plic = { workspace = true, optional = true } +sbi-rt = { version = "0.0.3", features = ["legacy"] } +some-serial.workspace = true + +ax-config-macros = { workspace = true } +ax-cpu = { workspace = true } +ax-driver = { workspace = true, features = ["block"] } +ax-plat = { workspace = true } +axklib = { workspace = true, optional = true } +mmio-api.workspace = true +rd-block.workspace = true +sdhci-host.workspace = true +sdmmc-protocol = { workspace = true, default-features = false, features = ["sdio"] } +spin.workspace = true + +[package.metadata.axplat] +platform = "riscv64-k230" +arch = "riscv64" +config = "axconfig.toml" +crate = "ax_plat_riscv64_k230" + +[package.metadata.docs.rs] +targets = ["riscv64gc-unknown-none-elf"] diff --git a/platforms/ax-plat-riscv64-k230/README.md b/platforms/ax-plat-riscv64-k230/README.md new file mode 100644 index 0000000000..6d391bd729 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/README.md @@ -0,0 +1,8 @@ +# ax-plat-riscv64-k230 + +`ax-plat-riscv64-k230` provides the `axplat` hardware abstraction layer for +the QEMU Kendryte K230 machine. + +The platform follows the K230 direct-boot layout used by the Kunos QEMU flow: +kernel at `0x0820_0000`, UART0 at `0x9140_0000`, PLIC at `0xf000_00000`, and +KPU CFG/L2 windows at `0x8040_0000` and `0x8000_0000`. diff --git a/platforms/ax-plat-riscv64-k230/README_CN.md b/platforms/ax-plat-riscv64-k230/README_CN.md new file mode 100644 index 0000000000..b022690175 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/README_CN.md @@ -0,0 +1,7 @@ +# ax-plat-riscv64-k230 + +`ax-plat-riscv64-k230` 是面向 QEMU Kendryte K230 机器的 `axplat` 平台适配。 + +该平台使用 Kunos QEMU 流程中的 K230 direct-boot 布局:内核加载到 +`0x0820_0000`,UART0 位于 `0x9140_0000`,PLIC 位于 `0xf000_00000`,KPU +CFG/L2 窗口分别位于 `0x8040_0000` 和 `0x8000_0000`。 diff --git a/platforms/ax-plat-riscv64-k230/axconfig.toml b/platforms/ax-plat-riscv64-k230/axconfig.toml new file mode 100644 index 0000000000..a15fd0d5c8 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/axconfig.toml @@ -0,0 +1,79 @@ +# Architecture identifier. +arch = "riscv64" # str +# Platform identifier. +platform = "riscv64-k230" # str +# Platform package. +package = "ax-plat-riscv64-k230" # str + +# +# Platform configs +# +[plat] +# Maximum number of CPUs. For platforms that do not support runtime CPU number +# detection, it's also the number of CPUs to boot. +max-cpu-num = 1 # uint +# Base address of Linux-usable DDR in QEMU K230 direct boot. +phys-memory-base = 0x0820_0000 # uint +# Size of usable DDR before the KPU L2/cache window. +phys-memory-size = 0x77e0_0000 # uint +# Base physical address of the kernel image. +kernel-base-paddr = 0x0820_0000 # uint +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ffc0_0820_0000" # uint +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ffc0_0000_0000" # uint +# Offset of bus address and phys address. some boards, the bus address is +# different from the physical address. +phys-bus-offset = 0 # uint +# Kernel address space base. +kernel-aspace-base = "0xffff_ffc0_0000_0000" # uint +# Kernel address space size. +kernel-aspace-size = "0x0000_003f_ffff_f000" # uint +# Stack size on bootstrapping. (256K) +boot-stack-size = 0x40000 # uint + +# +# Device specifications +# +[devices] +# MMIO ranges with format (`base_paddr`, `size`). +mmio-ranges = [ + [0x8000_0000, 0x0020_0000], # KPU L2 cache + [0x8020_0000, 0x0020_0000], # SRAM + [0x8040_0000, 0x0000_2000], # KPU CFG + FFT + AI 2D + [0x9100_0c00, 0x0000_0400], # RTC + [0x9110_8000, 0x0000_0800], # STC + [0x9140_0000, 0x0000_1000], # UART0 + [0x9158_0000, 0x0000_1000], # SD0 + [0x9158_1000, 0x0000_1000], # SD1 + [0xf000_00000, 0x0040_0000], # PLIC + [0xf040_00000, 0x0040_0000], # CLINT +] # [(uint, uint)] +# VirtIO MMIO ranges with format (`base_paddr`, `size`). +virtio-mmio-ranges = [] # [(uint, uint)] + +# Timer interrupt frequency in Hz. +timer-frequency = 27_000_000 # uint +# Timer interrupt num. +timer-irq = "0x8000_0000_0000_0005" # uint +# IPI interrupt num +ipi-irq = "0x8000_0000_0000_0001" # uint + +rtc-paddr = 0x9100_0c00 # uint + +plic-paddr = 0xf000_00000 # uint + +uart-paddr = 0x9140_0000 # uint +uart-irq = 16 # uint +uart-clock = 50_000_000 # uint + +sd1-paddr = 0x9158_1000 # uint +sd1-size = 0x1000 # uint +sd1-irq = 144 # uint + +kpu-cfg-paddr = 0x8040_0000 # uint +kpu-cfg-size = 0x800 # uint +kpu-l2-paddr = 0x8000_0000 # uint +kpu-l2-size = 0x20_0000 # uint +kpu-irq = 189 # uint diff --git a/platforms/ax-plat-riscv64-k230/build.rs b/platforms/ax-plat-riscv64-k230/build.rs new file mode 100644 index 0000000000..12b902da8a --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/build.rs @@ -0,0 +1,6 @@ +fn main() { + println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); + if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { + println!("cargo:rerun-if-changed={config_path}"); + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/boot.rs b/platforms/ax-plat-riscv64-k230/src/boot.rs new file mode 100644 index 0000000000..beaa90d2cb --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/boot.rs @@ -0,0 +1,112 @@ +use ax_plat::mem::{Aligned4K, pa}; + +use crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; + +#[unsafe(link_section = ".bss.stack")] +static mut BOOT_STACK: [u8; BOOT_STACK_SIZE] = [0; BOOT_STACK_SIZE]; + +#[unsafe(link_section = ".data")] +static mut BOOT_PT_SV39: Aligned4K<[u64; 512]> = Aligned4K::new([0; 512]); + +const PTE_FLAGS: u64 = 0xef; // VRWX_GAD +const PTE_SO: u64 = 1_u64 << 63; +const PTE_CACHE: u64 = 1_u64 << 62; +const PTE_BUF: u64 = 1_u64 << 61; +const PTE_SHARE: u64 = 1_u64 << 60; + +const MEMORY_FLAGS: u64 = PTE_FLAGS | PTE_CACHE | PTE_BUF | PTE_SHARE; +const IOREMAP_FLAGS: u64 = PTE_FLAGS | PTE_SO | PTE_SHARE; + +const fn sv39_l1_entry(paddr: usize, flags: u64) -> u64 { + (((paddr >> 12) as u64) << 10) | flags +} + +const fn high_map_index(paddr: usize) -> usize { + ((PHYS_VIRT_OFFSET + paddr) >> 30) & 0x1ff +} + +unsafe fn init_boot_page_table() { + unsafe { + // Direct mappings keep early code running after enabling MMU. + BOOT_PT_SV39[0] = sv39_l1_entry(0x0000_0000, MEMORY_FLAGS); + BOOT_PT_SV39[1] = sv39_l1_entry(0x4000_0000, MEMORY_FLAGS); + BOOT_PT_SV39[2] = sv39_l1_entry(0x8000_0000, IOREMAP_FLAGS); + BOOT_PT_SV39[0x3c] = sv39_l1_entry(0xf000_00000, IOREMAP_FLAGS); + + // High-half linear mappings used by phys_to_virt(). + BOOT_PT_SV39[high_map_index(0x0000_0000)] = sv39_l1_entry(0x0000_0000, MEMORY_FLAGS); + BOOT_PT_SV39[high_map_index(0x4000_0000)] = sv39_l1_entry(0x4000_0000, MEMORY_FLAGS); + BOOT_PT_SV39[high_map_index(0x8000_0000)] = sv39_l1_entry(0x8000_0000, IOREMAP_FLAGS); + BOOT_PT_SV39[high_map_index(0xf000_00000)] = sv39_l1_entry(0xf000_00000, IOREMAP_FLAGS); + } +} + +unsafe fn init_mmu() { + unsafe { + ax_cpu::asm::write_kernel_page_table(pa!(&raw const BOOT_PT_SV39 as usize)); + ax_cpu::asm::flush_tlb(None); + } +} + +/// The earliest entry point for the primary CPU. +#[unsafe(naked)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text.boot")] +unsafe extern "C" fn _start() -> ! { + // PC = 0x0820_0000 + // a0 = hartid + // a1 = dtb + core::arch::naked_asm!(" + mv s0, a0 // save hartid + mv s1, a1 // save DTB pointer + la sp, {boot_stack} + li t0, {boot_stack_size} + add sp, sp, t0 // setup boot stack + + call {init_boot_page_table} + call {init_mmu} // setup boot page table and enabel MMU + + li s2, {phys_virt_offset} // fix up virtual high address + add sp, sp, s2 + + mv a0, s0 + mv a1, s1 + la a2, {entry} + add a2, a2, s2 + jalr a2 // call_main(cpu_id, dtb) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const BOOT_STACK_SIZE, + boot_stack = sym BOOT_STACK, + init_boot_page_table = sym init_boot_page_table, + init_mmu = sym init_mmu, + entry = sym ax_plat::call_main, + ) +} + +/// The earliest entry point for secondary CPUs. +#[cfg(feature = "smp")] +#[unsafe(naked)] +pub(crate) unsafe extern "C" fn _start_secondary() -> ! { + // a0 = hartid + // a1 = SP + core::arch::naked_asm!(" + mv s0, a0 // save hartid + mv sp, a1 // set SP + + call {init_mmu} // setup boot page table and enabel MMU + + li s1, {phys_virt_offset} // fix up virtual high address + add a1, a1, s1 + add sp, sp, s1 + + mv a0, s0 + la a1, {entry} + add a1, a1, s1 + jalr a1 // call_secondary_main(cpu_id) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + init_mmu = sym init_mmu, + entry = sym ax_plat::call_secondary_main, + ) +} diff --git a/platforms/ax-plat-riscv64-k230/src/console.rs b/platforms/ax-plat-riscv64-k230/src/console.rs new file mode 100644 index 0000000000..c3ab9b8e74 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/console.rs @@ -0,0 +1,67 @@ +use ax_kspin::SpinNoIrq; +use ax_lazyinit::LazyInit; +use ax_plat::console::ConsoleIf; +#[cfg(feature = "irq")] +use ax_plat::console::ConsoleIrqEvent; +use some_serial::ns16550::dw_apb::DwApbUart; + +use crate::config::{ + devices::{UART_CLOCK, UART_PADDR}, + plat::PHYS_VIRT_OFFSET, +}; + +static UART: LazyInit> = LazyInit::new(); + +pub(crate) fn init_early() { + UART.init_once({ + let mut uart = DwApbUart::new(UART_PADDR + PHYS_VIRT_OFFSET); + uart.init_with_baud_clk(115_200, UART_CLOCK as u32); + SpinNoIrq::new(uart) + }); +} + +struct ConsoleIfImpl; + +#[impl_plat_interface] +impl ConsoleIf for ConsoleIfImpl { + /// Writes bytes to the console from input u8 slice. + fn write_bytes(bytes: &[u8]) { + for &c in bytes { + let mut uart = UART.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } + } + } + + /// Reads bytes from the console into the given mutable slice. + /// Returns the number of bytes read. + fn read_bytes(bytes: &mut [u8]) -> usize { + let mut uart = UART.lock(); + for (i, byte) in bytes.iter_mut().enumerate() { + match uart.getchar() { + Some(c) => *byte = c, + None => return i, + } + } + bytes.len() + } + + /// Returns the IRQ number for the console, if applicable. + #[cfg(feature = "irq")] + fn irq_num() -> Option { + None + } + + #[cfg(feature = "irq")] + fn set_input_irq_enabled(_enabled: bool) {} + + #[cfg(feature = "irq")] + fn handle_irq() -> ConsoleIrqEvent { + ConsoleIrqEvent::empty() + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/drivers.rs b/platforms/ax-plat-riscv64-k230/src/drivers.rs new file mode 100644 index 0000000000..c405e9a905 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/drivers.rs @@ -0,0 +1,435 @@ +use alloc::{boxed::Box, format, sync::Arc, vec::Vec}; +use core::num::NonZeroUsize; +#[cfg(not(feature = "paging"))] +use core::ptr::NonNull; + +use ax_driver::{DriverGeneric, PlatformDevice, block::PlatformDeviceBlock, probe::OnProbeError}; +use ax_kspin::SpinNoIrq; +#[cfg(not(feature = "paging"))] +use ax_plat::mem::{pa, phys_to_virt}; +use log::{info, warn}; +use mmio_api::MmioRaw; +#[cfg(not(feature = "paging"))] +use mmio_api::{MapError, MmioAddr, MmioOp}; +use rd_block::{BlkError, BuffConfig, Event, IQueue, Interface, Request, RequestId}; +use sdhci_host::{BlockRequest, BlockRequestSlot, Sdhci}; +use sdmmc_protocol::{ + BlockPoll, BlockTransferMode, Error, OperationPoll, + error::Phase, + sdio::{CardInfo, CardInitPreference, SdioInitScratch, SdioSdmmc}, +}; + +use crate::config::devices; + +const BLOCK_SIZE: usize = 512; +const SDHCI_POWER_330: u8 = 0x0e; +const DEVICE_NAME: &str = "k230-sdhci1"; + +type K230Sdhci = SdioSdmmc; + +#[cfg(not(feature = "paging"))] +static DIRECT_MMIO: DirectMmio = DirectMmio; + +#[cfg(not(feature = "paging"))] +struct DirectMmio; + +#[cfg(not(feature = "paging"))] +impl MmioOp for DirectMmio { + fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { + let ptr = NonNull::new(phys_to_virt(pa!(addr.as_usize())).as_mut_ptr()) + .ok_or(MapError::Invalid)?; + Ok(unsafe { MmioRaw::new(addr, ptr, size) }) + } + + fn iounmap(&self, _mmio: &MmioRaw) {} +} + +ax_driver::model_register!( + name: "Static K230 SDHCI1", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_sdhci1, + }], +); + +fn probe_sdhci1(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + register_sdhci( + plat_dev, + devices::SD1_PADDR, + devices::SD1_SIZE, + devices::SD1_IRQ, + ) +} + +fn register_sdhci( + plat_dev: PlatformDevice, + paddr: usize, + size: usize, + irq: usize, +) -> Result<(), OnProbeError> { + let mmio = map_mmio_raw(paddr, size).map_err(|err| { + OnProbeError::other(format!("failed to map K230 SDHCI at {paddr:#x}: {err:?}")) + })?; + info!("k230-sdhci: probe SD1 at [PA:{paddr:#x}, SZ:{size:#x})"); + + let mut host = unsafe { Sdhci::new(mmio.as_nonnull_ptr()) }; + host.reset_all() + .map_err(|err| init_error(paddr, size, err))?; + host.set_power(SDHCI_POWER_330); + host.enable_interrupts(); + + let mut card = SdioSdmmc::new(host); + card.set_sd_uhs_selection_enabled(false); + card.set_sd_speed_selection_enabled(false); + let card_info = + poll_card_init_sd(&mut card).map_err(|err| card_init_error(paddr, size, err))?; + info!( + "k230-sdhci: card kind={:?} high_capacity={} rca={} ocr={:#010x} capacity_blocks={:?}", + card_info.kind, + card_info.high_capacity, + card_info.rca, + card_info.ocr, + card_info.capacity_blocks, + ); + + let raw = Arc::new(SpinNoIrq::new(card)); + let dev = BlockDevice { + raw: raw.clone(), + capacity_blocks: card_info.capacity_blocks.unwrap_or(0), + queue_created: false, + irq_enabled: false, + }; + plat_dev.register_block_with_irq(dev, Some(irq)); + info!("k230-sdhci: block device registered irq={irq}"); + Ok(()) +} + +fn map_mmio_raw(base: usize, size: usize) -> Result { + #[cfg(feature = "paging")] + { + axklib::mmio::ioremap_raw(base.into(), size) + } + #[cfg(not(feature = "paging"))] + { + DIRECT_MMIO.ioremap(base.into(), size) + } +} + +fn poll_card_init_sd(card: &mut K230Sdhci) -> Result { + let mut scratch = SdioInitScratch::new(); + let mut request = + card.submit_init_with_preference(CardInitPreference::SdFirst, &mut scratch)?; + loop { + match card.poll_init_request(&mut request)? { + OperationPoll::Pending => { + if request.take_needs_pace() { + wait_init_pace(); + } else { + core::hint::spin_loop(); + } + } + OperationPoll::Complete(info) => return Ok(info), + _ => return Err(Error::UnsupportedCommand), + } + } +} + +fn wait_init_pace() { + #[cfg(feature = "paging")] + { + axklib::time::busy_wait(core::time::Duration::from_millis(10)); + } + + #[cfg(not(feature = "paging"))] + { + for _ in 0..10_000 { + core::hint::spin_loop(); + } + } +} + +struct BlockDevice { + raw: Arc>, + capacity_blocks: u64, + queue_created: bool, + irq_enabled: bool, +} + +struct BlockQueue { + raw: Arc>, + capacity_blocks: u64, + id: usize, + slot: BlockRequestSlot, + pending: Option, + completed: Vec, +} + +impl DriverGeneric for BlockDevice { + fn name(&self) -> &str { + DEVICE_NAME + } +} + +impl Interface for BlockDevice { + fn create_queue(&mut self) -> Option> { + if self.queue_created { + return None; + } + self.queue_created = true; + Some(Box::new(BlockQueue { + raw: self.raw.clone(), + capacity_blocks: self.capacity_blocks, + id: 0, + slot: BlockRequestSlot::default(), + pending: None, + completed: Vec::new(), + })) + } + + fn enable_irq(&mut self) { + self.raw.lock().host_mut().enable_completion_irq(); + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.raw.lock().host_mut().disable_completion_irq(); + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + block_event_from_sdhci_irq(self.raw.lock().host_mut().handle_irq()) + } +} + +fn block_event_from_sdhci_irq(irq_event: sdhci_host::Event) -> Event { + match irq_event { + sdhci_host::Event::None => Event::none(), + sdhci_host::Event::CommandComplete + | sdhci_host::Event::TransferComplete + | sdhci_host::Event::Error { .. } + | sdhci_host::Event::Other { .. } => { + let mut event = Event::none(); + event.queue.insert(0); + event + } + } +} + +impl IQueue for BlockQueue { + fn id(&self) -> usize { + self.id + } + + fn num_blocks(&self) -> usize { + self.capacity_blocks as usize + } + + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn buff_config(&self) -> BuffConfig { + BuffConfig { + dma_mask: u64::MAX, + align: BLOCK_SIZE, + size: BLOCK_SIZE, + } + } + + fn submit_request(&mut self, request: Request<'_>) -> Result { + self.reap_pending_request()?; + let mut raw = self.raw.lock(); + let start_block = block_addr_for_card(request.block_id, raw.is_high_capacity())?; + let id = match request.kind { + rd_block::RequestKind::Read(buffer) => { + if !buffer.len().is_multiple_of(BLOCK_SIZE) { + return Err(BlkError::Other("read buffer is not block aligned".into())); + } + let ptr = core::ptr::NonNull::new(buffer.virt) + .ok_or_else(|| BlkError::Other("read buffer pointer is null".into()))?; + let size = NonZeroUsize::new(buffer.len()) + .ok_or_else(|| BlkError::Other("read buffer is empty".into()))?; + submit_read_request( + raw.host_mut(), + start_block, + ptr, + size, + &mut self.slot, + &mut self.pending, + )? + } + rd_block::RequestKind::Write(items) => { + if !items.len().is_multiple_of(BLOCK_SIZE) { + return Err(BlkError::Other("write buffer is not block aligned".into())); + } + let ptr = core::ptr::NonNull::new(items.as_ptr() as *mut u8) + .ok_or_else(|| BlkError::Other("write buffer pointer is null".into()))?; + let size = NonZeroUsize::new(items.len()) + .ok_or_else(|| BlkError::Other("write buffer is empty".into()))?; + submit_write_request( + raw.host_mut(), + start_block, + ptr, + size, + &mut self.slot, + &mut self.pending, + )? + } + }; + Ok(RequestId::new(usize::from(id))) + } + + fn poll_request(&mut self, request: RequestId) -> Result<(), BlkError> { + if let Some(index) = self.completed.iter().position(|id| *id == request) { + self.completed.swap_remove(index); + return Ok(()); + } + self.poll_active_request(request) + } +} + +impl BlockQueue { + fn pending_id(&self) -> Option { + self.pending.as_ref().map(BlockRequest::id) + } + + fn reap_pending_request(&mut self) -> Result<(), BlkError> { + let Some(active) = self.pending_id() else { + return Ok(()); + }; + let id = RequestId::new(usize::from(active)); + match self.poll_active_request(id) { + Ok(()) => { + self.completed.push(id); + Ok(()) + } + Err(BlkError::Retry) => Err(BlkError::Retry), + Err(err) => Err(err), + } + } + + fn poll_active_request(&mut self, request: RequestId) -> Result<(), BlkError> { + match self.raw.lock().host_mut().poll_block_request( + &mut self.pending, + sdhci_host::RequestId::new(usize::from(request)), + &mut self.slot, + ) { + Ok(BlockPoll::Complete) => Ok(()), + Ok(BlockPoll::Pending) => Err(BlkError::Retry), + Ok(_) => Err(BlkError::Other( + "SDHCI returned an unknown poll state".into(), + )), + Err(err) => Err(map_dev_err_to_blk_err(err)), + } + } +} + +fn submit_read_request( + host: &mut Sdhci, + start_block: u32, + buffer: core::ptr::NonNull, + size: NonZeroUsize, + slot: &mut BlockRequestSlot, + pending: &mut Option, +) -> Result { + if pending.is_some() { + return Err(BlkError::Retry); + } + let request = host + .submit_read_blocks( + start_block, + buffer, + size, + None, + BlockTransferMode::Fifo, + slot, + ) + .map_err(map_dev_err_to_blk_err)?; + let id = request.id(); + *pending = Some(request); + Ok(id) +} + +fn submit_write_request( + host: &mut Sdhci, + start_block: u32, + buffer: core::ptr::NonNull, + size: NonZeroUsize, + slot: &mut BlockRequestSlot, + pending: &mut Option, +) -> Result { + if pending.is_some() { + return Err(BlkError::Retry); + } + let request = host + .submit_write_blocks( + start_block, + buffer, + size, + None, + BlockTransferMode::Fifo, + slot, + ) + .map_err(map_dev_err_to_blk_err)?; + let id = request.id(); + *pending = Some(request); + Ok(id) +} + +fn block_addr_for_card(block_id: usize, high_capacity: bool) -> Result { + let block_id = u32::try_from(block_id).map_err(|_| BlkError::InvalidBlockIndex(block_id))?; + if high_capacity { + Ok(block_id) + } else { + block_id + .checked_mul(BLOCK_SIZE as u32) + .ok_or(BlkError::InvalidBlockIndex(block_id as usize)) + } +} + +fn map_dev_err_to_blk_err(err: Error) -> BlkError { + match err { + Error::NoCard | Error::UnsupportedCommand | Error::CardLocked => BlkError::NotSupported, + Error::Misaligned | Error::InvalidArgument => { + BlkError::Other("SD/MMC request is not block aligned".into()) + } + _ => BlkError::Other("SDHCI I/O error".into()), + } +} + +fn init_error(address: usize, size: usize, err: Error) -> OnProbeError { + OnProbeError::other(format!( + "failed to initialize K230 SDHCI at [PA:{address:#x}, SZ:{size:#x}): {err:?}", + )) +} + +fn card_init_error(address: usize, size: usize, err: Error) -> OnProbeError { + if is_absent_card_init_error(err) { + warn!( + "k230-sdhci: no responsive card at [PA:{address:#x}, SZ:{size:#x}); skipping \ + controller: {err:?}", + ); + return OnProbeError::NotMatch; + } + + init_error(address, size, err) +} + +fn is_absent_card_init_error(err: Error) -> bool { + match err { + Error::NoCard => true, + Error::Timeout(ctx) | Error::Crc(ctx) | Error::BadResponse(ctx) => { + ctx.cmd.is_some() + && matches!( + ctx.phase, + Phase::CommandSend | Phase::ResponseWait | Phase::Init + ) + } + _ => false, + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/init.rs b/platforms/ax-plat-riscv64-k230/src/init.rs new file mode 100644 index 0000000000..5ee5f80afb --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/init.rs @@ -0,0 +1,40 @@ +use ax_plat::init::InitIf; + +struct InitIfImpl; + +#[impl_plat_interface] +impl InitIf for InitIfImpl { + /// This function should be called immediately after the kernel has booted, + /// and performed earliest platform configuration and initialization (e.g., + /// early console, clocking). + fn init_early(_cpu_id: usize, _mbi: usize) { + ax_cpu::init::init_trap(); + crate::console::init_early(); + crate::time::init_early(); + } + + /// Initializes the platform at the early stage for secondary cores. + #[cfg(feature = "smp")] + fn init_early_secondary(_cpu_id: usize) { + ax_cpu::init::init_trap(); + } + + /// Initializes the platform at the later stage for the primary core. + /// + /// This function should be called after the kernel has done part of its + /// initialization (e.g, logging, memory management), and finalized the rest of + /// platform configuration and initialization. + fn init_later(_cpu_id: usize, _arg: usize) { + #[cfg(feature = "irq")] + crate::irq::init_percpu(); + crate::time::init_percpu(); + } + + /// Initializes the platform at the later stage for secondary cores. + #[cfg(feature = "smp")] + fn init_later_secondary(_cpu_id: usize) { + #[cfg(feature = "irq")] + crate::irq::init_percpu(); + crate::time::init_percpu(); + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/irq.rs b/platforms/ax-plat-riscv64-k230/src/irq.rs new file mode 100644 index 0000000000..91d246b73c --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/irq.rs @@ -0,0 +1,292 @@ +use core::{ + num::NonZeroU32, + ptr::NonNull, + sync::atomic::{AtomicPtr, Ordering}, +}; + +use ax_kspin::SpinNoIrq; +use ax_plat::{ + irq::{HandlerTable, IpiTarget, IrqHandler, IrqIf}, + percpu::this_cpu_id, +}; +use ax_riscv_plic::Plic; +use riscv::register::sie; +use sbi_rt::HartMask; + +use crate::config::{devices::PLIC_PADDR, plat::PHYS_VIRT_OFFSET}; + +/// `Interrupt` bit in `scause` +pub(super) const INTC_IRQ_BASE: usize = 1 << (usize::BITS - 1); + +/// Supervisor software interrupt in `scause` +#[allow(unused)] +pub(super) const S_SOFT: usize = INTC_IRQ_BASE + 1; + +/// Supervisor timer interrupt in `scause` +pub(super) const S_TIMER: usize = INTC_IRQ_BASE + 5; + +/// Supervisor external interrupt in `scause` +pub(super) const S_EXT: usize = INTC_IRQ_BASE + 9; + +static TIMER_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +static IPI_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +#[cfg(feature = "hypervisor")] +static VIRTUAL_IRQ_INJECTOR: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +/// The maximum number of IRQs. +const MAX_IRQ_COUNT: usize = 1024; + +static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + +static PLIC: SpinNoIrq = SpinNoIrq::new(unsafe { + Plic::new(NonNull::new((PHYS_VIRT_OFFSET + PLIC_PADDR) as *mut _).unwrap()) +}); + +fn this_context() -> usize { + let hart_id = this_cpu_id(); + hart_id * 2 + 1 // supervisor context +} + +pub(super) fn init_percpu() { + // enable soft interrupts, timer interrupts, and external interrupts + unsafe { + sie::set_ssoft(); + sie::set_stimer(); + sie::set_sext(); + } + PLIC.lock().init_by_context(this_context()); +} + +/// Registers the virtual interrupt injector used by hypervisor builds. +#[cfg(feature = "hypervisor")] +pub fn register_virtual_irq_injector(injector: fn(usize)) { + VIRTUAL_IRQ_INJECTOR.store(injector as *mut (), Ordering::Release); +} + +macro_rules! with_cause { + ( + $cause:expr, @S_TIMER => + $timer_op:expr, @S_SOFT => + $ipi_op:expr, @S_EXT => + $ext_op:expr, @EX_IRQ => + $plic_op:expr $(,)? + ) => { + match $cause { + S_TIMER => $timer_op, + S_SOFT => $ipi_op, + S_EXT => $ext_op, + other => { + if other & INTC_IRQ_BASE == 0 { + // Device-side interrupts read from PLIC + $plic_op + } else { + // Other CPU-side interrupts + panic!("Unknown IRQ cause: {other}"); + } + } + } + }; +} + +struct IrqIfImpl; + +#[impl_plat_interface] +impl IrqIf for IrqIfImpl { + /// Enables or disables the given IRQ. + fn set_enable(irq: usize, enabled: bool) { + with_cause!( + irq, + @S_TIMER => { + unsafe { + if enabled { + sie::set_stimer(); + } else { + sie::clear_stimer(); + } + } + }, + @S_SOFT => {}, + @S_EXT => {}, + @EX_IRQ => { + let Some(irq) = NonZeroU32::new(irq as _) else { + return; + }; + trace!("PLIC set enable: {irq} {enabled}"); + let mut plic = PLIC.lock(); + if enabled { + plic.set_priority(irq, 6); + plic.enable(irq, this_context()); + } else { + plic.disable(irq, this_context()); + } + } + ); + } + + /// Registers an IRQ handler for the given IRQ. + /// + /// It also enables the IRQ if the registration succeeds. It returns `false` if + /// the registration failed. + /// + /// The `irq` parameter has the following semantics + /// 1. If its highest bit is 1, it means it is an interrupt on the CPU side. Its + /// value comes from `scause`, where [`S_SOFT`] represents software interrupt + /// and [`S_TIMER`] represents timer interrupt. If its value is [`S_EXT`], it + /// means it is an external interrupt, and the real IRQ number needs to + /// be obtained from PLIC. + /// 2. If its highest bit is 0, it means it is an interrupt on the device side, + /// and its value is equal to the IRQ number provided by PLIC. + fn register(irq: usize, handler: IrqHandler) -> bool { + with_cause!( + irq, + @S_TIMER => TIMER_HANDLER.compare_exchange(core::ptr::null_mut(), handler as *mut _, Ordering::AcqRel, Ordering::Acquire).is_ok(), + @S_SOFT => IPI_HANDLER.compare_exchange(core::ptr::null_mut(), handler as *mut _, Ordering::AcqRel, Ordering::Acquire).is_ok(), + @S_EXT => { + warn!("External IRQ should be got from PLIC, not scause"); + false + }, + @EX_IRQ => { + if IRQ_HANDLER_TABLE.register_handler(irq, handler) { + Self::set_enable(irq, true); + true + } else { + warn!("register handler for External IRQ {irq} failed"); + false + } + } + ) + } + + /// Unregisters the IRQ handler for the given IRQ. + /// + /// It also disables the IRQ if the unregistration succeeds. It returns the + /// existing handler if it is registered, `None` otherwise. + fn unregister(irq: usize) -> Option { + with_cause!( + irq, + @S_TIMER => { + let handler = TIMER_HANDLER.swap(core::ptr::null_mut(), Ordering::AcqRel); + if !handler.is_null() { + Some(unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler) }) + } else { + None + } + }, + @S_SOFT => { + let handler = IPI_HANDLER.swap(core::ptr::null_mut(), Ordering::AcqRel); + if !handler.is_null() { + Some(unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler) }) + } else { + None + } + }, + @S_EXT => { + warn!("External IRQ should be got from PLIC, not scause"); + None + }, + @EX_IRQ => IRQ_HANDLER_TABLE.unregister_handler(irq).inspect(|_| Self::set_enable(irq, false)) + ) + } + + /// Handles the IRQ. + /// + /// It is called by the common interrupt handler. It should look up in the + /// IRQ handler table and calls the corresponding handler. If necessary, it + /// also acknowledges the interrupt controller after handling. + fn handle(irq: usize) -> Option { + with_cause!( + irq, + @S_TIMER => { + trace!("IRQ: timer"); + let handler = TIMER_HANDLER.load(Ordering::Acquire); + if !handler.is_null() { + // SAFETY: The handler is guaranteed to be a valid function pointer. + unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler)(irq) }; + } + Some(irq) + }, + @S_SOFT => { + trace!("IRQ: IPI"); + // Clear SSIP before draining the queue so a new IPI that + // arrives while handling callbacks can set the pending bit + // again instead of losing the wakeup. + #[cfg(not(feature = "hypervisor"))] + unsafe { + riscv::register::sip::clear_ssoft(); + } + let handler = IPI_HANDLER.load(Ordering::Acquire); + if !handler.is_null() { + // SAFETY: The handler is guaranteed to be a valid function pointer. + unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler)(irq) }; + } + Some(irq) + }, + @S_EXT => { + // TODO: judge irq's ownership before handling (axvisor or any vm). + // Maybe later it will be done by registering all irqs IQR_HANDLER_TABLE. + + let mut plic = PLIC.lock(); + let Some(irq) = plic.claim(this_context()) else { + debug!("Spurious external IRQ"); + return None; + }; + #[cfg(feature = "hypervisor")] + { + drop(plic); + let injector = VIRTUAL_IRQ_INJECTOR.load(Ordering::Acquire); + if injector.is_null() { + warn!("virtual IRQ injector is not registered"); + } else { + // SAFETY: The injector is registered through + // register_virtual_irq_injector as a valid function pointer. + unsafe { + core::mem::transmute::<*mut (), fn(usize)>(injector)( + irq.get() as usize, + ); + } + } + } + #[cfg(not(feature = "hypervisor"))] + { + trace!("IRQ: external {irq}"); + IRQ_HANDLER_TABLE.handle(irq.get() as usize); + plic.complete(this_context(), irq); + } + Some(irq.get() as usize) + }, + @EX_IRQ => { + unreachable!("Device-side IRQs should be handled by triggering the External Interrupt."); + } + ) + } + + /// Sends an inter-processor interrupt (IPI) to the specified target CPU or all CPUs. + fn send_ipi(_irq_num: usize, target: IpiTarget) { + match target { + IpiTarget::Current { cpu_id } => { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << cpu_id, 0)); + if res.is_err() { + warn!("send_ipi failed: {res:?}"); + } + } + IpiTarget::Other { cpu_id } => { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << cpu_id, 0)); + if res.is_err() { + warn!("send_ipi failed: {res:?}"); + } + } + IpiTarget::AllExceptCurrent { cpu_id, cpu_num } => { + for i in 0..cpu_num { + if i != cpu_id { + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1 << i, 0)); + if res.is_err() { + warn!("send_ipi_all_others failed: {res:?}"); + } + } + } + } + } + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/lib.rs b/platforms/ax-plat-riscv64-k230/src/lib.rs new file mode 100644 index 0000000000..db66f31024 --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/lib.rs @@ -0,0 +1,39 @@ +#![no_std] +#![feature(used_with_arg)] + +extern crate alloc; + +#[macro_use] +extern crate log; +#[macro_use] +extern crate ax_plat; + +#[cfg(all(feature = "irq", not(feature = "hypervisor")))] +mod irq; + +#[cfg(feature = "hypervisor")] +pub mod irq; + +mod boot; +mod console; +mod drivers; +mod init; +mod mem; +mod power; +mod time; + +pub mod config { + //! Platform configuration module. + //! + //! If the `AX_CONFIG_PATH` environment variable is set, it will load the configuration from the specified path. + //! Otherwise, it will fall back to the `axconfig.toml` file in the current directory and generate the default configuration. + //! + //! If the `PACKAGE` field in the configuration does not match the package name, it will panic with an error message. + ax_config_macros::include_configs!(path_env = "AX_CONFIG_PATH", fallback = "axconfig.toml"); + assert_str_eq!( + PACKAGE, + env!("CARGO_PKG_NAME"), + "`PACKAGE` field in the configuration does not match the Package name. Please check your \ + configuration file." + ); +} diff --git a/platforms/ax-plat-riscv64-k230/src/mem.rs b/platforms/ax-plat-riscv64-k230/src/mem.rs new file mode 100644 index 0000000000..5c6f16407c --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/mem.rs @@ -0,0 +1,58 @@ +use ax_plat::mem::{MemIf, PhysAddr, RawRange, VirtAddr, pa, va}; + +use crate::config::{ + devices::MMIO_RANGES, + plat::{KERNEL_BASE_PADDR, PHYS_MEMORY_BASE, PHYS_MEMORY_SIZE, PHYS_VIRT_OFFSET}, +}; + +struct MemIfImpl; + +#[impl_plat_interface] +impl MemIf for MemIfImpl { + /// Returns all physical memory (RAM) ranges on the platform. + /// + /// All memory ranges except reserved ranges (including the kernel loaded + /// range) are free for allocation. + fn phys_ram_ranges() -> &'static [RawRange] { + // TODO: paser dtb to get the available memory ranges + // We can't directly use `PHYS_MEMORY_BASE` here, because it may has been used by sbi. + &[( + KERNEL_BASE_PADDR, + PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE - KERNEL_BASE_PADDR, + )] + } + + /// Returns all reserved physical memory ranges on the platform. + /// + /// Reserved memory can be contained in [`phys_ram_ranges`], they are not + /// allocatable but should be mapped to kernel's address space. + /// + /// Note that the ranges returned should not include the range where the + /// kernel is loaded. + fn reserved_phys_ram_ranges() -> &'static [RawRange] { + &[] + } + + /// Returns all device memory (MMIO) ranges on the platform. + fn mmio_ranges() -> &'static [RawRange] { + &MMIO_RANGES + } + + /// Translates a physical address to a virtual address. + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + va!(paddr.as_usize() + PHYS_VIRT_OFFSET) + } + + /// Translates a virtual address to a physical address. + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + pa!(vaddr.as_usize() - PHYS_VIRT_OFFSET) + } + + /// Returns the kernel address space base virtual address and size. + fn kernel_aspace() -> (VirtAddr, usize) { + ( + va!(crate::config::plat::KERNEL_ASPACE_BASE), + crate::config::plat::KERNEL_ASPACE_SIZE, + ) + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/power.rs b/platforms/ax-plat-riscv64-k230/src/power.rs new file mode 100644 index 0000000000..1a379c9bfb --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/power.rs @@ -0,0 +1,37 @@ +use ax_plat::power::PowerIf; + +struct PowerImpl; + +#[impl_plat_interface] +impl PowerIf for PowerImpl { + /// Bootstraps the given CPU core with the given initial stack (in physical + /// address). + /// + /// Where `cpu_id` is the logical CPU ID (0, 1, ..., N-1, N is the number of + /// CPU cores on the platform). + #[cfg(feature = "smp")] + fn cpu_boot(cpu_id: usize, stack_top_paddr: usize) { + use ax_plat::mem::{va, virt_to_phys}; + if sbi_rt::probe_extension(sbi_rt::Hsm).is_unavailable() { + warn!("HSM SBI extension is not supported for current SEE."); + return; + } + let entry = virt_to_phys(va!(crate::boot::_start_secondary as *const () as usize)); + sbi_rt::hart_start(cpu_id, entry.as_usize(), stack_top_paddr); + } + + /// Shutdown the whole system. + fn system_off() -> ! { + info!("Shutting down..."); + sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason); + warn!("It should shutdown!"); + loop { + ax_cpu::asm::halt(); + } + } + + /// Get the number of CPU cores available on this platform. + fn cpu_num() -> usize { + crate::config::plat::MAX_CPU_NUM + } +} diff --git a/platforms/ax-plat-riscv64-k230/src/time.rs b/platforms/ax-plat-riscv64-k230/src/time.rs new file mode 100644 index 0000000000..cd0c125aaa --- /dev/null +++ b/platforms/ax-plat-riscv64-k230/src/time.rs @@ -0,0 +1,52 @@ +use ax_plat::time::{NANOS_PER_SEC, TimeIf}; +use riscv::register::time; + +const NANOS_PER_TICK: u64 = NANOS_PER_SEC / crate::config::devices::TIMER_FREQUENCY as u64; +/// RTC wall time offset in nanoseconds at monotonic time base. +static mut RTC_EPOCHOFFSET_NANOS: u64 = 0; + +pub(super) fn init_early() {} + +pub(super) fn init_percpu() { + #[cfg(feature = "irq")] + sbi_rt::set_timer(0); +} + +struct TimeIfImpl; + +#[impl_plat_interface] +impl TimeIf for TimeIfImpl { + /// Returns the current clock time in hardware ticks. + fn current_ticks() -> u64 { + time::read() as u64 + } + + /// Converts hardware ticks to nanoseconds. + fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * NANOS_PER_TICK + } + + /// Converts nanoseconds to hardware ticks. + fn nanos_to_ticks(nanos: u64) -> u64 { + nanos / NANOS_PER_TICK + } + + /// Return epoch offset in nanoseconds (wall time offset to monotonic clock start). + fn epochoffset_nanos() -> u64 { + unsafe { RTC_EPOCHOFFSET_NANOS } + } + + /// Returns the IRQ number for the timer interrupt. + #[cfg(feature = "irq")] + fn irq_num() -> usize { + crate::config::devices::TIMER_IRQ + } + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds). + #[cfg(feature = "irq")] + fn set_oneshot_timer(deadline_ns: u64) { + sbi_rt::set_timer(Self::nanos_to_ticks(deadline_ns)); + } +} diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index 6b80f68708..8d1f20755c 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -948,6 +948,7 @@ fn is_known_ax_hal_platform_feature(platform: &str) -> bool { | "aarch64-raspi" | "aarch64-bsta1000b" | "aarch64-phytium-pi" + | "riscv64-k230" | "riscv64-qemu-virt" | "riscv64-sg2002" | "riscv64-visionfive2" diff --git a/scripts/axbuild/src/rootfs/qemu.rs b/scripts/axbuild/src/rootfs/qemu.rs index 7d47efc0e0..d98f4cb873 100644 --- a/scripts/axbuild/src/rootfs/qemu.rs +++ b/scripts/axbuild/src/rootfs/qemu.rs @@ -83,7 +83,11 @@ pub(crate) enum RootfsPatchMode { pub(crate) fn patch_rootfs(qemu: &mut QemuConfig, rootfs_path: &Path, mode: RootfsPatchMode) { match mode { RootfsPatchMode::ReplaceDriveOnly => replace_drive_arg(&mut qemu.args, rootfs_path), - RootfsPatchMode::EnsureDiskBootNet => ensure_disk_boot_net_args(qemu, rootfs_path), + RootfsPatchMode::EnsureDiskBootNet => { + if !replace_sd_drive_arg(&mut qemu.args, rootfs_path) { + ensure_disk_boot_net_args(qemu, rootfs_path); + } + } } } @@ -133,6 +137,43 @@ fn replace_drive_arg(args: &mut Vec, rootfs_path: &Path) { } } +fn replace_sd_drive_arg(args: &mut [String], rootfs_path: &Path) -> bool { + let mut replaced = false; + let mut index = 0; + while index < args.len() { + if args[index] == "-drive" && index + 1 < args.len() && drive_arg_is_sd(&args[index + 1]) { + replace_drive_file_part(&mut args[index + 1], rootfs_path); + replaced = true; + index += 2; + } else { + index += 1; + } + } + replaced +} + +fn drive_arg_is_sd(value: &str) -> bool { + value.split(',').any(|part| part == "if=sd") +} + +fn replace_drive_file_part(value: &mut String, rootfs_path: &Path) { + let replacement = format!("file={}", rootfs_path.display()); + let mut parts = value + .split(',') + .map(|part| { + if part.starts_with("file=") { + replacement.clone() + } else { + part.to_string() + } + }) + .collect::>(); + if !parts.iter().any(|part| part.starts_with("file=")) { + parts.push(replacement); + } + *value = parts.join(","); +} + /// Ensures a QEMU config contains the standard block device, drive, and user /// networking arguments required by the rootfs-backed boot flows. fn ensure_disk_boot_net_args(qemu: &mut QemuConfig, disk_img: &Path) { @@ -329,4 +370,30 @@ mod tests { ] ); } + + #[test] + fn ensure_disk_boot_net_patches_sd_drive_without_adding_virtio() { + let rootfs = Path::new("/tmp/new-rootfs.img"); + let mut qemu = QemuConfig { + args: vec![ + "-machine".to_string(), + "k230".to_string(), + "-drive".to_string(), + "if=sd,format=raw,file=/tmp/old-rootfs.img".to_string(), + ], + ..Default::default() + }; + + patch_rootfs(&mut qemu, rootfs, RootfsPatchMode::EnsureDiskBootNet); + + assert_eq!( + qemu.args, + vec![ + "-machine".to_string(), + "k230".to_string(), + "-drive".to_string(), + "if=sd,format=raw,file=/tmp/new-rootfs.img".to_string(), + ] + ); + } } diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index e304ffbd3e..32dd99ea49 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -102,11 +102,15 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu fn normalize_starry_platform_features(features: &mut Vec) { let has_sg2002 = features.iter().any(|feature| feature == "sg2002"); + let has_k230 = features.iter().any(|feature| feature == "k230"); let has_vf2 = features.iter().any(|feature| feature == "vf2"); if has_sg2002 { features.push("ax-hal/riscv64-sg2002".to_string()); } + if has_k230 { + features.push("ax-hal/riscv64-k230".to_string()); + } if has_vf2 { features.push("ax-hal/riscv64-visionfive2".to_string()); } @@ -728,7 +732,7 @@ HELLO = "world" let mut cargo = build_info.into_base_cargo_config_with_log( request.package.clone(), request.target.clone(), - StarryBuildInfo::build_cargo_args(&request.target, false, &[]), + StarryBuildInfo::build_cargo_args(&request.target, &[]), ); let metadata = crate::build::workspace_metadata().unwrap(); From a944a12f6123ecb476b78e9a635159523f37cb3f Mon Sep 17 00:00:00 2001 From: shuoshuo Date: Wed, 27 May 2026 23:04:32 +0800 Subject: [PATCH 2/2] test(starryos): add K230 KPU smoke case --- docs/k230-kpu-qemu-adaptation.md | 41 ++++++ test-suit/starryos/k230-qemu/README.md | 29 ++++ .../build-riscv64gc-unknown-none-elf.toml | 7 + .../qemu-k230/kpu-smoke/c/CMakeLists.txt | 8 ++ .../qemu-k230/kpu-smoke/c/src/main.c | 134 ++++++++++++++++++ .../qemu-k230/kpu-smoke/qemu-riscv64.toml | 25 ++++ 6 files changed, 244 insertions(+) create mode 100644 test-suit/starryos/k230-qemu/README.md create mode 100644 test-suit/starryos/k230-qemu/qemu-k230/build-riscv64gc-unknown-none-elf.toml create mode 100644 test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/CMakeLists.txt create mode 100644 test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/src/main.c create mode 100644 test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/qemu-riscv64.toml diff --git a/docs/k230-kpu-qemu-adaptation.md b/docs/k230-kpu-qemu-adaptation.md index eb0cfcc5cc..fbb6249326 100644 --- a/docs/k230-kpu-qemu-adaptation.md +++ b/docs/k230-kpu-qemu-adaptation.md @@ -182,6 +182,7 @@ flowchart TD | `scripts/axbuild/src/rootfs/qemu.rs` | 修改 | 保留并 patch `-drive if=sd,...`,避免注入 VirtIO rootfs | | `os/StarryOS/configs/board/k230-canmv.*` | 新增 | K230 board 配置和 DTB | | `os/StarryOS/configs/qemu/qemu-k230.toml` | 新增 | QEMU K230 参数 | +| `test-suit/starryos/k230-qemu/` | 新增 | 显式 K230 QEMU 测试组,包含 `/dev/kpu` 用户态 smoke | | `Cargo.toml`, `Cargo.lock` | 修改 | 纳入新增 crate | ## KPU 驱动 crate 设计 @@ -767,6 +768,46 @@ od 读数 -> 00000000 这说明 StarryOS `/dev` 节点创建、KPU CFG MMIO 映射和 read path 已经打通。 +## 用户态 KPU smoke 用例 + +在阶段一手动 guest smoke 的基础上,后续补充了一个可由 Starry test-suit 发现的 K230 专用测试组: + +```text +test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke +``` + +该测试组没有放入默认 `normal` group,因为它依赖带 K230 machine/KPU model 的 QEMU binary。默认 CI 使用的 stock QEMU 不一定包含 `-machine k230`,因此该组需要显式运行: + +```sh +PATH="$PWD/target/qemu-k230/bin:$PATH" \ +cargo xtask starry test qemu --test-group k230-qemu --arch riscv64 -c kpu-smoke +``` + +本地约定的 K230 QEMU 布局: + +```text +target/qemu-k230/bin/qemu-system-riscv64 +target/qemu-k230/pc-bios/ +``` + +`kpu-smoke` 使用 C asset pipeline 构建并安装: + +```text +/usr/bin/k230-kpu-smoke +``` + +该程序覆盖以下用户态 ABI: + +- 打开 `/dev/kpu` 与 `/dev/kpu0`。 +- 使用 `pread()` 从 KPU CFG offset `0` 读取 32-bit 寄存器。 +- 使用 `KPU_IOC_GET_STATUS` 读取 64-bit status。 +- 使用 `KPU_IOC_CLEAR` 清 done 状态。 +- 使用 `mmap(offset = 0)` 映射 KPU CFG,并从 mmap 区域读取 status 寄存器。 +- 使用 `mmap(offset = 0x1000)` 映射 KPU L2,并完成 32-bit 写入/读回。 +- 成功时输出稳定 marker:`KPU_SMOKE_PASS`。 + +该 smoke 仍不执行真实 KPU command stream,因此它验证的是 `/dev/kpu` 的基础 ABI 与映射路径,而不是 GNNE 模型推理结果。 + ## 当前阶段结论 阶段一已经完成 QEMU K230 上 StarryOS 启动与 KPU 基础设备暴露。最关键的系统路径已经闭环: diff --git a/test-suit/starryos/k230-qemu/README.md b/test-suit/starryos/k230-qemu/README.md new file mode 100644 index 0000000000..5bc5797347 --- /dev/null +++ b/test-suit/starryos/k230-qemu/README.md @@ -0,0 +1,29 @@ +# StarryOS K230 QEMU Cases + +This group contains K230-specific StarryOS QEMU cases. It is intentionally +separate from the default `normal` group because it requires a QEMU build that +contains the K230 machine and KPU model. + +Expected local layout for running these cases: + +```text +target/qemu-k230/bin/qemu-system-riscv64 +target/qemu-k230/pc-bios/ +``` + +The binary should come from the K230 QEMU branch, and `pc-bios` should point to +the matching QEMU source tree's `pc-bios` directory. One local setup option is: + +```sh +mkdir -p target/qemu-k230/bin +ln -sf /mnt/tmp/tgoskits/target/qemu-k230-docker-build/qemu-system-riscv64 \ + target/qemu-k230/bin/qemu-system-riscv64 +ln -sfn /mnt/tmp/qemu/pc-bios target/qemu-k230/pc-bios +``` + +Then run the group with the K230 QEMU binary prepended to `PATH`: + +```sh +PATH="$PWD/target/qemu-k230/bin:$PATH" \ +cargo xtask starry test qemu --test-group k230-qemu --arch riscv64 -c kpu-smoke +``` diff --git a/test-suit/starryos/k230-qemu/qemu-k230/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/k230-qemu/qemu-k230/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..620d961dfd --- /dev/null +++ b/test-suit/starryos/k230-qemu/qemu-k230/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,7 @@ +env = {} +features = [ + "k230", +] +log = "Info" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/CMakeLists.txt b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/CMakeLists.txt new file mode 100644 index 0000000000..f8aa1b18eb --- /dev/null +++ b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(k230-kpu-smoke C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(k230-kpu-smoke src/main.c) +target_compile_options(k230-kpu-smoke PRIVATE -Wall -Wextra -Werror) +install(TARGETS k230-kpu-smoke RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/src/main.c b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/src/main.c new file mode 100644 index 0000000000..af99b8d9dd --- /dev/null +++ b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/c/src/main.c @@ -0,0 +1,134 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define KPU_IOC_GET_STATUS 0x4b00 +#define KPU_IOC_CLEAR 0x4b01 + +#define KPU_MMAP_CFG_OFFSET 0 +#define KPU_MMAP_L2_OFFSET 0x1000 +#define KPU_STATUS_LO 0x130 +#define KPU_STATUS_HI 0x134 + +static int fail_errno(const char *what) +{ + printf("KPU_SMOKE_FAIL: %s: %s\n", what, strerror(errno)); + return 1; +} + +static int fail_msg(const char *what) +{ + printf("KPU_SMOKE_FAIL: %s\n", what); + return 1; +} + +static int check_device_node(const char *path) +{ + int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + return fail_errno(path); + } + close(fd); + printf("KPU_SMOKE: opened %s\n", path); + return 0; +} + +static int check_pread_reg(int fd) +{ + uint32_t value = 0; + ssize_t nread = pread(fd, &value, sizeof(value), 0); + if (nread < 0) { + return fail_errno("pread /dev/kpu"); + } + if (nread != (ssize_t)sizeof(value)) { + return fail_msg("pread /dev/kpu returned a short register value"); + } + printf("KPU_SMOKE: reg0=0x%08x\n", value); + return 0; +} + +static int check_status_ioctl(int fd) +{ + uint64_t status = 0; + if (ioctl(fd, KPU_IOC_GET_STATUS, &status) != 0) { + return fail_errno("KPU_IOC_GET_STATUS"); + } + printf("KPU_SMOKE: status=0x%016llx\n", (unsigned long long)status); + + if (ioctl(fd, KPU_IOC_CLEAR, 0) != 0) { + return fail_errno("KPU_IOC_CLEAR"); + } + printf("KPU_SMOKE: clear_done ok\n"); + return 0; +} + +static int check_cfg_mmap(int fd) +{ + volatile uint32_t *cfg = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + KPU_MMAP_CFG_OFFSET); + if (cfg == MAP_FAILED) { + return fail_errno("mmap KPU CFG"); + } + + uint64_t status = ((uint64_t)cfg[KPU_STATUS_HI / sizeof(uint32_t)] << 32) | + cfg[KPU_STATUS_LO / sizeof(uint32_t)]; + printf("KPU_SMOKE: mmap_status=0x%016llx\n", (unsigned long long)status); + + if (munmap((void *)cfg, 4096) != 0) { + return fail_errno("munmap KPU CFG"); + } + return 0; +} + +static int check_l2_mmap(int fd) +{ + volatile uint32_t *l2 = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + KPU_MMAP_L2_OFFSET); + if (l2 == MAP_FAILED) { + return fail_errno("mmap KPU L2"); + } + + const uint32_t marker = 0x4b505532; + l2[0] = marker; + if (l2[0] != marker) { + munmap((void *)l2, 4096); + return fail_msg("KPU L2 mmap readback mismatch"); + } + printf("KPU_SMOKE: l2_mmap_rw=0x%08x\n", marker); + + if (munmap((void *)l2, 4096) != 0) { + return fail_errno("munmap KPU L2"); + } + return 0; +} + +int main(void) +{ + if (check_device_node("/dev/kpu") != 0 || check_device_node("/dev/kpu0") != 0) { + return 1; + } + + int fd = open("/dev/kpu", O_RDWR | O_CLOEXEC); + if (fd < 0) { + return fail_errno("open /dev/kpu"); + } + + int failed = check_pread_reg(fd) != 0 || check_status_ioctl(fd) != 0 || + check_cfg_mmap(fd) != 0 || check_l2_mmap(fd) != 0; + close(fd); + if (failed) { + return 1; + } + + printf("KPU_SMOKE_PASS\n"); + return 0; +} diff --git a/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/qemu-riscv64.toml b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/qemu-riscv64.toml new file mode 100644 index 0000000000..0ae6707900 --- /dev/null +++ b/test-suit/starryos/k230-qemu/qemu-k230/kpu-smoke/qemu-riscv64.toml @@ -0,0 +1,25 @@ +args = [ + "-L", + "${workspace}/target/qemu-k230/pc-bios", + "-machine", + "k230", + "-smp", + "1", + "-m", + "2G", + "-nographic", + "-dtb", + "${workspace}/os/StarryOS/configs/board/k230-canmv.dtb", + "-drive", + "if=sd,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/k230-kpu-smoke" +success_regex = ["(?m)^KPU_SMOKE_PASS$"] +fail_regex = [ + "(?i)\\bpanic(?:ked)?\\b", + "(?m)^KPU_SMOKE_FAIL:", +] +timeout = 180