第三章:开发环境搭建

好,咱们正式开始动手了。这一章我带你搭好开发环境。说实话,很多初学者卡在第一步——环境没配好,后面全是坑。我自己带过不少新人,发现环境问题能占掉调试时间的 30%。所以这一章,咱们一次搞定。

3.1 Linux 驱动开发环境配置

做存储芯片驱动,首选 Linux。为什么?因为 Linux 内核本身就是为嵌入式设计的,驱动框架成熟,工具链齐全。我个人习惯用 Ubuntu 20.04 LTS,稳定,社区支持好。

你需要准备一台 Linux 主机,或者虚拟机也行。我建议直接装双系统,虚拟机跑编译会慢一些。嗯,这里要注意:千万别用 WSL1,它缺少很多内核头文件,后面编译驱动时会报一堆莫名其妙的错。

核心依赖包安装:

sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install linux-headers-$(uname -r)
sudo apt-get install git fakeroot ncurses-dev xz-utils libssl-dev bc flex bison

为什么要装 linux-headers?因为驱动编译需要内核的构建系统。说白了,它提供了 Kbuild 框架和一堆宏定义。我曾经遇到过一位同事,装完系统直接编译驱动,结果报 Cannot use CONFIG_CC_STACKPROTECTOR_STRONG,就是缺了内核头文件。

小技巧: 装完后运行 ls /lib/modules/$(uname -r)/build,如果能看到一个软链接指向内核源码目录,说明环境就绪了。

3.2 交叉编译工具链安装

你想想看,我们的存储芯片通常跑在 ARM 或 RISC-V 架构上,而你的开发机是 x86。这就需要一个「翻译官」——交叉编译工具链。

我个人推荐用 Linaro 的 GCC 工具链,或者 ARM 官方的 gcc-arm-none-eabi。对于存储芯片,我常用的是 aarch64-linux-gnu- 系列,因为很多主控芯片是 64 位 ARM Cortex-A 系列。

安装步骤:

# 下载 ARM64 工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz

# 解压到 /opt
sudo tar -xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/

# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin' >> ~/.bashrc
source ~/.bashrc

# 验证安装
aarch64-none-linux-gnu-gcc --version

这里有个坑:工具链版本要和内核版本匹配。比如你编译 Linux 5.10 内核,最好用 GCC 10.x 系列。版本差太多,编译出来的驱动可能无法加载。我曾经用 GCC 9 编译 5.15 内核的驱动,结果 insmod 时报 Unknown symbol,查了半天才发现是 ABI 不兼容。

避坑指南: 不要用系统自带的 gcc 去编译嵌入式驱动。交叉编译工具链的前缀一定要写对,比如 aarch64-none-linux-gnu-gccarm-linux-gnueabihf-gcc 是两回事,前者是 64 位,后者是 32 位。

3.3 QEMU 模拟器与开发板选择

做驱动开发,你不可能每次都往真板子上烧写。调试一次几分钟,效率太低。所以我们需要 QEMU 模拟器。

QEMU 能模拟完整的 ARM 或 RISC-V 开发板,包括中断控制器、定时器、甚至 NAND Flash 控制器。我刚开始做存储驱动时,就是在 QEMU 上把 NAND 驱动调通了,才敢往真板子上烧。

安装 QEMU:

sudo apt-get install qemu-system-arm qemu-system-aarch64

然后你需要一个设备树文件(.dtb)和内核镜像(zImage)。我一般用 vexpress-a9 板型来模拟,它支持 4 个 Cortex-A9 核心,足够跑存储驱动测试。

# 启动 QEMU 模拟 vexpress-a9 开发板
qemu-system-arm -M vexpress-a9 -kernel zImage -dtb vexpress-v2p-ca9.dtb -nographic -append "console=ttyAMA0"

至于真开发板的选择,我建议从 STM32MP157Raspberry Pi 3B+ 入手。为什么?

开发板 架构 存储接口 适合场景
STM32MP157 ARM Cortex-A7 SDIO、NAND、QSPI 入门级存储驱动
Raspberry Pi 3B+ ARM Cortex-A53 SDHCI、USB SD/eMMC 驱动开发
Allwinner V3s ARM Cortex-A7 NAND、SPI NAND NAND Flash 驱动

我个人推荐先玩 QEMU,把驱动框架跑通,再上真板。因为 QEMU 可以打断点、单步调试,真板一旦死机就只能按复位键。嗯,这里有个经验:QEMU 上调试好的驱动,上真板后至少还要改 20% 的代码,因为时序和硬件寄存器细节不同。

小技巧: 在 QEMU 上调试存储驱动时,可以用 -drive file=flash.img,if=pflash,format=raw 参数挂载一个模拟的 Flash 镜像,这样就能测试读写擦除了。

3.4 环境验证:写一个最小驱动

环境搭好了,咱们写个最简单的驱动验证一下。这个驱动什么都不做,只打印一条消息。

// hello_drv.c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Storage Driver World!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Storage Driver!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

Makefile 怎么写?注意,交叉编译时要用 CROSS_COMPILE 指定工具链前缀。

obj-m := hello_drv.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

如果是交叉编译,改成这样:

ARCH := arm64
CROSS_COMPILE := aarch64-none-linux-gnu-
KDIR := /path/to/kernel/source

编译后得到 hello_drv.ko,用 insmod 加载,dmesg 查看输出。如果看到 "Hello, Storage Driver World!",恭喜你,环境搭建成功!

避坑指南: 如果 insmodInvalid module format,八成是内核版本不匹配。检查一下 modinfo hello_drv.ko 显示的 vermagic 是否和当前内核一致。

好,环境搭建就到这里。下一章咱们开始写真正的存储驱动——先从一个简单的字符设备驱动入手,把读写接口打通。你准备好了吗?