4、Linux内核移植:内核配置、设备树修改、驱动编译与加载
好,咱们进入第四章。这一章可以说是整个Hi3516方案落地的核心环节之一。你想想看,芯片选好了,硬件画好了,板子打回来了,然后呢?得让Linux跑起来对吧?内核移植这事儿,说白了就是让操作系统认识你的硬件,并且能管好它。
我个人习惯把内核移植分成三步走:配置、裁剪、适配。配置决定了内核有什么功能,裁剪决定了内核有多胖,适配决定了硬件能不能正常工作。咱们一个一个来聊。
4.1 内核配置——别贪多,够用就行
拿到海思官方的SDK之后,里面通常会有一个针对Hi3516的默认配置文件。比如 hi3516dv300_defconfig 或者类似的。我建议你先用这个默认配置编译一遍,确保工具链和环境没问题。
内核配置的命令很简单:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
这里要注意几个关键点:
- CPU类型:选Cortex-A7,别选错了。我在项目中遇到过有人选了A9,编译倒是过了,跑起来直接panic。
- 内存管理:Hi3516一般512MB或1GB DDR,开启CONFIG_HIGHMEM如果内存超过1GB。
- 文件系统:建议开启ext4、squashfs、initramfs支持。我一般用initramfs做调试阶段的内核,稳定后再切到squashfs。
- 网络协议栈:IP、TCP、UDP是必须的。如果你要做视频流传输,别忘了开启CONFIG_NETFILTER和CONFIG_IP_MULTICAST。
避坑指南:我曾经在配置内核时漏掉了CONFIG_DEVTMPFS_MOUNT,结果根文件系统挂载不上,卡在kernel panic。查了两天才发现是udev没启动。嗯,这个选项一定要勾上。
4.2 设备树修改——让内核认识你的板子
设备树(Device Tree)是ARM Linux的标配了。说白了,它就是一份硬件描述文件,告诉内核:你的I2C挂在哪个控制器上、GPIO哪个引脚接了LED、DDR的时序参数是多少。
Hi3516的设备树文件一般在 arch/arm/boot/dts/ 目录下,命名类似 hi3516dv300.dtsi 和 hi3516dv300-emmc.dts。前者是芯片级描述,后者是板级描述。
我一般会改这几个地方:
- 内存节点:根据你的DDR容量修改
reg = <0x80000000 0x20000000>;(512MB为例) - 串口节点:确认uart0或uart1的引脚复用是否正确。调试串口搞错了,你就只能看灯闪了。
- 网口节点:Hi3516内置GMAC,需要配置phy地址和复位引脚。我踩过坑,phy地址写错了,网口死活link不上。
- 视频子系统:VI、VPSS、VENC这些节点,海思默认已经配好了,但如果你改了硬件管脚,记得同步修改。
举个例子,修改内存大小:
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB */
};
make dtbs 检查一下。
4.3 驱动编译——静态编译 vs 模块化
驱动怎么编译,这是个老生常谈的问题。我的原则是:核心驱动静态编译,外设驱动模块化。
什么叫核心驱动?内存控制器、中断控制器、定时器、串口——这些你系统启动就必须有的,直接编进内核。什么叫外设驱动?WiFi模块、音频Codec、USB摄像头——这些可以按需加载的,做成ko模块。
静态编译很简单,在menuconfig里把选项设为 <*> 就行。模块化编译设为 <M>。
编译模块的命令:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- modules
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- modules_install INSTALL_MOD_PATH=./modules
编译出来的ko文件放在 ./modules/lib/modules/ 下。你把它拷贝到板子的文件系统里,用 insmod 或 modprobe 加载就行。
我的经验:调试阶段我习惯用模块化编译。改一行代码,重新编译一个ko,scp到板子上insmod一下,几秒钟就能看到效果。要是每次都重新编内核、烧镜像,那效率太低了。
4.4 驱动加载——insmod、modprobe与自动加载
驱动编译好了,怎么让它跑起来?有三种方式:
| 方式 | 命令 | 适用场景 |
|---|---|---|
| 手动加载 | insmod xxx.ko |
调试阶段,临时测试 |
| 依赖加载 | modprobe xxx |
有依赖关系的驱动,自动解决依赖 |
| 自动加载 | 放入 /etc/modules-load.d/ 或 /lib/modules/ |
生产环境,开机自启 |
我调试视频驱动时,经常这么干:
# 先加载内核依赖
insmod hi_osal.ko
insmod hi_media.ko
# 再加载视频模块
insmod hi3516dv300_vi.ko
insmod hi3516dv300_vpss.ko
insmod hi3516dv300_venc.ko
# 检查是否加载成功
lsmod | grep hi
如果一切正常,你会看到 /dev/ 下出现了对应的设备节点,比如 /dev/hi_vi、/dev/hi_venc。
避坑指南:我曾经在加载驱动时忽略了加载顺序。先加载了VENC,再加载VPSS,结果VENC初始化失败,因为它依赖VPSS提供的buffer。后来我学乖了,严格按照海思文档里的依赖顺序来加载。嗯,顺序真的很重要。
4.5 调试技巧——三板斧
内核移植过程中,出问题是常态。我总结了三个调试技巧:
- 串口日志:内核启动时加上
console=ttyAMA0,115200参数,所有printk信息都会从串口输出。这是最直接的调试手段。 - 动态调试:如果驱动里用了
dev_dbg(),可以通过echo 'file xxx.c +p' > /sys/kernel/debug/dynamic_debug/control动态打开调试信息。 - 查看设备树:在板子上执行
ls /proc/device-tree/,可以确认内核解析到的设备树是否正确。我经常用这招检查内存大小和时钟频率。
举个例子,查看内存信息:
cat /proc/device-tree/memory/reg | hexdump -C
如果输出的值和你在dts里写的不一样,那说明设备树没更新,或者uboot传参有问题。
我的建议:每次修改设备树后,记得重新编译dtb并烧写到板子上。我见过有人改了dts,但忘了更新dtb,结果折腾了一下午。嗯,这种低级错误我也犯过。
4.6 总结
内核移植这事儿,说难不难,说简单也不简单。核心就是三点:配置要对、设备树要准、驱动要顺。你只要把这三点做好了,Hi3516的Linux系统就能稳稳地跑起来。
下一章咱们聊聊根文件系统的构建。到时候我会分享一些裁剪文件系统的小技巧,让系统启动更快、占用更小。咱们下章见。