4. RISC-V Linux内核源码结构

在RISC-V平台上做驱动适配,第一步不是写代码,而是看懂Linux内核源码的“地图”。内核源码并不是一堆随意堆放的C文件,它按功能、架构和构建规则被严格组织。理解这套组织方式,才能快速定位RISC-V相关代码、找到驱动子系统入口,并正确配置交叉编译工具链。

一、内核目录组织:从顶层看功能分区

Linux内核源码根目录下,每个一级目录都承担明确职责。对RISC-V驱动适配而言,最常打交道的目录如下:

  • arch/:架构相关代码,RISC-V对应arch/riscv/,包含启动、异常、MMU、设备树、SBI调用等。
  • drivers/:驱动子系统,按总线或功能分类,如drivers/gpio/、drivers/i2c/、drivers/net/。
  • include/:公共头文件,include/linux/为通用接口,include/asm-riscv/链接到架构头文件。
  • kernel/:核心调度、中断、时间、同步等与架构无关的内核逻辑。
  • mm/:内存管理,RISC-V的页表、TLB、内存模型相关代码会与arch/riscv/mm/协作。
  • lib/:通用库函数,如字符串、CRC、位操作等。
  • scripts/:构建脚本、Kconfig解析、设备树编译工具等。

驱动工程师最需要建立的条件反射是:看到外设类型,先想它属于哪个子系统;看到架构差异,先查arch/riscv/;看到编译开关,先找对应Kconfig和Makefile。

二、架构相关代码:RISC-V的“专属层”

arch/riscv/是RISC-V平台适配的核心。它主要包含:

  • boot/:内核启动入口、设备树处理、早期控制台。
  • kernel/:异常向量、SBI调用、CPU热插拔、信号处理。
  • mm/:页表初始化、TLB刷新、内存映射。
  • include/asm/:架构相关宏、寄存器定义、屏障指令。
  • dts/:RISC-V开发板设备树源文件。

驱动适配时,架构层通常不直接改,但必须理解它提供的接口。例如,设备树中的compatible字符串会与驱动匹配;中断号通过irqchip和PLIC映射;DMA操作依赖dma-mapping架构实现。

提示:RISC-V使用SBI作为固件与内核的接口,很多平台相关操作通过ecall进入M模式,驱动中一般不需要直接调用SBI,但排障时要能识别SBI相关日志。

三、驱动子系统:按总线与功能分层

Linux驱动模型围绕总线、设备、驱动三者匹配展开。RISC-V平台常见驱动子系统包括:

子系统目录典型设备关键接口
GPIOdrivers/gpio/GPIO控制器gpiod_get、gpiod_direction_output
I2Cdrivers/i2c/传感器、EEPROMi2c_transfer、i2c_smbus_read_byte
SPIdrivers/spi/Flash、显示屏spi_sync、spi_write
网络drivers/net/以太网MACnetdev_ops、phy_connect
字符设备drivers/char/串口、自定义设备file_operations、cdev_add

每个子系统内部通常分为“核心层”和“控制器驱动”。核心层提供统一API,控制器驱动负责具体硬件。以GPIO为例,gpiolib.c是核心,gpio-riscv.c或厂商GPIO驱动负责注册gpio_chip。

四、编译配置与交叉编译工具链

内核构建由Kconfig和Makefile共同驱动。Kconfig定义配置项,Makefile决定编译哪些文件。RISC-V交叉编译需要指定工具链前缀和架构:

export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-
make defconfig
make menuconfig
make -j$(nproc) Image.gz dtbs modules

常用配置项包括:

  • CONFIG_ARCH_RISCV=y:启用RISC-V架构支持。
  • CONFIG_SOC_VIRT=y或厂商SoC选项:选择具体平台。
  • CONFIG_GPIO_SYSFS=y:启用GPIO用户空间接口,便于调试。
  • CONFIG_I2C_CHARDEV=y:启用I2C字符设备,方便用户态访问。
注意:交叉编译工具链必须与内核版本匹配。使用过新的GCC编译旧内核,可能因插件或头文件变化失败;使用过旧的GCC则可能不支持RISC-V新扩展。

五、案例:为RISC-V虚拟平台添加一个GPIO驱动

假设在QEMU RISC-V virt平台上添加一个简单GPIO控制器驱动。流程如下:

  1. 在drivers/gpio/下新建gpio-demo.c,实现gpio_chip的direction_output和get。
  2. 在drivers/gpio/Kconfig中添加config GPIO_DEMO,依赖ARCH_RISCV。
  3. 在drivers/gpio/Makefile中添加obj-$(CONFIG_GPIO_DEMO) += gpio-demo.o。
  4. 在设备树中增加节点,compatible = "demo,gpio",并指定寄存器基址和中断。
  5. 执行make menuconfig选中GPIO_DEMO,重新编译内核和设备树。

启动后可通过/sys/class/gpio或gpiodetect验证驱动是否加载。若匹配失败,优先检查compatible字符串、Kconfig依赖和模块是否被编译进内核。

六、误区与复盘

常见误区有三个:一是把架构代码和驱动代码混在一起改,导致升级内核时冲突;二是只改Makefile不改Kconfig,配置项无法被选中;三是忽略设备树,驱动写好了但设备没有注册。复盘时应建立“配置—编译—设备树—驱动匹配—用户接口”的完整链路检查表。

RISC-V Linux 内核源码结构与驱动适配链路 arch/riscv/ 启动 / 异常 / MMU SBI / 设备树 / 中断 RISC-V 专属层 drivers/ GPIO / I2C / SPI 网络 / 字符设备 驱动子系统 Kconfig + Makefile 配置项 / 编译规则 模块 / 内建 构建系统 交叉工具链 riscv64-linux-gnu- GCC / binutils glibc / musl 设备树(DTS)描述硬件资源:寄存器基址、中断号、compatible 字符串 驱动通过 of_match_table 与 compatible 匹配,完成 probe 配置阶段 make menuconfig 选中 CONFIG_GPIO_DEMO 生成 .config 检查依赖与架构选项 编译阶段 make Image.gz dtbs modules CROSS_COMPILE 指定前缀 ARCH=riscv 生成 vmlinux / Image / dtb 验证阶段 启动内核,查看 dmesg 检查 /sys/class/gpio gpiodetect / gpioinfo 确认 probe 成功

掌握这张“源码地图”后,RISC-V驱动适配就不再是盲目搜索。先定位子系统,再确认架构接口,接着配置Kconfig和Makefile,最后用设备树完成匹配,整个链路清晰可控。