| ||
By Toradex秦海
1). 简介
Linux Kernel 通常包含了常见的硬件驱动,只需要通过 Kernel Configuration 来使能即可。不过有的时候还是会遇到需要针对特定硬件或者功能来开发定制化驱动的场景,本文就用一个简单的 Hello World 设备驱动示例相关驱动的开发编译调试流程。
本文所演示的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。
2. 准备
a). Verdin i.MX8MP ARM核心版配合Dahlia 载板,并连接调试串口用于测试
3). Linux Kernel 源码下载和编译
a). 参考这里文章说明,下载适用于 Verdin iMX8MP 的 Linux Kernel 源码,以及配置交叉编译 SDK。本文演示使用基于 NXP Downstream Kernel 版本进行操作,如果需要使用 Upstream Kernel 版本,除了上述文章说明也可以参考这里
./ 首先下载对应版本的 Linux Kernel 源代码。如果是 Upstream Kernel 版本,下载源码后还需要应用一系列补丁文件:
---------------------------------------
$ cd <work_dir>
$ git clone -b toradex_6.6-2.2.x-imx git://git.toradex.com/linux-toradex.git
---------------------------------------
./ 参考这里说明下载交叉编译 tool chain,根据具体使用应用平台选择 32-bit 还是 64-bit 版本,本文这里针对 i.MX8MP 平台使用 64-bit 版本:
---------------------------------------
$ cd ~
$ wget -O arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz "https://developer.arm.com/-/media/Files/downloads/gnu/12.3.rel1/binrel/arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz?rev=cf8baa0ef2e54e9286f0409cdda4f66c&hash=4E1BA6BFC2C09EA04DBD36C393C9DD3A"
$ tar xvf arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
$ ln -s arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu gcc-linaro
---------------------------------------
./ 在准备进行后续编译的 Terminal 终端窗口下 export 相关 tool chain 环境变量:
---------------------------------------
$ export ARCH=arm64
$ export DTC_FLAGS="-@"
$ export PATH=~/gcc-linaro/bin/:$PATH
$ export CROSS_COMPILE=aarch64-none-linux-gnu-
---------------------------------------
b). 参考这里说明下载针对 Verdin i.MX8MP 硬件的初始化 Kernel Configuration 文件,然后配置
---------------------------------------
$ cd <work_dir>
$ wget https://artifacts.toradex.com:443/artifactory/tdxref-oe-prod-frankfurt/scarthgap-7.x.y/release/5/verdin-imx8mp/tdx-xwayland/tdx-reference-multimedia-image/oedeploy/kernel-config
$ cp kernel-config linux-toradex/.config
$ cd linux-toradex/
$ make olddefconfig
---------------------------------------
c). 由于 NXP i.MX8 系列 SoC 默认 Yocto Linux GPU 驱动使用 out of tree 驱动,而单独编译 Kernel 源码时候则需要将 built-in 版本 GPU 驱动使能,否则显示相关就会异常。当然后续如果采用 built-in 方式编译定制化驱动也需要在这里使能
---------------------------------------
$ make menuconfig
Device Drivers > MXC support drivers > MXC Vivante GPU support
<*> MXC Vivante GPU support
---------------------------------------
./ 当然也可以将如下配置直接添加到上述步骤 b 下载的 kernel-config 文件中去使能
---------------------------------------
CONFIG_MXC_GPU_VIV=y
---------------------------------------
d). 编译 Kernel Binary Image
---------------------------------------
$ make -j$(nproc) Image.gz 2>&1 | tee build.log
---------------------------------------
e). 编译和打包 Kernel Modules
---------------------------------------
$ make -j$(nproc) modules
//在 Kernel 源码目录 linux-toradex 外创建 kernel-modules 目录
$ mkdir ../kernel-modules
// 通过pwd 命令获取 kernel-modules 目录路径 <path-to-kernel-modules>
$ cd ../kernel-modules
$ pwd
// 部署 kernel modules
$ cd ../linux-toradex
$ sudo -E env "PATH=$PATH" make INSTALL_MOD_PATH=<path-to-kernel-modules>/ modules_install
// 打包 kernel modules
$ cd ../kernel-modules
$ tar cjvf kernel-modules.tar.bz2 lib/modules/*
---------------------------------------
f). 上传生成的 Image.gz 和 kenrel-modules.tar.bz2 文件到 Verdin i.MX8MP 模块,这里使用 ssh 网络上传,也可以使用 SD卡/U盘复制
---------------------------------------
$ cd ../linux-toradex/
$ scp arch/arm64/boot/Image.gz ../kernel-modules/kernel-modules.tar.bz2 root@<ip_address_verdin_imx8mp>:/home/root/
---------------------------------------
g). 在 Verdin i.MX8MP 上面部署新的 Linux Kernel 和 Kernel Modules,重启后新的部署生效
---------------------------------------
root@verdin-imx8mp-06849028:~# cd /home/root
root@verdin-imx8mp-06849028:/home/root# cp Image.gz /boot/
root@verdin-imx8mp-06849028:/home/root# cd /
root@verdin-imx8mp-06849028:/# tar xvf ~/kernel-modules.tar.bz2
root@verdin-imx8mp-06849028:/# reboot
---------------------------------------
4). 定制 Linux Driver 采用 Out-of-Tree 方式单独编译部署
a). 在 Kernel 源码目录 linux-toradex 外创建定制驱动工作目录,并创建驱动源码 c 代码以及 Makefile 文件
-------------------------------
$ mkdir ../hello-world-driver
$ cd ../hello-world-driver/
$ tree -L 1
.
├── hello_world.c
└── Makefile
-------------------------------
./ 驱动源代码文件为 hello_world.c,完整代码如下:
-------------------------------
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
static struct class *hello_class;
static struct device *hello_device;
static int __init hello_init(void)
{
int ret;
// 创建一个设备类
hello_class = class_create("hello_debug");
if (IS_ERR(hello_class)) {
ret = PTR_ERR(hello_class);
pr_err("Failed to create class: %d\n", ret);
return ret;
}
// 创建一个设备实例
hello_device = device_create(hello_class, NULL, 0, NULL, "hello%d", 0);
if (IS_ERR(hello_device)) {
ret = PTR_ERR(hello_device);
pr_err("Failed to create device: %d\n", ret);
class_destroy(hello_class);
return ret;
}
// 使用 dev_dbg 输出调试信息
dev_dbg(hello_device, "Hello, world! Debug message enabled.\n");
pr_info("Hello module loaded successfully.\n");
return 0;
}
static void __exit hello_exit(void)
{
dev_dbg(hello_device, "Goodbye, world! Debug message enabled.\n");
device_destroy(hello_class, 0);
class_destroy(hello_class);
pr_info("Hello module unloaded.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple hello-world kernel module with dev_dbg");
MODULE_AUTHOR("Toradex");
-------------------------------
### 代码创建一个 hello 设备类和实例,在正常挂载和移除时候通过 pr_info 命令来打印正常系统信息;同时通过 dev_dbg 命令来打印调试信息 ###
-------------------------------
// module loaded
dev_dbg(hello_device, "Hello, world! Debug message enabled.\n");
pr_info("Hello module loaded successfully.\n");
// module unloaded
dev_dbg(hello_device, "Goodbye, world! Debug message enabled.\n");
pr_info("Hello module unloaded.\n");
-------------------------------
./ Makefile 文件完整代码:
-------------------------------
obj-m += hello_world.o
# Linux Kernel Path(Modify accordingly)
KDIR ?= /<work_dir>/linux-toradex
all:
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean
-------------------------------
### KDIR 指定 linux-toradex Kernel 源代码路径,根据实际情况修改 ###
b). 编译并将生成的 hello_world.ko 驱动文件上传到 Verdin i.MX8MP $HOME 目录
-------------------------------
$ make
$ scp hello_world.ko root@<ip_address_verdin_imx8mp>:/home/root/
-------------------------------
c). 加载测试,由于未使能 DEBUG,因此只有 pr_info 信息被打印输出(确保Linux log level 设置要支持,详细说明可以查看这里)
-------------------------------
root@verdin-imx8mp-06849028:~# insmod hello_world.ko
[ 3746.252795] hello_world: loading out-of-tree module taints kernel.
[ 3746.261753] Hello module loaded successfully.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 3746.252795] hello_world: loading out-of-tree module taints kernel.
root@verdin-imx8mp-06849028:~# rmmod hello_world.ko
[ 3808.225680] Hello module unloaded.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 3746.252795] hello_world: loading out-of-tree module taints kernel.
-------------------------------
d). 使能静态 DEBUG 测试 (需要重新编译驱动源代码)
./ Linux Kernel 支持 debug 信息显示,需要如下配置:
-------------------------------
Kernel hacking → Kernel debugging (CONFIG_DEBUG_KERNEL=y)
Kernel hacking → Compile-time checks and compiler options Debug information → Disable debug information (CONFIG_DEBUG_INFO_NONE=y)
Kernel hacking → Debug Filesystem (CONFIG_DEBUG_FS=y)
-------------------------------
./ 编译定制驱动源码时候使能 DEBUG:
i). 在驱动源码 C 文件所有头文件声明之前增加如下声明:
-------------------------------
#define DEBUG
-------------------------------
Ii). 在 MakeFile 文件增加如下任一 CFLAGS 声明:
-------------------------------
// 针对某一个驱动文件生效
CFLAGS_hello_world.o := -DDEBUG
// 对于 Makefile 编译的所有驱动文件生效
EXTRA_CFLAGS += -DDEBUG
-------------------------------
./ 通过上述任一种方式使能 DEBUG 后加载测试,debug 信息被打印到 Linux log buffer,可以通过 dmesg 查看到:
-------------------------------
root@verdin-imx8mp-06849028:~# insmod hello_world.ko
[ 635.927571] hello_world: loading out-of-tree module taints kernel.
[ 635.934286] Hello module loaded successfully.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 635.927571] hello_world: loading out-of-tree module taints kernel.
[ 635.934274] hello_debug hello0: Hello, world! Debug message enabled.
root@verdin-imx8mp-06849028:~# rmmod hello_world.ko
[ 679.999629] Hello module unloaded.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 635.927571] hello_world: loading out-of-tree module taints kernel.
[ 635.934274] hello_debug hello0: Hello, world! Debug message enabled.
[ 679.999479] hello_debug hello0: Goodbye, world! Debug message enabled.
-------------------------------
e). 使能动态 DEBUG 测试 (无需重新编译源代码)
./ Linux Kernel 支持动态 debug 显示,和静态 debug 信息相比优势是无需重新编译驱动或者 Kernel 源代码,而且没有静态编译 debug 代码负担,只在需要显示的时候动态显示。
./ 支持 Dynamic Debug 需要使能如下 Kernel 配置,更多详细说明请见这里:
-------------------------------
Kernel hacking → Debug Filesystem (CONFIG_DEBUG_FS=y)
Kernel hacking → printk and dmesg options → Enable dynamic printk() support (CONFIG_DYNAMIC_DEBUG=y)
-------------------------------
./ 使能 Hello_world 驱动 Dynamic Debug 功能:
-------------------------------
// 如果已经默认挂载则不需要重复操作
$ mount -t debugfs none /sys/kernel/debug/
// 加载驱动,此时 DEBUG 未使能
root@verdin-imx8mp-06849028:~# insmod hello_world.ko
[ 359.310527] hello_world: loading out-of-tree module taints kernel.
[ 359.317172] Hello module loaded successfully.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 359.310527] hello_world: loading out-of-tree module taints kernel.
// Dynamic Debug 也未使能 hello_world 驱动 Debug 信息打印
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
/home/simon/local/tdx_source_local/imx8/v7.2-20250619/hello-world-driver/hello_world.c:40 [hello
_world]hello_exit =_ "Goodbye, world! Debug message enabled.\n"
/home/simon/local/tdx_source_local/imx8/v7.2-20250619/hello-world-driver/hello_world.c:32 [hello
_world]hello_init =_ "Hello, world! Debug message enabled.\n"
// 使能 Dynamic Debug
root@verdin-imx8mp-06849028:~# echo "file hello_world.c +p" > /sys/kernel/debug/dynamic_debug/co
ntrol
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
/home/simon/local/tdx_source_local/imx8/v7.2-20250619/hello-world-driver/hello_world.c:40 [hello
_world]hello_exit =p "Goodbye, world! Debug message enabled.\n"
/home/simon/local/tdx_source_local/imx8/v7.2-20250619/hello-world-driver/hello_world.c:32 [hello
_world]hello_init =p "Hello, world! Debug message enabled.\n"
// 卸载驱动,此时 Dynamic Debug 已经生效,Debug 信息已经可以打印
root@verdin-imx8mp-06849028:~# rmmod hello_world.ko
[ 400.480206] Hello module unloaded.
root@verdin-imx8mp-06849028:~# dmesg |grep hello
[ 359.310527] hello_world: loading out-of-tree module taints kernel.
[ 400.480054] hello_debug hello0: Goodbye, world! Debug message enabled.
-------------------------------
5). 定制 Linux Driver 采用集成到 Linux Kernel 源代码中一起编译部署
a). 在 Kernel 源码目录 linux-toradex 外创建定制驱动工作目录,并创建驱动源码 c 代码以及 Kconfig 和 Makefile 文件
-------------------------------
// 进入 Linux Kernel 源码
$ cd <work_dir>/linux-toradex
// 在 Character 驱动下创建 Hello_world 驱动
$ mkdr drivers/char/hello_world_driver
$ cd drivers/char/hello_world_driver/
$ tree -L 1
.
├── hello_world.c
├── Kconfig
└── Makefile
-------------------------------
./ 驱动源代码文件 hello_world.c 和章节3 中一致无需任何修改。
./ Kconfig 文件完整代码,用于 Kernel Configuration 的相关驱动配置说明:
------------------------------
#
# Hello World Driver
#
config HELLO_WORLD
tristate "Hello World Driver"
depends on ARM || ARM64
help
This is a simple hello world driver for testing.
------------------------------
./ Makefile 文件完整代码:
------------------------------
#
# Makefile for hello world driver
#
#
obj-$(CONFIG_HELLO_WORLD) += hello_world.o
# Enable Debug Info
#CFLAGS_hello_world.o := -DDEBUG
#EXTRA_CFLAGS += -DDEBUG
------------------------------
b). 修改高一级 Kconfig 和 Makefile 文件增加 Hello_world 驱动相关配置
------------------------------
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 86cb16e65d88..d9c09281c841 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -423,5 +423,6 @@ config ADI
driver include crash and makedumpfile.
source "drivers/char/imx_amp/Kconfig"
+source "drivers/char/hello_world_driver/Kconfig"
endmenu
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index b84b1a6db304..142d00e1d883 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -45,3 +45,4 @@ obj-$(CONFIG_XILLYBUS_CLASS) += xillybus/
obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o
obj-$(CONFIG_ADI) += adi.o
obj-$(CONFIG_HAVE_IMX_AMP) += imx_amp/
+obj-$(CONFIG_HELLO_WORLD) += hello_world_driver/
------------------------------
c). Built-in 方式编译,将 Hello_world 驱动编译进 Kernel Binary Image
./ 配置内核并重新编译 Kernel Image:
------------------------------
$ cd <work_dir>/linux-toradex
$ make menuconfig
Device Drivers > Character devices
<*> Hello World Driver
$ make -j$(nproc) Image.gz 2>&1 | tee build.log
------------------------------
./ 和章节 3 同样方法将 Kernel Image 上传到 Verdin i.MX8MP 并部署后重启测试,驱动加载成功:
------------------------------
root@verdin-imx8mp-06849028:~# dmesg |grep Hello
[ 1.577391] Hello module loaded successfully.
------------------------------
./ 使能静态 DEBUG 功能:
### 两种使能代码方式 ###
i). 在驱动源码 C 文件所有头文件声明之前增加如下声明:
-------------------------------
#define DEBUG
-------------------------------
Ii). 在 MakeFile 文件增加如下任一 CFLAGS 声明:
-------------------------------
// 针对某一个驱动文件生效
CFLAGS_hello_world.o := -DDEBUG
// 针对一个 Kernel 配置驱动生效
ccflags-$(CONFIG_HELLO_WORLD) += -DDEBUG
// 对于 Makefile 编译的所有配置驱动生效
EXTRA_CFLAGS += -DDEBUG
-------------------------------
### 通过上述任一种方式使能 DEBUG 后加载测试,debug 信息被打印到 Linux log buffer,可以通过 dmesg 查看到 ###
-------------------------------
root@verdin-imx8mp-06849028:~# dmesg |grep Hello
[ 1.575157] hello_debug hello0: Hello, world! Debug message enabled.
[ 1.575165] Hello module loaded successfully.
------------------------------
./ 使能动态 DEBUG 功能:
------------------------------
// 默认未使能
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
drivers/char/hello_world_driver/hello_world.c:32 [hello_world]hello_init =_ "Hello, world! Debug
message enabled.\n"
drivers/char/hello_world_driver/hello_world.c:40 [hello_world]hello_exit =_ "Goodbye, world! Deb
ug message enabled.\n"
// 通过设置 U-Boot 环境变量在启动过程使能
root@verdin-imx8mp-06849028:~# fw_setenv tdxargs 'dyndbg=\\"file hello_world.c +p;\\"'
root@verdin-imx8mp-06849028:~# fw_printenv tdxargs
tdxargs=dyndbg=\\"file hello_world.c +p;\\"
root@verdin-imx8mp-06849028:~# reboot
// 重启后 Dynamic Debug 使能
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
drivers/char/hello_world_driver/hello_world.c:32 [hello_world]hello_init =p "Hello, world! Debug
message enabled.\n"
drivers/char/hello_world_driver/hello_world.c:40 [hello_world]hello_exit =p "Goodbye, world! Deb
ug message enabled.\n"
// Debug log 打印
root@verdin-imx8mp-06849028:~# dmesg |grep Hello
[ 1.579354] hello_debug hello0: Hello, world! Debug message enabled.
[ 1.579363] Hello module loaded successfully.
------------------------------
d). Kernel Module 方式编译,将 Hello_world 驱动编译为 Kernel Module
./ 配置内核并重新编译 Kernel Image 和 Kernel Modules:
------------------------------
$ cd <work_dir>/linux-toradex
$ make menuconfig
Device Drivers > Character devices
<M> Hello World Driver
$ make -j$(nproc) Image.gz 2>&1 | tee build.log
$ make -j$(nproc) modules
------------------------------
./ 和章节 3 同样方法将 Kernel Image 和 Kernel Modules 压缩包上传到 Verdin i.MX8MP 并部署后重启测试,驱动加载成功:
------------------------------
root@verdin-imx8mp-06849028:~# modprobe hello_world
[ 69.862475] Hello module loaded successfully.
root@verdin-imx8mp-06849028:~# dmesg |grep Hello
[ 69.862475] Hello module loaded successfully.
------------------------------
./ 使能静态 DEBUG 功能,使能方式和上述 Built-in 编译方式一致,这里不再赘述。
./ 使能动态 DEBUG 功能:
------------------------------
// 默认未使能
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
drivers/char/hello_world_driver/hello_world.c:32 [hello_world]hello_init =_ "Hello, world! Debug
message enabled.\n"
drivers/char/hello_world_driver/hello_world.c:40 [hello_world]hello_exit =_ "Goodbye, world! Deb
ug message enabled.\n"
// 通过设置 U-Boot 环境变量或者 Modprobe 配置文件在加载 Hello_world 驱动过程使能
i). U-Boot 环境变量
root@verdin-imx8mp-06849028:~# fw_setenv tdxargs 'hello_world.dyndbg=\\"+p;\\"'
root@verdin-imx8mp-06849028:~# fw_printenv tdxargs
tdxargs=hello_world.dyndbg=\\"+p;\\"
ii). Modprobe 配置文件
root@verdin-imx8mp-06849028:~# vi /etc/modprobe.d/test_debug.conf
options hello_world dyndbg=+p
root@verdin-imx8mp-06849028:~# reboot
// 重启后 Dynamic Debug 使能
root@verdin-imx8mp-06849028:~# modprobe hello_world
[ 43.410229] Hello module loaded successfully.
root@verdin-imx8mp-06849028:~# cat /sys/kernel/debug/dynamic_debug/control |grep hello
drivers/char/hello_world_driver/hello_world.c:40 [hello_world]hello_exit =p "Goodbye, world! Deb
ug message enabled.\n"
drivers/char/hello_world_driver/hello_world.c:32 [hello_world]hello_init =p "Hello, world! Debug
message enabled.\n"
// Debug log 打印
root@verdin-imx8mp-06849028:~# dmesg |grep Hello
[ 43.410211] hello_debug hello0: Hello, world! Debug message enabled.
[ 43.410229] Hello module loaded successfully.
------------------------------
6). 总结
本文基于 NXP i.MX8MP 处理器平台演示了定制化 Linux Hello World 设备驱动开发编译和调试流程。