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.dtsihi3516dv300-emmc.dts。前者是芯片级描述,后者是板级描述。

我一般会改这几个地方:

  1. 内存节点:根据你的DDR容量修改 reg = <0x80000000 0x20000000>;(512MB为例)
  2. 串口节点:确认uart0或uart1的引脚复用是否正确。调试串口搞错了,你就只能看灯闪了。
  3. 网口节点:Hi3516内置GMAC,需要配置phy地址和复位引脚。我踩过坑,phy地址写错了,网口死活link不上。
  4. 视频子系统:VI、VPSS、VENC这些节点,海思默认已经配好了,但如果你改了硬件管脚,记得同步修改。

举个例子,修改内存大小:

memory@80000000 {
    device_type = "memory";
    reg = <0x80000000 0x20000000>;  /* 512MB */
};
注意:设备树的语法很严格,少一个分号或者多一个逗号,编译出来的dtb就是坏的。我建议每次改完都跑一遍 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/ 下。你把它拷贝到板子的文件系统里,用 insmodmodprobe 加载就行。

我的经验:调试阶段我习惯用模块化编译。改一行代码,重新编译一个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 调试技巧——三板斧

内核移植过程中,出问题是常态。我总结了三个调试技巧:

  1. 串口日志:内核启动时加上 console=ttyAMA0,115200 参数,所有printk信息都会从串口输出。这是最直接的调试手段。
  2. 动态调试:如果驱动里用了 dev_dbg(),可以通过 echo 'file xxx.c +p' > /sys/kernel/debug/dynamic_debug/control 动态打开调试信息。
  3. 查看设备树:在板子上执行 ls /proc/device-tree/,可以确认内核解析到的设备树是否正确。我经常用这招检查内存大小和时钟频率。

举个例子,查看内存信息:

cat /proc/device-tree/memory/reg | hexdump -C

如果输出的值和你在dts里写的不一样,那说明设备树没更新,或者uboot传参有问题。

我的建议:每次修改设备树后,记得重新编译dtb并烧写到板子上。我见过有人改了dts,但忘了更新dtb,结果折腾了一下午。嗯,这种低级错误我也犯过。

4.6 总结

内核移植这事儿,说难不难,说简单也不简单。核心就是三点:配置要对、设备树要准、驱动要顺。你只要把这三点做好了,Hi3516的Linux系统就能稳稳地跑起来。

下一章咱们聊聊根文件系统的构建。到时候我会分享一些裁剪文件系统的小技巧,让系统启动更快、占用更小。咱们下章见。