该文章仅供参考,编写人不对任何实验设备、人员及测量结果负责!!!
0 引言
文章主要介绍King3399(ubuntu文件系统)驱动模块编译过程,涉及交叉编译工具以及驱动模块的加载与卸载
1 交叉编译工具链
本文驱动开发的编译方式为交叉编译,首先需要知道文件的编译设备与运行设备分别是何种架构,在ubuntu主机与king3399从机的终端分别输入arch
查询得到x86_64
与aarch64
,这也就意味着是用x86_64的设备去编译文件,用aarch64的设备去执行文件
其次我们还需要知道king3399内核源码的版本,在king3399从机终端输入uname -a
查询得到Linux ubuntu2004 5.10.198 ...aarch64...
,从返回结果可以知道版本号为5.10.198,进入king3399从机的/lib/modules
目录,可以看到几个以版本号命名的文件夹(在查找资料时看到有人说这里可能没有对应的版本号文件夹,可以尝试创建一个),本人该目录下存在5.10.160与5.10.198这两个文件夹,显然后者为我们的目标文件夹,后边编译好的.ko文件需要放到该文件夹才能够运行
再下一步便是安装交叉编译工具链,由于使用的是官方的SDK进行编译,这个SDK中自带了所需的工具链,因此无需单独安装,关于交叉编译器的使用可以到官方的下述目录去查找:
cdrom_king3399_new\02-软件文档\荣品文档\编译问题\linux\交叉编译器使用说明.pdf
不过这个文件也没有提供什么有用的信息,可能是官方觉得大家都懂,无需多言
至此还有最后一步:找到交叉编译工具的路径,这个在编写Makefile时需要传入交叉编译工具的路径,通常这个工具链会在/home/username/ws/sdk/
或者/home/username/ws/sdk/kernel/
目录下的脚本中提到,为节约时间这里直接给出涉及到的文件/home/username/ws/sdk/build.sh
,可以看到文件中有如下函数:
get_toolchain()
{
MODULE="$1"
TC_ARCH="${2/arm64/aarch64}"
TC_VENDOR="${3-none}"
TC_OS="${4:-linux}"
MACHINE=$(uname -m)
if [ "$MACHINE" != x86_64 ]; then
notice "Using Non-x86 toolchain for $MODULE!" >&2
if [ "$TC_ARCH" = aarch64 -a "$MACHINE" != aarch64 ]; then
echo aarch64-linux-gnu-
elif [ "$TC_ARCH" = arm -a "$MACHINE" != armv7l ]; then
echo arm-linux-gnueabihf-
fi
return 0
fi
# RV1126 uses custom toolchain
if [ "$RK_CHIP_FAMILY" = "rv1126_rv1109" ]; then
TC_VENDOR=rockchip
fi
TC_DIR="$RK_SDK_DIR/prebuilts/gcc/linux-x86/$TC_ARCH"
if [ "$TC_VENDOR" ]; then
TC_PATTERN="$TC_ARCH-$TC_VENDOR-$TC_OS-[^-]*-gcc"
else
TC_PATTERN="$TC_ARCH-$TC_OS-[^-]*-gcc"
fi
GCC="$(find "$TC_DIR" -name "*gcc" | grep -m 1 "/$TC_PATTERN$" || true)"
if [ ! -x "$GCC" ]; then
{
error "No prebuilt GCC toolchain for $MODULE!"
error "Arch: $TC_ARCH"
error "Vendor: $TC_VENDOR"
error "OS: $TC_OS"
} >&2
exit 1
fi
echo ${GCC%gcc}
}
这是一个shell脚本,本人不精通,不过可以猜出大致内容,有能力的可以尝试翻译翻译,这里取个巧,直接看函数的最后一句echo ${GCC%gcc}
这里应该是将传入的ARCH与CROSS_COMPILE合成路径并打印出,有了最终的路径就好办,那再想想我们何时用过这个build.sh文件,编译SDK源码时应该用过,而编译日志里边对编译过程有详细记录,编译日志路径为/home/username/ws/sdk/output/sessions/
,进入该路径可以看到若干以时间命令的文件夹,进入一个之前成功编译后的日志,打开xx-all-build.log
可以看到有如下内容:
......
^[[36m==========================================^[[0m
^[[36m Start building all images^[[0m
^[[36m==========================================^[[0m
^[[35mGenerated blank misc image^[[0m
^[[35mDone packing /home/username/ws/sdk/output/firmware/misc.img^[[0m
^[[35mRunning mk-misc.sh - build_misc succeeded.^[[0m
^[[36mToolchain for loader (U-Boot):^[[0m
^[[36m/home/username/ws/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-^[[0m
^[[36m==========================================^[[0m
^[[36m Start building loader^[[0m
^[[36m==========================================^[[0m
......
找到关键字串Toolchain
,该字串后边所跟便是交叉编译工具链的绝对路径,将该路径复制,后边会用到
至此,一切工作准备就绪,接下来将进行驱动模块的开发
2 驱动模块开发浅尝
驱动模块内容繁多,涉及面极广,本文只以最简单的驱动模块举例,梳理使用king3399进行驱动开发的流程
首先在ubuntu主机/home/username/ws/
目录下创建mydriver文件夹,并在该文件夹内创建helloworld.c与Makefile文件,目录树如下
│
├── mydriver
│ ├── helloworld.c
│ └── Makefile
在helloworld.c中添加如下内容
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_EMERG "[ KERN_EMERG ] Hello world Init\n");
printk( "[ default ] Hello world Init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("[ default ] Hello world Exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello world module");
在Makefile中添加如下内容(注意Makefile的书写格式!),KERNEL_DIR、ARCH、CROSS_COMPILE
需要替换为自己的路径与目标架构,KERNEL_DIR为官方SDK下的kernel层目录,CROSS_COMPILE为本文第1小节中提到的Toolchain
KERNEL_DIR=/home/username/ws/sdk/kernel/
ARCH=arm64
CROSS_COMPILE=/home/username/ws/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
export ARCH CROSS_COMPILE
obj-m:=helloworld.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
随后在/home/username/ws/mydriver/
目录下执行make指令,当返回内容如下时代表成功编译驱动模块
make
# make -C /home/username/ws/sdk/kernel/ M=/home/username/ws/mydriver modules
# make[1]: Entering directory '/home/username/ws/sdk/kernel'
# CC [M] /home/username/ws/mydriver/helloworld.o
# MODPOST /home/username/ws/mydriver/Module.symvers
# CC [M] /home/username/ws/mydriver/helloworld.mod.o
# LD [M] /home/username/ws/mydriver/helloworld.ko
# make[1]: Leaving directory '/home/username/ws/sdk/kernel'
可以看到此时在当前目录下生成很多文件,其中helloworld.ko为我们所需驱动文件,利用readelf -hl helloworld.ko
可以查看该文件的简要信息(若没有readelf指令,可以使用sudo apt install binutils
安装),其中Machine为AArch64,也即目标设备架构
3 运行驱动模块
运行驱动模块的步骤包括加载依赖与模块、查看模块、卸载模块,其中加载模块有两种方式:insmod与modprobe,区别可自行查找相关资料,这里会分别使用两种方式加载模块
3.1 insmod加载模块
使用insmod加载模块可在用户目录下的任意文件夹中实现,无需在/lib/modules/5.10.198
中,使用scp将ubuntu主机上刚才编译出的.ko文件传到king3399从机中
scp helloworld.ko username_king@aaa.bbb.ccc.ddd:/home/username_king/ws/
加载该驱动模块
sudo /sbin/insmod helloworld.ko # 这里有.ko后缀
运行成功会看到相关日志以及在.c文件中需要打印的字串
查看模块列表
lsmod
卸载模块
sudo /sbin/rmmod helloworld # 这里没有.ko后缀
3.2 modprobe加载模块
使用modprobe加载模块必须进入/lib/modules/5.10.198
目录,该目录出处可参考本文第1小节,使用scp将ubuntu主机上刚才编译出的.ko文件传到king3399从机中
scp helloworld.ko username_king@aaa.bbb.ccc.ddd:/lib/modules/5.10.198/
加载相关依赖
sudo /sbin/depmod
此处可能返回“WARING:could not open modules.order at…”和“WARING:could not open modules.builtin at…”,这两条警告可不用管
加载模块
sudo /sbin/modprobe helloworld # 这里没有.ko后缀!!!
运行成功会看到相关日志以及在.c文件中需要打印的字串
查看模块列表
lsmod
卸载模块
sudo /sbin/rmmod helloworld # 这里没有.ko后缀
[1] 野火-驱动章节实验环境搭建
[2] 《【野火】嵌入式Linux驱动开发实战指南—基于LubanCat RK系列板卡》
[3] 《【正点原子】ATK-DLRV1126嵌入式Linux驱动开发指南V1.2》