2-Linux驱动开发-内核;内核模块;设备树;设备树插件
交叉编译器以及基础环境和内核构建
- 虚拟机的交叉编译要和硬件交叉编译的版本适配
- 各个版本交叉编译器链接https://mirrors.tuna.tsinghua.edu.cn/armbian-releases/_toolchain/
- 下载以下版本gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf.tar.xz
虚拟机环境构建 - 拖到vscode一个文件夹然后进行解压
sudo tar -xvf gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf.tar.xz - 将工具链路径添加到系统环境变量中,
vim ~/.bashrc - 路径一定是你解压的位置,到你解压位置pwd,然后把路径替换为你的路径
export PATH=$PATH:/opt/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/bin - 让环境变量立即生效:
source ~/.bashrc - 验证交叉编译器是否配置成功:
arm-none-linux-gnueabihf-gcc -v

由于编译器默认名字太长,所以采用别名进行修改,在~/.bashrc中末尾添加alias arm-gcc='arm-none-linux-gnueabihf-gcc',source ~/.bashrc生效一下,操作见下图


其他插件
sudo apt install make bison flex libssl-dev dpkg-dev lzop
• bison 语法分析器
• flex 词法分析器
• libssl-dev OpenSSL 通用库
• lzop LZO 压缩库的压缩软件
编译内核
uname -r //查看内核版本
linux:
kun@kun-virtual-machine:~/Desktop$ uname -r
6.8.0-87-generic
stm32mp157:
root@lubancat:~# uname -r
4.19.94-stm-r1
说明:主机的 6.8.0 内核与驱动开发完全无关。它唯一的角色是提供一个工作台,来运行交叉编译工具链。
1.核心工作:在主机编译内核的真正目的真正目的是在主机上,构建一个与目标板 (lubancat) 内核版本(4.19.94)完全一致的“内核模块开发环境”。这个开发环境是后续编译所有外部驱动模块的绝对前提。缺少这一步,驱动模块(.ko)将无法编译或加载。
2.关键依赖:内核编译的“副产品”
编译内核的过程会生成三个关键的“副产品”,它们是编译外部 .ko 模块时必需的:内核头文件 (Headers): 提供了驱动开发所需的 API 声明和数据结构。.config 配置文件: 确保了您的驱动是基于与目标内核完全一致的功能配置来编译的。Module.symvers (符号表): “API 字典”,记录了4.19.94内核中所有导出的、可供模块调用的函数(如 printk, kmalloc)及其内存地址。
具体操作
获取内核源码
去https://github.com/Embedfire/ebf_linux_kernel.git下载ebf_4.19_star分支(对应板子内核源码分支)
unzip ebf_linux_kernel-ebf_4.19_star.zip
内核源码进行编译
找到 make_deb.sh 脚本,里面有配置好的参数,只需要执行脚本便可编译内核。要进入对于文件夹
编译出来的内核相关文件存放位置,由脚本 make_deb.sh 中 build_opts="${build_opts}O=build_image/build" 指定。**需要进行以下两次修改(修改交叉链路径和bison环境变量)才能执行脚本**
make_deb.sh中文件详情如下:
============================================================
deb_distro=bionic
DISTRO=stable
build_opts="-j 16"
build_opts="${build_opts} O=build_image/build"
build_opts="${build_opts} ARCH=arm"
build_opts="${build_opts} KBUILD_DEBARCH=${DEBARCH}"
build_opts="${build_opts} LOCALVERSION=-stm-r1"
build_opts="${build_opts} KDEB_CHANGELOG_DIST=${deb_distro}"
build_opts="${build_opts} KDEB_PKGVERSION=1.$(date +%g%m)${DISTRO}"
# build_opts="${build_opts} CROSS_COMPILE=arm-linux-gnueabihf-"
build_opts="${build_opts} CROSS_COMPILE=arm-none-linux-gnueabihf-" #这里我根据自己的.bashrc中的arm-gcc进行修改的交叉编译链
build_opts="${build_opts} KDEB_SOURCENAME=linux-upstream"
make ${build_opts} stm32mp157_ebf_defconfig
#make ${build_opts} menuconfig
make ${build_opts}
make ${build_opts} bindeb-pkg
============================================================由于我的linux版本22.04所以新版 bison 生成的代码(dtc-parser.tab.o)和新版 flex 生成的代码(dtc-lexer.lex.o)在 4.l9 这个老脚本里,都声明了 yylloc 变量,会导致了冲突。
操作:/scripts/dtc/dtc-lexer.l中YYLTYPE yylloc;修改为extern YYLTYPE yylloc;最后执行编译脚本
./make_deb.sh
编译和加载内核驱动模块
目的:使用刚编译好的内核环境,去逐个编译示例驱动,生成 .ko 文件,最后加载到 lubancat 板子上去验证。
获取内核驱动模块
git clone https://gitee.com/embedfire-st/embed_linux_driver_tutorial_stm32mp157_code.git
操作此路径linux_driver/module/hellomodule下的文件
首先修改Makefile文件
hellomodule
两处修改:首先是内核的build加载路径,改为你的绝对路径(在相应build下面进行pwd然后输出路径)KERNEL_DIR交叉编译链的具体名字,根据你下载的来进行修改 CROSS_COMPILE
=========================Makefile文件=============================
# KERNEL_DIR=../../../ebf_linux_kernel/build_image/build
KERNEL_DIR=/home/kun/kernal_code/ebf_linux_kernel-ebf_4.19_star/build_image/build
ARCH=arm
# CROSS_COMPILE=arm-linux-gnueabihf-
CROSS_COMPILE=arm-none-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := hellomodule.o
all:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules.PHONE:clean copy
clean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:sudo cp *.ko /home/embedfire/workdir
================================================================
进行make生成hellomodule.ko文件
进行文件挂载:(如果构建好NFS此步跳过)
-
Linux上
#关闭防火墙 sudo ufw disable #虚拟机中安装并启动NFS 服务器,并设置为自启动 sudo apt install nfs-kernel-server sudo systemctl start nfs-kernel-server sudo systemctl enable nfs-kernel-server #要告诉 NFS 服务器允许共享哪个文件夹,因为板子root用户会被默认为nobody,服务器会拒绝这个挂载请求 sudo nano /etc/exports #添加你共享的目录 /home/kun/arm_kernal *(rw,sync,no_subtree_check,no_root_squash) #生效配置 sudo exportfs -a -
如果板子没有文件系统只需要下载文件系统,其他的都不需要
板子上进行挂载:
ko文件拖到虚拟机中的/home/kun/arm_kernal下面
sudo mount -t nfs -o nolock 192.168.137.3:/home/kun/arm_kernal /mnt/host_projects

#插入这个内核模块 insmod仅用于测试用
debian@lubancat:/mnt/host_projects$ sudo insmod hellomodule.ko
#插入成功的默认内容输出
Message from syslogd@lubancat at Nov 13 21:12:09 ...kernel:[ 666.015524] [ KERN_EMERG ] Hello Module Init
#利用管道查看此内核module
debian@lubancat:/mnt/host_projects$ lsmod | grep hellomodule
hellomodule 16384 0
#删除这个内核模块
debian@lubancat:/mnt/host_projects$ sudo rmmod hellomodule.ko
设备树
引言:内核模块 (驱动, .ko) 解决了“如何做”的问题(它包含代码)。 设备树 (.dtb) 解决了“做在谁身上”的问题(它包含数据)。实现了“代码”与“数据”分离;例如:通用的 LED 驱动(内核模块)。不知道 LED 在哪个引脚上,设备树 (.dtb)会告诉它取名叫 user-led,它连接在 GPIO A 口的第 5 引脚。
启动时: Bootloader (如 U-Boot) 加载 zImage (内核) 和 .dtb (设备树) 到内存中。
内核初始化: 内核启动,它首先读取 .dtb 文件,在内存中建立一个“硬件清单”,知道硬件上有什么设备。
加载驱动模块: 您通过 NFS 挂载,然后运行: sudo insmod led-driver.ko(内核模块插入示例)
匹配(Probe):led-driver.ko 向内核注册,内核马上去查它的“硬件清单”(来自 .dtb),匹配成功后,内核立刻“激活”这个驱动,并把从 .dtb 里读到的“数据”(“GPIO A 口第 5 引脚”)传递给 led-driver.ko。总结:Probe(匹配)就是内核使用设备树(.dtb)中的 compatible 属性,为“硬件”找到了能处理它的“驱动”,并把“硬件”的具体参数(如引脚、地址)传递给“驱动”使其运行
驱动工作: led-driver.ko (内核驱动模块文件)拿到引脚信息,开始工作。
编译和加载设备树
说明:
编译要在内核源码目录 (KSRC):/
所有产物会输出到:内核编译目录 (KOUT):/build_image/build下
在虚拟机上将可读的文本文件(.dts)转换成内核可读的二进制文件(.dtb)
编译工具 (dtc): Device Tree Compiler (设备树编译器)。
源码文件 (.dts): Device Tree Source
目标文件 (.dtb): Device Tree Blob
#使用示例
内核目录/scripts/dtc/dtc -I dts -O dtb -o xxx.dtb xxx.dts
# -I (Input format: dts)
# -O (Output format: dtb)
# -o (Output file)
dtc -I dts -O dtb -o stm32mp157-lubancat.dtb stm32mp157-lubancat.dts
说明:
实际上设备树中有非常多的依赖关系,这些依赖关系通过 Makefile 文件去处理,所以一般情况下,设备树不仅仅只是通过一个 dtc 命令就能将编译出来的。
所要用到的设备树文件都存放在 内核源码/home/kun/kernal_code/ebf_linux_kernel-ebf_4.19_star/arch/arm/boot/dts中
编译内核时会自动去编译设备树,但是编译内核很耗时,所以我们推荐使用如下命令只编译设备树。在内核文件根目录执行。
#配置步骤。它不编译任何东西,它只生成一个 .config 文件,这个文件是编译的前提
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157_ebf_defconfig
#以 ARCH=arm 模式工作,目标架构
#使用 CROSS_COMPILE=... 编译器
#stm32mp157_ebf_defconfig: 指定要执行的构建目标 (Build Target)
#stm32mp157_ebf_defconfig默认配置,它会搜索 arch/arm/configs/ 目录,找到 stm32mp157_ebf_defconfig 这个“基准配置文件”(它由内核或板卡维护者提供),并以此为蓝本,在顶层目录生成最终的 .config 文件。#编译步骤。它读取 .config 文件,然后只编译设备树(.dts -> .dtb),跳过所有其他耗时的内核编译。
make ARCH=arm -j4 CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs
#以 ARCH=arm 模式工作,目标架构
#用 -j4 (4 个 CPU 核心) 来加速
#使用 CROSS_COMPILE=... 编译器
#执行 dtbs “目标”名称,#make 会首先读取 .config 文件,查看里面配置了哪些板子。#跳过所有 C 代码的编译。#只去 arch/arm/boot/dts/ 目录,找到所有需要编译的 .dts 源文件。#调用 scripts/dtc/dtc (设备树编译器) 来把它们全部编译成 .dtb 文件。
引入说明:
内核源码目录 (KSRC):/
内核编译目录 (KOUT):/build_image/build
在根目录下执行
生成配置文件:make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157_ebf_defconfig流程:读取arch/arm/configs/stm32mp157_ebf_defconfig(蓝本)生成/.config(隐藏文件在当前目录)执行编译:make ARCH=arm -j4 CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs流程:读取配置/.config,读取 .dts 源码/arch/arm/boot/dts/生成/build_image/build/arch/arm/boot/dts/
先筛选进一步找相应文件文件:ls /build_image/build/arch/arm/boot/dts/stm32mp157*发板适配的设备树文件名以及所在位置:
ebf_4.19_star/build_image/build/arch/arm/boot/dts/stm32mp157a-basic.dtb 通过NFS文件系统传给板子
stm32mp157a-basic.dtb 替换板子里面的/boot/dtbs/stm32mp157a-basic.dtb
最后进行reboot新的设备树就生效了
编译和加载设备树插件
设备树插件和设备树是互补的关系,设备树插件可以在主设备树定型的情况下,再对主设备树未描述的功能进行动态的拓展。
设备树插件与设备树都是使用device tree compiler,但是区别在于设备树编译为.dtb,设备树插件编译为.dtbo。
写新设备树插件的时候,自己的设备树插件添加到:/arch/arm/boot/dts/overlays 目录下
修改 arch/arm/boot/dts/overlays/Makefile 文件,添加编译选项/添加dtbo文件

上述设备树插件添加后
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157_ebf_defconfig
make ARCH=arm -j4 CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs
make ... dtbs
上述操作完成两个操作:
自动编译主设备树,生成主 .dtb 文件。
同时(同步),它会自动去 arch/arm/boot/dts/overlays/Makefile 目录,编译所有被您登记在册的插件,生成插件 .dtbo 文件。
板子上加载设备树插件
#将.dtbo 设备树插件拷贝到开发板 /usr/lib/linux-image-4.19.94-stm-r1/overlays/ 上
mv 挂载文件地址 /usr/lib/linux-image-4.19.94-stm-r1/overlays/
#修改/boot下的uEnv.txt,写入加载的设备树插件
dtoverlay=/usr/lib/linux-image-4.19.94-stm-r1/overlays/"这里对应的你的设备树插件名字".dtbo


总结设备树,设备树插件
设备树,设备树插件说明:
| 对比项 | 设备树 (Device Tree) | 设备树插件 (Device Tree Overlay) |
|---|---|---|
| 作用 | 主蓝图 (完整的硬件地图) | 补丁 (对主蓝图的“修改”或“补充”) |
| 源码示例 | stm32mp157a-basic.dts | fire-rgb-led-overlay.dts |
| 编译方式 | make dtbs 会自动找到。 | 需将 .dts 放入 overlays/ 目录,并修改 overlays/Makefile 进行登记。 |
| 编译命令 | make … dtbs | make … dtbs (同一个命令,它会“顺带”编译插件) |
| 编译产物 | stm32mp157a-basic.dtb (.dtb 文件) | rgb.dtbo (.dtbo 文件) |
| 加载方式 | 替换 /boot/ 目录文件 | 复制到“货架” , 修改 uEnv.txt |
make … stm32mp157_ebf_defconfig ; dtbs命令说明
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157_ebf_defconfig
“内核”自带功能的“总开关”列表,生成了一份功能清单 (.config),只要没有运行 make clean 或 make distclean就不需要重新运行 defconfigmake ARCH=arm -j4 CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs
无论是修改主设备树(.dts)还是设备树插件(.dts / .dtbo),都只需要 make ... dtbs。
.config, .dtb, .ko分工
| 文件类型 | .config (内核配置文件) | .dtb / .dtbo (设备树) | .ko (内核模块) |
|---|---|---|---|
| 角 色 | “软件功能清单” | “硬件布局蓝图” | “驱动程序代码” |
| 内 容 | 软件功能的开关列表 | 硬件数据的描述列表 | CPU 机器指令 |
| 生成指令 | make ..._defconfig | make dtbs (调用 dtc 工具) | make (在模块目录中) |
| 谁依赖它 | make dtbs 和 make (编译 .ko) 都依赖它。 | 驱动(.ko)的 probe 依赖它。 | (无,它是最终执行者) |
