最近迫于项目的压力,一直在跟着韦东山的驱动入门实验班学习,学完第一个hello驱动的编写和验证后便想在自己的项目平台(64位系统的树莓派4B)上动手复现一遍。在网上搜索资料时发现此类教程因面向的树莓派型号、系统位数、内核版本号不同,实现的细节上有较大差异,导致整个复现过程曲折而有趣,故作此文澄清一些细节上的差别帮助后来者少走弯路,同时也是对我学习过程中的一个记录。
前期准备工作
韦东山老师的驱动入门实验班面向的芯片平台是恩智浦公司的IMX6ULL,树莓派4B搭载的芯片是博通公司的bcm2711,它们都是ARM平台Cortex-A架构的处理器,这意味着移植难度较低,不过还是有不少需要注意的地方。编译的话我们有两种方式:在树莓派上编译和在Linux虚拟机上交叉编译,现在x86平台的性能普遍比ARM平台强悍,为了加快编译速度我们选择在虚拟机上交叉编译。如果你对交叉编译的概念仍然不够清楚,可以参考我的这篇文章,里面有交叉编译的相关介绍:
树莓派LVGL移植简易教程https://blog.youkuaiyun.com/qq_38667470/article/details/145840485
既然我们选择交叉编译,那么相应的软件和系统镜像就是必须的。为了图方便我直接下载韦东山打包好的vmware系统镜像,下载后直接用vmware打开便可使用,里面有韦东山写好的代码并下载好了一些开发常用的软件。这里贴上韦东山资料的百度云下载链接,里面也有vmware的软件资源,如果你没有安装vmware的话也可以一并下载安装:
vmware安装后,直接打开下图中的文件便可装载韦东山的系统镜像:
更换交叉编译链
韦东山交叉编译的目标芯片平台和我的不同,故使用的交叉编译工具链也不同,我们需要先在韦东山的ubuntu中使用如下命令安装适配的交叉编译工具链:
sudo apt-get install g++-aarch64-linux-gnu
然后更改默认的编译配置文件,输入vim ~/.bashrc并在底下添加如下代码:
export ARCH=arm
export CROSS_COMPILE=aarch64-linux-gnu-
export PATH=$PATH:/usr/aarch64-linux-gnu/bin
使用source命令更新一下:
source ~/.bashrc
验证一下是否更换成功,成功后应该如下图所示:
下载并配置编译内核
驱动的编译必须在编译好了的内核目录下进行,韦东山使用的内核是Linux-4.9.88,我们树莓派有自己的内核文件,可进入官方的GitHub链接下载:树莓派内核代码6.1版本http://link.zhihu.com/?target=https%3A//github.com/raspberrypi/linux/tree/rpi-6.1.y注意我的树莓派内核版本是6.1.21所以下的是6.1版本的,你们可以根据自己的内核版本下载相应版本的代码,不知道内核版本的可使用以下命令查看:
uname -r
推荐使用压缩包模式一键下载,git clone的话,因为下载的是树莓派内核的一个分支,所以需要在命令里加上你的分支文件名,也不是不行,不过下载显示的目录名直接就是Linux,我不确定下载的是不是我想要的分支就没用这种方法。
下载完成后解压,打开虚拟机终端窗口cd到解压出来的目录,完成接下来的两步操作才能开始编译驱动。
配置内核源码
源码配置是源码编译的前置操作,配置后会生成一个.config文件,有了这个文件后Makefile才能开始正式编译内核,说白了.config文件决定了要把哪些内容编译进内核,这些内容可由我们自行选择,我们通过以下命令选择默认的配置文件:
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KERNEL=kernel8 make bcm2711_defconfig
成功后会在最后显示:configuration written to .config
编译内核源码
配置完成后便可开始编译,使用下面的命令编译内核源码:
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KERNEL=kernel8 make -j4 Image modules dtbs
这里其实将三条命令合并为了一条,一起编译出了镜像(Image),驱动(modules)和设备树文件(dtbs)。注意很多教程会把这里的Image改为zImage,我试了下这样会报错,估计是新版本不支持这种镜像名称了。-j4指的是使用4个并行的进程来执行该任务,不加这个的话编译会比较慢。
编译成功后会在默认目录下多出名为vmlinux的文件。
编译hello驱动
终于到了激动人心的时刻,如果能成功编译出驱动就意味着我们的移植成功了一大半了。韦东山老师的代码基本上是通用的,我们直接新建一个文件夹,然后把下面三个文件拖到新建的文件夹:
注意路径,最初的目录是桌面那个文件夹
为了避免和原代码弄混,可以稍微改下名字,我把两个.c文件的名字后面都加上了_raspi。把Makefile里面指定的内核目录的路径和对应的文件名改了后直接make,结果不出意外的话还是出意外了,编译过程中报错,而且是从没见过的错误。当时真的百思不得其解,搜索报错内容也没有结果。在反复对比Makefile中的命令和教程中的命令后,我尝试在make前面添加以下内容:
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
保存后尝试make,终于成功编译出来了!
Makefile完整代码如下图所示:
KERN_DIR = /home/book/100ask_imx6ull_mini-sdk/linux-rpi-6.1.y
all:
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o hello_test_raspi hello_test_raspi.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_test_raspi
obj-m += hello_drv_raspi.o
然而事后证明我还是高兴的太早,我把编译出的.ko文件(hello驱动)传输到树莓派,使用如下命令尝试装载驱动:
insmod hello_drv_raspi.ko
又开始报错了,错误提示如下:
could not insert module : Invalid module format
搜索了下,原因很可能出在编译出的驱动支持的内核版本号和目标系统的内核版本号对不上,内核版本的搜索命令之前已经提过了,可用如下命令查看驱动支持的内核版本号:
modinfo <module_name.ko>
查看了下果然如此,我的系统内核版本是6.1.21,而驱动面向的内核版本是6.1.93。
如何解决呢?只能将系统内核换成驱动面向的版本了。
更换树莓派内核
好在我们之前已经将内核代码编译好了,接下来只需要将这份代码拷贝到树莓派就可以直接进行更换了(编译出的驱动面向的内核版本和下载下来的内核代码是一致的)。编译出的内核代码体积比较大,我先把它压缩,然后用U盘拷贝到了树莓派(这里提一嘴,现在Linux桌面系统对于可移动存储设备的支持已经很完善了,不再需要使用命令进行挂载,使用体验接近windows)。在树莓派上解压缩,然后cd到解压出来的目录,依次执行下列命令:
sudo make modules_install
sudo cp arch/arm64/boot/Image /boot/$KERNEL.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb /boot/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm64/boot/dts/overlays/README /boot/overlays/
sudo reboot
重启后查看系统内核版本,结果没变化,这是怎么回事?
查看教程后发现,原来自己没有在启动的配置文件里指定新的系统镜像为默认启动镜像。根据教程,只需要依次执行下面两条命令:
sudo cp arch/arm64/boot/Image /boot/kernel-my.img
sudo vim /boot/config.txt
然后在最下面添加一条代码:
kernel=kernel-my.img
保存后重启即可,重启后执行uname -r命令可以看到版本号已变为6.1.93。
装载驱动并测试
树莓派的内核更换完毕后,移植的工作基本就进入尾声了。将编译出的驱动(.ko文件)和测试程序(可执行文件)拷贝到树莓派,先执行下面的命令装载驱动:
sudo insmod hello_drv_raspi.ko
然后执行下面的命令查看已装载驱动的设备号:
cat /proc/devices
可以看到驱动已成功装载,且主设备号是236:
为了测试该驱动程序是否正常,我们还需要先新建一个设备节点:
mknod /dev/xyz c 236 0
然后执行sudo su切换为root用户,使用下列命令对驱动执行测试程序:
./hello_test_raspi /dev/xyz
发现终端只打印出简单的信息:read str :�2�
其实这已经意味着测试成功了,我们的驱动成功被我们的测试程序调用,这只是测试程序中我们规定打印的内容,内核部分也打印出了驱动程序中规定输出的内容,但这些内容在终端是不可见的,需要使用dmesg命令查看:
dmesg | grep hello
这里我们使用grep将搜索内容限定在和hello相关的信息上,这样更加直观,执行后输出内容如下:
韦东山的课程中有条命令可以将内核的打印信息输出到终端:
echo "7 4 1 4" > /proc/sys/kernel/printk
不过我尝试后发现这条命令对树莓派无效,想查看内核打印信息还是得用dmesg命令。
等等,我好像知道第一次编译hello驱动的时候为什么会出错了。
我在配置交叉工具链的时候输入的是export ARCH=arm,那意味着默认是以32位系统为目标去编译的,而Makefile中指定的内核源码是64位的,这样的话make就会产生不兼容。在make前面加前缀指定目标系统为64位就顺利解决了这个兼容问题。
交叉工具链配置的确实有问题,输入vim ~/.bashrc并把当时输入代码的第一行改为下面这样:
export ARCH=arm64
然后source更新一下,这样配置完后用修改前的Makefile应该也能顺利编译,试过的朋友可以在评论区反馈一下。
就像我的标题写的那样,碍于篇幅,这篇文章只是单纯地教如何操作的,如果还要介绍原理那文章就太长了,也没有必要。想深入了解原理建议直接看韦东山老师的驱动教学视频。
注:这篇文章参考并引用了韦东山老师的部分教学资料和网上其他博主的内容,这些内容仅用于科普,若构成侵权本人将第一时间进行删改。
觉得文章有帮助的朋友可以点赞收藏转发支持一下,感谢!
当然,也欢迎来我其他的内容平台逛逛,B站、知乎、公众号同名。