在 Linux 内核中添加自己的驱动并进行验证:从 Kconfig 配置到设备创建
在 Linux 内核中,驱动程序通常通过 Kconfig 和 Makefile 进行配置和编译,最终生成 内建驱动 或 模块驱动。关于如何在标准的内核源码中添加属于自己的驱动配置并编译和测试可以参考这位佬的文章:
Kconfig 编译驱动到内核
本文将详细介绍在将自定义的驱动程序添加到 Linux 内核,配置内核后的验证驱动是否已正确编译并创建设备文件。
概念和流程概述
在 Linux 内核中,驱动程序的工作流程大致分为两个阶段:
- 编译阶段:
- 通过 Kconfig 配置驱动是否被编译到内核中或作为模块编译。
- Makefile 控制驱动文件的编译和链接过程,生成
.ko文件(模块)或直接编译进内核。
- 运行阶段:
- 驱动程序根据设备类型(如 platform 设备、PCI 设备等)将设备注册到内核,创建
struct device对象。 - 驱动通过
probe()函数将设备与驱动程序绑定,随后可以创建设备文件供用户访问。
- 驱动程序根据设备类型(如 platform 设备、PCI 设备等)将设备注册到内核,创建
无论采用何种方式编写驱动,都有一个共同的目标:驱动能够正常加载并使设备文件在 /dev 下可见。
三种驱动编写方式
驱动编写方式通常有三种:传统方式、平台总线方式和设备树方式。以下将详细介绍每种方式的设备创建流程以及如何验证驱动是否编译成功并成功创建设备。
1. 传统方式
传统方式通常用于不依赖设备树和总线机制的简单驱动,设备通过 class_create 和 device_create 在 /sys/class 下创建,用户态通过 udev 或 mdev 在 /dev 下创建对应的设备节点。
设备创建
在驱动代码中,创建设备和注册字符设备的流程如下:
static dev_t mydev_num;
static struct class *mydev_class;
static int __init mydev_init(void)
{
int ret;
// 申请设备号
ret = alloc_chrdev_region(&mydev_num, 0, 1, "mydev");
if (ret)
return ret;
// 注册 cdev
cdev_add(&my_cdev, mydev_num, 1);
// 创建 sysfs 下的 class
mydev_class = class_create(THIS_MODULE, "mydev");
// 创建逻辑 device
device_create(mydev_class, NULL, mydev_num, NULL, "mydev0");
return 0;
}
通过上述代码,在 /sys/class/mydev/mydev0 和 /dev/mydev0 下会创建对应的设备。
验证驱动是否编译并生成设备
-
检查配置是否生效:
在内核源码目录或目标板上,查看配置是否正确:
grep CONFIG_MY_DRIVER .config # 编译前检查配置 zcat /proc/config.gz | grep CONFIG_MY_DRIVER # 编译后检查配置 -
检查内核模块是否编译:
-
如果是模块 (
CONFIG_MY_DRIVER=m),可以使用以下命令查看模块文件:find /lib/modules/$(uname -r) -name "my_driver.ko" -
如果是内建驱动 (
CONFIG_MY_DRIVER=y),则不会生成.ko文件,但可以通过以下命令检查内核模块:ls /sys/module | grep my_driver
-
-
查看驱动是否正常加载:
-
使用
lsmod检查模块是否加载:lsmod | grep my_driver -
使用
dmesg查看启动日志,确认驱动是否成功加载:dmesg | grep -i my_driver
-
-
查看设备文件是否生成:
如果是字符设备,udev会自动在/dev下创建设备文件:ls /dev | grep mydev
2. 平台总线方式
在平台总线方式下,设备通常通过 platform_device 注册,而驱动通过 platform_driver 绑定到设备。设备的创建通常在 /sys/bus/platform/devices 目录下。
设备创建
在设备树或板级代码中,创建一个平台设备:
static struct platform_device mydev_device = {
.name = "mydev",
.id = -1,
.num_resources = ARRAY_SIZE(mydev_resources),
.resource = mydev_resources,
};
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.driver = {
.name = "mydev",
},
};
module_platform_driver(mydev_driver);
内核启动时会自动加载并绑定驱动,调用 probe() 函数进行初始化。
验证驱动是否编译并生成设备
-
检查内核配置:
使用与传统方式相同的方法检查内核配置。 -
检查设备和驱动是否绑定:
查看
/sys/bus/platform/devices和/sys/bus/platform/drivers目录,确认设备和驱动是否成功绑定:ls /sys/bus/platform/devices | grep mydev ls /sys/bus/platform/drivers | grep mydev如果设备和驱动成功绑定,
/sys/bus/platform/drivers/mydev/中会出现指向设备的符号链接。 -
查看设备文件是否生成:
如果驱动成功绑定设备,可以在
/dev目录下查看设备文件:ls /dev | grep mydev
3. 设备树方式
设备树方式适用于 ARM 等嵌入式平台,设备信息通过 DTS 文件描述,内核启动时解析设备树创建设备并绑定驱动。
设备创建
设备树节点示例如下:
mydev@12340000 {
compatible = "myvendor,mydev";
reg = <0x12340000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <10>;
status = "okay";
};
驱动代码中使用 of_device_id 表示驱动与设备的匹配关系:
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "myvendor,mydev", },
{ /* sentinel */ }
};
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
};
module_platform_driver(mydev_driver);
当内核启动时,设备树中的节点会被解析为 platform_device,并与相应的驱动进行匹配,调用 probe() 函数进行初始化。
验证驱动是否编译并生成设备
-
检查设备树节点是否存在:
在目标板上,使用以下命令查看设备树是否包含你的设备:
ls /sys/firmware/devicetree/base | grep mydev -
检查平台设备和驱动是否绑定:
同平台总线方式,查看
/sys/bus/platform/devices和/sys/bus/platform/drivers目录,确认设备和驱动是否成功绑定:ls /sys/bus/platform/devices | grep mydev ls /sys/bus/platform/drivers | grep mydev -
查看设备文件是否生成:
如果设备绑定成功,可以查看
/dev目录下的设备文件:ls /dev | grep mydev
验证驱动是否成功加载
无论你采用哪种驱动方式,以下是几种通用的验证方法:
-
查看内核日志:
使用
dmesg查看内核日志,确保驱动已成功加载并执行了probe()函数:dmesg | grep -i my_driver -
查看设备文件:
查看
/dev目录下是否已创建相应的设备文件:ls /dev | grep mydev -
检查模块:
如果使用模块,使用
lsmod确认模块是否加载:lsmod | grep my_driver
总结
通过本文的讲解,你应该能够理解如何通过 Kconfig 和 Makefile 配置自己的驱动程序,并在编译内核时将其编译进内核或作为模块加载。验证驱动是否成功编译和加载,可以通过查看 /dev 下的设备文件、内核日志以及 lsmod 等工具来确认驱动是否正常运行。
无论你选择传统的方式、平台总线方式还是设备树方式,这些方法都能确保你的驱动成功加载并在系统中生成设备文件供用户使用。

422

被折叠的 条评论
为什么被折叠?



