引言:为什么 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 ModeU-mode用户态应用应用程序、库(libc、libc++)
Supervisor ModeS-mode操作系统内核Linux 内核(内核态)、U-Boot 部分阶段
Machine ModeM-mode固件/BIOSOpenSBI、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 适用场景
Sv3232-bit (4GB)234-bit嵌入式 MCU
Sv3939-bit (512GB)356-bit (256TB)主流 64 位 RISC-V SoC
Sv4848-bit (256TB)456-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/DTSIdtc -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 方式已废弃
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部