第二章 驱动开发环境搭建:交叉编译工具链安装、Linux内核源码获取、设备树(DTS)基础、TFTP/NFS网络启动环境配置
说实话,环境搭建这一步,看着琐碎,但真能卡住不少人。我见过太多新手,代码写得挺溜,结果卡在编译不过、板子起不来这种基础问题上。咱们今天就把这些坑一个个填平。
2.1 交叉编译工具链安装
嵌入式开发嘛,目标芯片性能有限,咱们得在PC上编译好,再把二进制丢到板子上跑。这个在PC上编译、在ARM上运行的编译器,就叫交叉编译工具链。
为什么不用PC自带的gcc? 你想想看,PC的gcc编译出来的是x86指令,ARM芯片根本看不懂。所以必须用专门为ARM定制的gcc。
2.1.1 获取工具链
我个人习惯用Linaro提供的工具链,稳定且社区活跃。以ARM Cortex-A系列为例:
# 下载ARM GCC工具链(以gcc-arm-9.2-2019.12为例)
wget https://releases.linaro.org/components/toolchain/binaries/9.2-2019.12/arm-linux-gnueabihf/gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf.tar.xz
# 解压到指定目录
tar -xvf gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf.tar.xz -C /opt/
# 添加环境变量
export PATH=/opt/gcc-arm-9.2-2019.12-x86_64-arm-linux-gnueabihf/bin:$PATH
2.1.2 验证安装
装完得验证一下,别白忙活:
arm-linux-gnueabihf-gcc --version
如果看到版本号输出,恭喜你,第一步成了。写个简单的hello world试试:
# 编译测试
arm-linux-gnueabihf-gcc -o hello hello.c
# 查看文件类型
file hello
# 输出:hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)
看到ARM字样,说明确实是交叉编译出来的。
2.2 Linux内核源码获取
驱动开发离不开内核源码。你想想看,驱动本质上就是内核的一个模块,不了解内核结构怎么行?
2.2.1 从官方仓库克隆
# 克隆主线内核(以5.10 LTS为例)
git clone --depth 1 -b v5.10 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 或者只克隆最新版本,省空间
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
为什么用--depth 1? 完整的内核仓库有好几个G,咱们做驱动开发一般不需要看历史提交,只取最新代码就够了。省时间也省硬盘。
2.2.2 芯片厂商提供的内核
实际项目中,我建议直接用芯片厂商(如NXP、TI、Rockchip)提供的BSP内核。这些内核已经针对特定芯片做了适配,省去很多移植工作。
# 以NXP i.MX系列为例
git clone https://source.codeaurora.org/external/imx/linux-imx -b imx_5.10.52_2.1.0
2.3 设备树(DTS)基础
设备树,说白了就是描述硬件信息的配置文件。以前的内核用大量C代码描述硬件,改个引脚都要重新编译内核。有了设备树,改硬件配置就像改文本文件一样简单。
2.3.1 设备树文件结构
一个典型的设备树文件长这样:
/dts-v1/;
/ {
model = "My AI Board";
compatible = "mycompany,myboard";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB内存
};
soc {
uart1: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
i2c1: i2c@10020000 {
compatible = "mycompany,i2c";
reg = <0x10020000 0x1000>;
interrupts = <0 34 4>;
#address-cells = <1>;
#size-cells = <0>;
ai_accelerator: ai-chip@10 {
compatible = "mycompany,ai-chip";
reg = <0x10>;
interrupt-parent = <&gpio1>;
interrupts = <5 2>;
};
};
};
};
关键点解释:
compatible:驱动匹配的关键字,驱动通过它找到对应的硬件reg:寄存器地址和大小interrupts:中断号、触发方式&uart1:引用其他节点,类似C语言的指针
2.3.2 编译设备树
设备树源文件(.dts)需要编译成二进制(.dtb)才能被内核使用:
# 编译设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译(查看二进制内容)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
ls /dev看设备节点有没有生成,或者用cat /proc/device-tree/查看内核实际解析的设备树。
2.4 TFTP/NFS网络启动环境配置
开发阶段,总不能每次改个驱动就烧写一次Flash吧?太慢了。网络启动可以让你从PC加载内核和文件系统,改完直接重启板子就能看到效果。
2.4.1 TFTP服务器配置(传输内核和设备树)
TFTP用于从PC下载内核镜像和设备树到板子内存:
# 安装TFTP服务器(Ubuntu)
sudo apt-get install tftpd-hpa
# 配置TFTP目录
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
# 编辑配置文件 /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure"
# 重启服务
sudo systemctl restart tftpd-hpa
把内核镜像zImage和设备树myboard.dtb放到/srv/tftp目录:
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/myboard.dtb /srv/tftp/
2.4.2 NFS服务器配置(共享根文件系统)
NFS让板子通过网络挂载PC上的文件系统,这样修改文件系统内容也不用重新烧写:
# 安装NFS服务器
sudo apt-get install nfs-kernel-server
# 创建根文件系统目录
sudo mkdir -p /srv/nfs/rootfs
# 解压根文件系统
sudo tar -xvf rootfs.tar.gz -C /srv/nfs/rootfs/
# 编辑 /etc/exports,添加共享目录
/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
# 重启NFS服务
sudo systemctl restart nfs-kernel-server
no_root_squash这个选项很重要。如果不加,板子上的root用户会被映射成nobody,导致很多操作权限不足。我曾经因为这个排查了半天,最后发现是权限问题。
2.4.3 U-Boot启动参数设置
在板子的U-Boot命令行中设置启动参数:
# 设置IP地址(根据实际网络修改)
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
# 设置启动参数
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.10:/srv/nfs/rootfs rw ip=192.168.1.100:192.168.1.10:192.168.1.1:255.255.255.0::eth0:off'
# 从TFTP加载内核和设备树
tftp 0x42000000 zImage
tftp 0x43000000 myboard.dtb
# 启动内核
bootz 0x42000000 - 0x43000000
参数说明:
root=/dev/nfs:告诉内核从NFS挂载根文件系统nfsroot=...:NFS服务器地址和路径ip=...:板子IP、服务器IP、网关、子网掩码
ping 192.168.1.10,通了再继续。
2.5 环境验证
全部配置完成后,重启板子。如果看到内核启动日志,最后出现类似这样的提示:
VFS: Mounted root (nfs filesystem) on device 0:13
Freeing unused kernel memory: 1024K
Run /sbin/init as init process
恭喜你,环境搭建成功了!现在你可以愉快地修改驱动、重新编译、重启板子,整个过程不超过1分钟。
嗯,环境搭建这部分确实繁琐,但这是后面所有工作的基础。我建议你按照这个流程走一遍,遇到问题别慌,多半是网络配置或者路径写错了。下一章咱们开始写第一个真正的AI芯片驱动。