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生效一下,操作见下图

其他插件

bash 复制代码
sudo apt install make bison flex libssl-dev dpkg-dev lzop
• bison 语法分析器
• flex 词法分析器
• libssl-dev OpenSSL 通用库
• lzop LZO 压缩库的压缩软件

编译内核

bash 复制代码
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 内核与驱动开发完全无关。它唯一的角色是提供一个工作台,来运行交叉编译工具链。

html 复制代码
1.核心工作:在主机编译内核的真正目的
	真正目的是在主机上,构建一个与目标板 (lubancat) 内核版本(4.19.94)完全一致的"内核模块开发环境"。
	这个开发环境是后续编译所有外部驱动模块的绝对前提。缺少这一步,驱动模块(.ko)将无法编译或加载。
2.关键依赖:内核编译的"副产品"
编译内核的过程会生成三个关键的"副产品",它们是编译外部 .ko 模块时必需的:
	内核头文件 (Headers): 提供了驱动开发所需的 API 声明和数据结构。
	.config 配置文件: 确保了您的驱动是基于与目标内核完全一致的功能配置来编译的。
	Module.symvers (符号表): "API 字典",记录了4.19.94内核中所有导出的、可供模块调用的函数(如 printk, kmalloc)及其内存地址。

具体操作

html 复制代码
获取内核源码
去https://github.com/Embedfire/ebf_linux_kernel.git下载ebf_4.19_star分支(对应板子内核源码分支) 
unzip ebf_linux_kernel-ebf_4.19_star.zip
html 复制代码
内核源码进行编译
找到 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 板子上去验证。

html 复制代码
获取内核驱动模块
git clone https://gitee.com/embedfire-st/embed_linux_driver_tutorial_stm32mp157_code.git

操作此路径linux_driver/module/hellomodule下的文件

html 复制代码
首先修改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上

    bash 复制代码
    #关闭防火墙
    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下面

bash 复制代码
sudo mount -t nfs -o nolock 192.168.137.3:/home/kun/arm_kernal  /mnt/host_projects
html 复制代码
#插入这个内核模块 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 引脚。

html 复制代码
启动时: 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 (内核驱动模块文件)拿到引脚信息,开始工作。
编译和加载设备树
bash 复制代码
说明:
编译要在内核源码目录 (KSRC):/
所有产物会输出到:内核编译目录 (KOUT):/build_image/build下

在虚拟机上将可读的文本文件(.dts)转换成内核可读的二进制文件(.dtb)

编译工具 (dtc): Device Tree Compiler (设备树编译器)。

源码文件 (.dts): Device Tree Source

目标文件 (.dtb): Device Tree Blob

bash 复制代码
#使用示例
内核目录/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

说明:

html 复制代码
实际上设备树中有非常多的依赖关系,这些依赖关系通过 Makefile 文件去处理,所以一般情况下,设备树不仅仅只是通过一个 dtc 命令就能将编译出来的。
所要用到的设备树文件都存放在 内核源码/home/kun/kernal_code/ebf_linux_kernel-ebf_4.19_star/arch/arm/boot/dts中

编译内核时会自动去编译设备树,但是编译内核很耗时,所以我们推荐使用如下命令只编译设备树。在内核文件根目录执行。

bash 复制代码
#配置步骤。它不编译任何东西,它只生成一个 .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 文件。
html 复制代码
引入说明:
内核源码目录 (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文件

上述设备树插件添加后

bash 复制代码
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 文件。

板子上加载设备树插件

bash 复制代码
#将.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命令说明

html 复制代码
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157_ebf_defconfig
"内核"自带功能的"总开关"列表,生成了一份功能清单 (.config),只要没有运行 make clean 或 make distclean就不需要重新运行 defconfig

make 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 dtbsmake (编译 .ko) 都依赖它。 驱动(.ko)的 probe 依赖它。 (无,它是最终执行者)
相关推荐
Orlando cron2 小时前
CPU Load(系统平均负载)
运维·服务器·网络
SUPER52662 小时前
deepseek-R1模型输出时截断异常
运维·服务器·deepseek-r1调用
张暮笛2 小时前
Linux内核LED驱动开发:实现可控制闪烁与常亮的GPIO驱动
linux·驱动开发
CheungChunChiu2 小时前
[特殊字符] 嵌入式音频接口全景图解:I2S、TDM、PDM、SPDIF、AC’97 与 PCM 的关系
linux·audio·pulseaudio
Nimsolax2 小时前
Linux网络数据链路层
linux·网络
小武~3 小时前
嵌入式网络编程实战:从Socket基础到高并发优化
linux·网络
大聪明-PLUS3 小时前
Rsync:管理员详细指南 第2部分
linux·嵌入式·arm·smarc
chenzhou__3 小时前
LinuxC语言文件i/o笔记(第十七天)
linux·c语言·笔记·学习
chenzhou__3 小时前
LinuxC语言文件i/o笔记(第十八天)
linux·c语言·笔记·学习