引言:为什么 RISC-V 正在重塑芯片世界
2025 年,RISC-V 已从学术界的实验品蜕变为产业界的核心力量。SiFive、StarFive、Alibaba XuanTie(平头哥 C910)、Ventana Micro 等公司的高性能 RISC-V SoC 已进入数据中心、AI 推理、工业控制和消费电子领域。RISC-V 基金会拥有超过 4000 家会员,预计到 2028 年 RISC-V 芯片出货量将突破 160 亿颗。与 ARM 和 x86 不同,RISC-V 的开放 ISA(指令集架构)允许任何人免费设计、制造和销售 RISC-V 芯片,这使得 Linux 内核移植到新的 RISC-V 板卡成为越来越多工程师需要掌握的核心技能。
一、RISC-V ISA 特权架构:移植前的理论准备
1.1 特权级别与 CSR
RISC-V 定义了三个特权级别,这对于 Linux 内核移植至关重要:
| 特权级别 | 缩写 | 用途 | 运行内容 |
|---|---|---|---|
| User Mode | U-mode | 用户态应用 | 应用程序、库(libc、libc++) |
| Supervisor Mode | S-mode | 操作系统内核 | Linux 内核(内核态)、U-Boot 部分阶段 |
| Machine Mode | M-mode | 固件/BIOS | OpenSBI、M-mode 固件、 BootROM |
CSR(Control and Status Registers)是 RISC-V 特权架构的核心,移植内核时必须理解和配置的关键 CSR:
// 关键 CSR 列表(Linux 内核移植必修)
// ----- M-mode CSR -----
mstatus // 机器状态寄存器(全局中断使能)
mtvec // Machine Trap Vector(异常/中断入口地址)
mepc // Machine Exception PC(异常返回地址)
mcause // Machine Trap Cause(异常原因编码)
mie // Machine Interrupt Enable(中断使能掩码)
mscratch // Machine Scratch(临时存储,OpenSBI 使用)
// ----- S-mode CSR -----
sstatus // Supervisor 状态寄存器(含 SIE 中断使能位)
stvec // Supervisor Trap Vector(S-mode 异常入口)
sepc // Supervisor Exception PC
scause // Supervisor Trap Cause
sie // Supervisor Interrupt Enable
satp // Supervisor Address Translation and Protection(页表基址)
sscratch // Supervisor Scratch(内核 per-CPU 指针常用)
1.2 RISC-V 异常与中断委托
RISC-V 默认所有陷阱(异常+中断)都在 M-mode 处理。通过 medeleg(Machine Exception Delegation)和 mideleg(Machine Interrupt Delegation)CSR,可以将 S-mode 和 U-mode 的陷阱委托给 S-mode:
// OpenSBI 启动时执行的委托设置(M → S 委托)
// 委托给 S-mode 的异常类型:
// - Instruction address misaligned
// - Illegal instruction
// - Environment call from U-mode/S-mode
// - Store/AMO/Load page fault
// - Instruction page fault
// 委托给 S-mode 的中断类型:
// - Software interrupt (Supervisor)
// - Timer interrupt (Supervisor)
// - External interrupt (Supervisor)
// Linux 内核期望在 S-mode 处理陷阱,因此 M-mode 固件
// (OpenSBI)必须正确设置委托寄存器
1.3 RISC-V 页表格式(Sv39/Sv48)
| 模式 | 虚拟地址空间 | 页表级数 | 最大物理地址 | Linux 适用场景 |
|---|---|---|---|---|
| Sv32 | 32-bit (4GB) | 2 | 34-bit | 嵌入式 MCU |
| Sv39 | 39-bit (512GB) | 3 | 56-bit (256TB) | 主流 64 位 RISC-V SoC |
| Sv48 | 48-bit (256TB) | 4 | 56-bit | 高性能服务器 |
Sv39 三级页表结构:VPN[2](L2,9bit)→ VPN[1](L1,9bit)→ VPN[0](L0,9bit)→ PPN(物理页号)+ Offset(12bit)。每个页表项(PTE)64 位,包含:Valid、Read/Write/Execute 权限位、User/Super 访问权限、Global、Accessed、Dirty 标志。
二、OpenSBI 固件搭建:RISC-V 的 "BIOS"
2.1 OpenSBI 的角色定位
OpenSBI 是 RISC-V 生态的标准 M-mode 运行时固件,等价于 x86 的 BIOS/UEFI、ARM 的 ATF(ARM Trusted Firmware)。它提供三大核心功能:
- M-mode 运行时:初始化 M-mode CSR、设置中断委托、配置 PMP(Physical Memory Protection)
- SBI 接口:为 S-mode 内核提供标准化 Ecalls(如定时器设置、IPI 发送、控制台 I/O)
- Payload 加载:加载并跳转到 U-Boot 或 Linux 内核(跳板模式)
2.2 编译 OpenSBI
# 安装 RISC-V 工具链
sudo apt install gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
# 下载 OpenSBI 源码
git clone https://github.com/riscv-software-src/opensbi.git
cd opensbi && git checkout v1.4
# 编译 generic 平台固件(适配大部分 SoC)
make CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic \
FW_PAYLOAD_PATH=/path/to/u-boot/spl/u-boot-spl.bin \
FW_PAYLOAD_FDT_PATH=/path/to/my-board.dtb
# 输出: build/platform/generic/firmware/fw_payload.bin
// 这是烧写到 Flash 或加载到内存的固件二进制
2.3 PMP(Physical Memory Protection)配置
PMP 是 RISC-V 特有的内存保护机制,M-mode 通过 PMP 寄存器限制 S/U-mode 对物理内存的访问:
// PMP 配置示例:将 0x80000000-0x8FFFFFFF 全部开放给 S-mode
// 使用 TOR (Top of Range) 模式
// pmpaddr0 = 0x8FFFFFFF >> 2 (TOR模式下地址为包含边界)
// pmpcfg0 = 0x1F (R=1, W=1, X=1, A=TOR, L=0)
// OpenSBI 内部执行(移植时通常不需要手动修改):
void sbi_hart_pmp_configure(struct sbi_scratch *scratch)
{
pmp_set(0, PMP_R | PMP_W | PMP_X | PMP_TOR,
memregion_get_sz(&scratch->fw_region), -1U);
}
// 注意: L=1 表示 Locked,运行时不可修改
// Linux 内核启动后需要 PMP 开放全部物理内存
三、设备树(DTS):描述硬件的语言
3.1 DTS 核心结构
设备树是 RISC-V Linux 移植中最关键的输入文件。与 x86(ACPI 自动发现)和早期 ARM(大量硬编码平台数据)不同,RISC-V 完全依赖设备树描述硬件。一旦 DTS 出错,内核可能无法启动或关键外设失灵。
// 示例:自定义 RISC-V 板卡的 DTS 骨架(简化版)
/dts-v1/;
/ {
compatible = "myvendor,myboard";
model = "My RISC-V Development Board";
#address-cells = <2>;
#size-cells = <2>;
// CPU 节点(RISC-V 特有格式)
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <24000000>; // 24MHz 时钟源
cpu@0 {
device_type = "cpu";
reg = <0>; // Hart ID 0
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdc_zicsr_zifencei"; // ISA 字符串
mmu-type = "riscv,sv39";
cpu0_intc: interrupt-controller {
#interrupt-cells = <1>;
interrupt-controller;
compatible = "riscv,cpu-intc";
linux,phandle = <0x100>;
phandle = <0x100>;
};
};
// ... cpu@1, cpu@2, cpu@3 ...(多核时逐个描述)
// 共享的 PLIC(Platform-Level Interrupt Controller)
plic@c000000 {
compatible = "riscv,plic0";
reg = <0x0 0xc000000 0x0 0x4000000>;
interrupts-extended = <&cpu0_intc 11 &cpu1_intc 11 ...>;
riscv,ndev = <64>; // 64 个外部中断源
interrupt-controller;
#interrupt-cells = <1>;
};
// APLIC(Advanced PLIC,RISC-V AIA 规范)
// 用于 MSI 中断传递
};
// 内存节点
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>; // 1GB @ 0x80000000
};
// SoC 顶层描述
soc {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges;
// UART 节点 (NS16550A 兼容)
serial@10000000 {
compatible = "ns16550a";
reg = <0x0 0x10000000 0x0 0x100>;
clock-frequency = <24000000>;
interrupt-parent = <&plic>;
interrupts = <1>; // PLIC 中断号 1
reg-shift = <0>;
reg-io-width = <1>;
status = "okay";
};
// 时钟节点
clint@2000000 {
compatible = "riscv,clint0";
reg = <0x0 0x2000000 0x0 0x10000>;
interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7>;
// Software interrupt (3) + Timer interrupt (7)
};
// SPI Flash 节点
spi@10040000 {
compatible = "myvendor,spi";
reg = <0x0 0x10040000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
spi-max-frequency = <25000000>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <25000000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
firmware@0 {
reg = <0x0 0x100000>; // 640KB OpenSBI+U-Boot
label = "firmware";
};
dtb@100000 {
reg = <0x100000 0x200000>; // 2MB 设备树 + kernel
label = "kernel";
};
};
};
};
};
// chosen 节点:内核命令行参数
chosen {
bootargs = "console=ttyS0,115200 earlycon=sbi root=/dev/mmcblk0p2 rw rootwait";
stdout-path = "serial@10000000:115200n8";
};
};
3.2 RISC-V ISA 字符串的重要性
riscv,isa 属性是 DTS 中最关键的信息,内核在启动时通过它判断硬件支持哪些 ISA 扩展:
// ISA 字符串含义解析
"rv64imafdc_zicsr_zifencei"
│ │││││ │ │
│ │││││ │ └─ Zifencei: FENCE.I 指令支持(指令流同步)
│ │││││ └──────── Zicsr: CSR 指令支持(特权级访问必需)
│ ││││└─────────── C: 压缩指令集(16-bit 指令)
│ │││└──────────── D: 双精度浮点
│ ││└───────────── F: 单精度浮点
│ │└────────────── A: 原子指令(lr/sc, amo)
│ └─────────────── M: 乘除法
└─────────────────── rv64: 64 位基址 ISA
// Linux 内核 6.x 自动检测的 ISA 扩展
// - Svnapot: 支持大页 TLB 合并
│ - Svpbmt: 基于页面的内存类型(NC/IO)
│ - Zbc: 进位-less 乘法
│ - Smaia/Sstc: AIA 中断架构 + stimecmp
四、Linux 内核配置与编译
4.1 defconfig 配置选项
# 1. 下载 Linux 内核源码(6.x 主线已完整支持 RISC-V)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux && git checkout v6.10
# 2. 编译内核镜像 + 模块 + DTB
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig
# 关键配置项 (arch/riscv/Kconfig):
CONFIG_ARCH_RISCV=y
CONFIG_RISCV_SBI=y # SBI 支持(含 OpenSBI 接口)
CONFIG_MMU=y # MMU 必需(除嵌入式 nommu 模式)
CONFIG_RISCV_ISA_C=y # 压缩指令集(节省代码体积 30%)
CONFIG_RISCV_ISA_F=y # 单精度浮点
CONFIG_RISCV_ISA_D=y # 双精度浮点
CONFIG_64BIT=y # 64 位模式(默认=y 在 riscv64_defconfig)
CONFIG_SMP=y # 多核对称处理
CONFIG_SBI_V01=n # 禁用旧版 SBI(使用新版 SBI HSM)
# 设备驱动配置(根据实际硬件选择):
CONFIG_SERIAL_OF_PLATFORM=y # 设备树描述的串口驱动
CONFIG_SPI=y
CONFIG_MTD_SPI_NOR=y # SPI NOR Flash
CONFIG_MMC=y
CONFIG_MMC_SPI=y # SD 卡 over SPI
CONFIG_MMC_SDHCI=y # SD Host Controller
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_RTC_CLASS=y
CONFIG_RTC_DRV_DS1307=y # I2C RTC
# 网络(如果 SoC 含 MAC):
CONFIG_NET_VENDOR_RENESAS=y
CONFIG_ETHOC=y # OpenCore Ethernet MAC
# 编译
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc) Image.gz dtbs modules
# 输出文件:
# arch/riscv/boot/Image.gz # 压缩的内核镜像
# arch/riscv/boot/dts/<vendor>/<board>.dtb # 编译后的设备树
4.2 内核启动流程(RISC-V 特有路径)
// RISC-V Linux 启动时序
//
// BootROM (M-mode, 芯片内部)
// ↓ 加载 Flash 头部 OpenSBI
// OpenSBI (M-mode)
// ↓ 初始化硬件、设置 mideleg/medeleg
// ↓ 跳转到 S-mode (U-Boot 或 Linux entry)
// U-Boot SPL (S-mode)
// ↓ 初始化 DDR、加载完整 U-Boot
// U-Boot Proper (S-mode)
// ↓ 加载 kernel image + DTB 到内存
// ↓ 调用 smp 设置辅助核启动地址
// ↓ 跳转到 Linux 入口 _start
//
// Linux 内核入口 (_start, arch/riscv/kernel/head.S):
// 1. 关闭中断(清除 SIE)
// 2. 识别启动 Hart ID(通过 a0 寄存器传入)
// 3. 加载 DTB 地址(通过 a1 寄存器传入)
// 4. 查找 boot hart(指定 Hart ID 或 fdt 中 first-available)
// 5. 设置初始页表(early_pg_dir,identity mapping)
// 6. 使能 MMU(写入 satp 寄存器)
// 7. 跳转到 C 代码 start_kernel()
// head.S 关键汇编片段(RISC-V 特有)
ENTRY(_start)
// a0 = Hart ID, a1 = DTB 物理地址
csrw sie, zero // 关闭 S-mode 全部中断
csrr a2, CSR_MHARTID // 读取 M-mode Hart ID
LA t0, hart_lottery
li(t1, 1)
amoadd.w t0, t1, (t0) // 原子锁,仅 0 号 Hart 进入
bnez t0, .Lsecondary_start // 非零(辅助核早期等待)
// 设置 boot Hart 的页表
LA sp, boot_cpu_stack + INIT_TASK_STACK_SIZE
call setup_vm // setup early PTEs
la(satp, early_pg_dir)
call relocate_enable_mmu // 使能 MMU 并跳转到高地址
tail start_kernel
ENDPROC(_start)
五、驱动移植实战:以 SPI 控制器为例
5.1 编写平台设备驱动框架
// 示例:自定义 RISC-V SoC 的 SPI 控制器驱动
// drivers/spi/spi-myvendor.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
struct myvendor_spi {
void __iomem *regs;
struct clk *clk;
unsigned int freq;
struct completion xfer_done;
};
#define SPI_CTRL 0x00
#define SPI_STATUS 0x04
#define SPI_TXDATA 0x08
#define SPI_RXDATA 0x0C
#define SPI_INTEN 0x10
#define SPI_CLKDIV 0x14
static irqreturn_t myvendor_spi_irq(int irq, void *dev_id)
{
struct spi_master *master = dev_id;
struct myvendor_spi *ms = spi_master_get_devdata(master);
u32 status = readl(ms->regs + SPI_STATUS);
if (status & 0x01) {
// TX 完成
writel(0, ms->regs + SPI_INTEN);
complete(&ms->xfer_done);
}
return IRQ_HANDLED;
}
static int myvendor_spi_transfer_one(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
{
struct myvendor_spi *ms = spi_master_get_devdata(master);
u8 *tx_buf = xfer->tx_buf;
u8 *rx_buf = xfer->rx_buf;
unsigned int len = xfer->len;
int i;
reinit_completion(&ms->xfer_done);
writel(0x01, ms->regs + SPI_INTEN); // 使能 TX 中断
for (i = 0; i < len; i++) {
writel(tx_buf ? tx_buf[i] : 0xFF, ms->regs + SPI_TXDATA);
wait_for_completion(&ms->xfer_done);
if (rx_buf)
rx_buf[i] = readl(ms->regs + SPI_RXDATA);
reinit_completion(&ms->xfer_done);
}
return 0;
}
static int myvendor_spi_probe(struct platform_device *pdev)
{
struct myvendor_spi *ms;
struct spi_master *master;
struct resource *res;
int irq, ret;
master = spi_alloc_master(&pdev->dev, sizeof(*ms));
if (!master)
return -ENOMEM;
ms = spi_master_get_devdata(master);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ms->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ms->regs))
return PTR_ERR(ms->regs);
ms->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ms->clk))
return PTR_ERR(ms->clk);
clk_prepare_enable(ms->clk);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, myvendor_spi_irq, 0,
dev_name(&pdev->dev), master);
if (ret)
return ret;
master->bus_num = pdev->id;
master->num_chipselect = 1;
master->mode_bits = SPI_CPOL | SPI_CPHA;
master->transfer_one = myvendor_spi_transfer_one;
master->dev.of_node = pdev->dev.of_node;
init_completion(&ms->xfer_done);
platform_set_drvdata(pdev, ms);
return spi_register_master(master);
}
static const struct of_device_id myvendor_spi_of_match[] = {
{ .compatible = "myvendor,spi", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myvendor_spi_of_match);
static struct platform_driver myvendor_spi_driver = {
.probe = myvendor_spi_probe,
.remove = myvendor_spi_remove,
.driver = {
.name = "myvendor-spi",
.of_match_table = myvendor_spi_of_match,
},
};
module_platform_driver(myvendor_spi_driver);
MODULE_LICENSE("GPL");
5.2 对应的 DTS 节点
// 在 SoC 的 DTSI(include 文件)中添加:
spi@10040000 {
compatible = "myvendor,spi";
reg = <0x0 0x10040000 0x0 0x1000>;
interrupts = <34>;
interrupt-parent = <&plic>;
clocks = <&clk_core>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
// 在板级 DTS 中添加从设备:
&spi0 {
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <25000000>;
};
};
六、双平台实战:QEMU 与 VisionFive 2
6.1 QEMU 快速验证
# QEMU 6.2+ 已内置 virt 机器(RISC-V)
qemu-system-riscv64 \
-machine virt \
-cpu rv64 \
-m 512M \
-bios opensbi/generic/fw_jump.bin \
-kernel arch/riscv/boot/Image.gz \
-dtb myboard.dtb \
-append "console=ttyS0,115200 earlycon=sbi root=/dev/vda rw" \
-drive file=rootfs.ext4,format=raw,if=virtio \
-netdev user,id=mynet \
-device virtio-net-device,netdev=mynet \
-nographic
# 预期输出(内核启动日志)
// [ 0.000000] Linux version 6.10.0-rc1 (user@host) (riscv64-linux-gnu-gcc ...)
// [ 0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
// [ 0.000000] Machine model: riscv-virtio,qemu
// [ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '')
// [ 0.000000] printk: legacy console [sbi0] enabled
// [ 0.000000] Zone ranges:
// [ 0.000000] DMA32 [mem 0x0000000080200000-0x000000009fffffff]
// [ 0.000000] Normal empty
// [ 0.000000] Built 1 zonelists, mobility grouping on.
// [ 0.000000] Kernel command line: console=ttyS0 root=/dev/vda rw
// [ 0.000000] Dentry cache hash table entries: 131072
// [ 0.000000] Mount-cache hash table entries: 2048
// [ 0.000000] Mountpoint-cache hash table entries: 2048
// [ 0.000000] smp: Bringing up secondary CPUs ...
// [ 0.000000] smp: Brought up 1 node, 4 CPUs
// [ 1.234566] Freeing unused kernel image (initmem) memory: 3968K
// [ 1.456789] Run /sbin/init as init process
// # 恭喜!你已经进入了 RISC-V Linux Shell
6.2 StarFive VisionFive 2 真机移植
// VisionFive 2 使用 JH7110 SoC(4× U74 @ 1.5GHz + 1× S7 monitor core)
// 步骤 1: 获取 U-Boot 并编译 SPL
git clone https://github.com/visionfive2/u-boot.git
cd u-boot
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- starfive_visionfive2_defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)
// 步骤 2: 编译 OpenSBI(payload 模式)
cd opensbi
make CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic \
FW_PAYLOAD_PATH=../u-boot/spl/u-boot-spl.bin \
FW_PAYLOAD_FDT_PATH=../u-boot/arch/riscv/dts/starfive_visionfive2.dtb
// 步骤 3: 编译 Linux 内核
cd linux
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- starfive_visionfive2_defconfig
// 额外配置:启用 VisionFive 2 特定驱动
make ARCH=riscv menuconfig
// Device Drivers →
// [*] Network device support → Ethernet driver support →
// StarFive devices → <*> StarFive dwmac support
// [*] I2C support → <*> Synopsys DesignWare I2C adapter
// [*] MMC/SD/SDIO card support → <*> SDHCI platform and OF driver helper
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc) Image.gz modules dtbs
// 步骤 4: 构建根文件系统 (Debian RISC-V64)
sudo debootstrap --arch=riscv64 --foreign bookworm /tmp/riscv-rootfs
sudo cp /usr/bin/qemu-riscv64-static /tmp/riscv-rootfs/usr/bin/
sudo chroot /tmp/riscv-rootfs /debootstrap/debootstrap --second-stage
// 安装内核模块
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- \
INSTALL_MOD_PATH=/tmp/riscv-rootfs modules_install
// 步骤 5: 烧写到 SD 卡
sudo dd if=opensbi/build/platform/generic/firmware/fw_payload.bin of=/dev/sdX bs=512 seek=1
sudo dd if=linux/arch/riscv/boot/Image.gz of=/dev/sdX bs=512 seek=2048
sudo mount /dev/sdXp2 /mnt
sudo cp -a /tmp/riscv-rootfs/* /mnt/
七、调试技巧与常见问题
7.1 QEMU GDB 调试内核
# 启动 QEMU 并等待 GDB 连接
qemu-system-riscv64 -machine virt -m 512M \
-bios fw_jump.bin -kernel Image.gz \
-dtb myboard.dtb -append "console=ttyS0 root=/dev/vda rw nokaslr" \
-S -s -nographic
# ^^^ -S: 启动时暂停 -s: 监听 :1234
# 另一个终端:连接 GDB
riscv64-linux-gnu-gdb vmlinux
(gdb) target remote :1234
(gdb) b start_kernel
(gdb) c
// 断点触发!现在可以逐步调试 RISC-V 内核启动
(gdb) info registers
// 查看 RISC-V 32 个通用寄存器 + PC + CSR
(gdb) p/x $satp
// 查看页表基址寄存器
(gdb) monitor info mem # (QEMU monitor) 查看内存映射
7.2 常见启动失败排查
| 症状 | 原因 | 解决方法 |
|---|---|---|
| "Cannot open root device" | 缺少存储控制器驱动或 root= 参数错误 | 检查 DTS 中的 MMC/SPI 节点;确认 root=/dev/mmcblk0p2 |
| 内核 panic "Unable to handle kernel paging fault" | 页表映射缺失或 DTB 地址错误 | 检查 DTB 加载地址是否与 a1 寄存器匹配 |
| 串口无输出 | 串口节点缺失或中断配置错误 | DTS 确认 compatible="ns16550a"; interrupts = <正确号> |
| "SBI HSM hart_start failed" | CPU Hart ID 不匹配或 status="disabled" | 检查 DTS 中 cpu@N 的 status 和 reg 值 |
| PCIe 设备不可见 | 缺少 PCIe 控制器节点或_ranges 错误 | DTS 添加 pcie 节点并正确配置 ranges 和 bus-range |
| 网络不通 | 以太网 MAC 节点缺失或 PHY 复位 GPIO 未定义 | DTS 检查 stmmac/ethernet 节点, phy-handle, reset-gpios |
八、RISC-V 内核新进展(2025-2026)
8.1 RISC-V AIA(Advanced Interrupt Architecture)
RISC-V AIA 规范定义了新一代中断控制器架构,替代传统的 PLIC,支持:MSI(Message Signaled Interrupts)、中断优先级、虚拟中断(VS-level)。Linux 6.10+ 内核已合并完整的 AIA 支持。对于新的 SoC 移植,推荐使用 AIA 而非 PLIC。
8.2 RISC-V IOMMU
RISC-V IOMMU 规范(2025 年发布)定义了与 ARM SMMU 等效的 I/O 内存管理单元,支持设备 DMA 虚拟化、地址隔离。Linux 6.11+ 内核已合并 RISC-V IOMMU 框架,新的 SoC 移植需要实现对应的 IOMMU 驱动。
8.3 KVM RISC-V 虚拟化
Linux 6.8 稳定了 RISC-V KVM 支持,允许在 RISC-V SoC 上运行虚拟机。移植新 SoC 时需要确认 KVM 兼容性(H 扩展:Hypervisor Extension),并通过 DTS 正确声明。
九、移植检查清单
| 阶段 | 检查项 | 验证方法 |
|---|---|---|
| DTS | 所有外设节点已描述 DTS/DTSI | dtc -I dtb -O dts /sys/firmware/fdt > /tmp/live.dts 对比 |
| OpenSBI | 固件版本与内核 SBI 规范兼容 | cat /proc/device-tree/fw-version 或 dmesg | grep SBI |
| 内核配置 | ARCH_RISCV=y, 驱动已选中 | grep CONFIG_ .config | grep -E "RISCV|MMU|SMP" |
| 串口 | 控制台输出正常、中断工作 | 向 /dev/ttyS0 写入数据测试回显 |
| 存储 | eMMC/SD/SPI Flash 可读写 | mkfs.ext4 <device> 挂载读写测试 |
| 网络 | 链路层 UP、IP 通信正常 | ip link set eth0 up; udhcpc -i eth0 |
| 多核 | 所有 Hart 在 S-mode 可调度 | cat /proc/cpuinfo | grep processor → 数量正确 |
| 中断 | PLIC/AIA 正常工作,中断分布均匀 | watch -n1 /proc/interrupts |
十、总结
RISC-V Linux 内核移植是一项涉及 ISA 架构、固件开发、设备树描述、驱动编程和系统调试的综合性工程。与 ARM 和 x86 相比,RISC-V 的开放生态意味着每个 SoC 都有其独特性,没有"万能 BSP"可供套用,工程师需要从 DTS 这一核心入口逐层打通硬件抽象。但正是这种开放性让 RISC-V 成为最适合深入理解计算机体系结构的平台——当你能够从零让 Linux 在一颗全新的 RISC-V SoC 上启动时,你对内核、硬件、系统软件的交互机制将达到一个全新的高度。
关键技术总结:
- ISA 扩展:riscv,isa 字符串是内核识别硬件能力的唯一依据,缺一个 Z 扩展都可能导致启动失败
- OpenSBI:RISC-V 固件标准,提供 SBI 接口和 PMP 配置,内核依赖它完成早期初始化
- 设备树:RISC-V 完全依赖 DTS 描述硬件,精确到 reg、interrupts、clock-frequency 每个字段
- 异常委托:M→S 委托的陷阱类型决定了内核能否在 S-mode 正常处理中断和异常
- 页表格式:Sv39 三级页表是 64 位 RISC-V Linux 的默认选择,head.S 中早期页表设置不正确会导致启动即崩溃
- 多核启动:RISC-V 使用 SBI HSM 接口协调多核启动,spin-table 方式已废弃

发表评论 取消回复