一、杂项设备
1、设备驱动三大分类
- 字符设备
- 块设备
- 网络设备
普通字符设备的注册流程,太过繁琐
- 自己分配或申请主设备号,确保设备在系统中的唯一标识
- 初始化 cdev 结构体,并与 file_operations 绑定
- 将 cdev 结构体注册到内核
- 注册设备类(class),用于在 /sys/class 下创建对应的目录
- 手动创建 /dev/ 下的设备节点,方便用户空间程序访问
2、杂项设备
是字符设备的简化版本,属于字符设备的子集。
所有杂项设备都属于字符设备,但字符设备不一定是杂项设备。
- 主设备号固定为10
- 次设备号可以指定,也可用 MISC_DYNAMIC_MINOR 让内核自
- 动分配
- 设备节点由内核自动在 /dev 下生成,无需手动创建
- 注册接口是 misc_register(),代码量小,流程简单
- 适用于简单设备:LED、按键、看门狗等
- 不适用于复杂设备:高速、大数据量、需要缓存的场景
- 识别方法:ls -l 查看主设备号是否为10,或查看 /proc/misc
3、杂项设备示例(LED举例)
一、杂项设备 vs 普通字符设备
- 主设备号:普通字符设备需要动态或静态分配,杂项设备固定为10
- 次设备号:普通字符设备自己管理,杂项设备可自动分配或指定
- /dev/节点:普通字符设备需要手动或通过class创建,杂项设备自动创建
- 代码量:普通字符设备较多,杂项设备简洁
- 适合设备:普通字符设备适合复杂设备(如串口、USB),杂项设备适合简单设备(如LED、按键、蜂鸣器)
二、普通字符设备驱动代码框架
/* 定义文件操作函数集 */
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = demo_read,
.write = demo_write,
};
/* 定义cdev和设备号 */
static struct cdev cdev;
static dev_t dev_num;
static struct class *cls;
/* 模块初始化 */
static int __init demo_init(void)
{
/* 动态分配设备号 */
alloc_chrdev_region(&dev_num, 0, 1, "demo");
/* 初始化cdev并绑定fops */
cdev_init(&cdev, &fops);
cdev_add(&cdev, dev_num, 1);
/* 创建设备类和设备节点 */
cls = class_create(THIS_MODULE, "demo_class");
device_create(cls, NULL, dev_num, NULL, "demo");
return 0;
}
/* 模块退出 */
static void __exit demo_exit(void)
{
/* 注销设备号和cdev */
unregister_chrdev_region(dev_num, 1);
cdev_del(&cdev);
/* 销毁设备和类 */
device_destroy(cls, dev_num);
class_destroy(cls);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
三、杂项设备驱动核心代码
/* 定义miscdevice结构体 */
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, // 自动分配次设备号
.name = "misc_led", // 设备名 /dev/misc_led
.fops = &fops, // 文件操作函数集
};
/* 注册杂项设备 */
misc_register(&misc);
/* 注销杂项设备 */
misc_deregister(&misc);
四、完整的杂项设备驱动模板
#include <linux/miscdevice.h>
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_led",
.fops = &fops,
};
static int __init led_init(void)
{
return misc_register(&misc);
}
static void __exit led_exit(void)
{
misc_deregister(&misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
五、在Kconfig中添加
config MISC_LED
tristate "This is misc led driver"
default y
六、模块加载命令
- insmod misc_led.koc 加载模块
- lsmod 查看已加载模块
- rmmod misc_led 卸载模块
二、Platform
1、什么是Platform总线
- Linux内核中的一种虚拟总线,用于管理不需要PCI、USB、I2C等传统总线的设备
- 设备直接集成在SoC内部或固定在特定内存地址上,如GPIO控制器、UART、I2C控制器、看门狗、RTC、LED、按键
- 将硬件资源信息与驱动逻辑分离,传统方式需要在驱动中写死硬件地址,更换板卡必须修改驱动源码
- Platform总线支持一个驱动管理多个相同类型的设备
2、两个核心结构体
-
platform_device:描述硬件资源(寄存器地址、中断号、GPIO)
-
platform_driver:实现硬件操作逻辑(probe、remove等)
-
两者通过name字段进行匹配,匹配成功自动调用probe
/* platform_device核心结构 */
static struct resource led_res[] = {
[0] = {
.start = 0x12345678,
.end = 0x1234567b,
.flags = IORESOURCE_MEM,
},
};static struct platform_device led_device = {
.name = "my_led",
.id = 0,
.num_resources = ARRAY_SIZE(led_res),
.resource = led_res,
};/* platform_driver核心结构 */
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "my_led",
},
};
3、probe函数的核心作用
-
获取硬件资源
-
初始化硬件
-
注册杂项设备或字符设备,生成/dev节点
static int led_probe(struct platform_device pdev)
{
/ 获取硬件资源 */
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/* 注册杂项设备 */ misc_register(&misc); return 0;}
4、Platform总线与杂项设备的关系
- Platform总线是设备和驱动的匹配机制
- 杂项设备是字符设备的简化类型
- 两者配合使用:Platform驱动的probe函数中注册杂项设备
5、注意事项
- device.c和driver.c是分开的
- 添加完device和driver后,记得修改Kconfig和Makefile
- 然后执行make modules编译
- 进入内核加载这两个模块
- 在应用层写一个ledapp测试即可
三、设备树
1、设备树的作用
上面我们编写的device.c是过时的操作,设备树源文件(DTS)是现代Linux内核中用来替代devices.c的方式
2、两种方式的对比
devices.c方式:硬件信息通过C代码静态定义struct resource和struct platform_device,调用platform_device_register提交给内核
设备树方式:硬件信息写在.dts文本文件中,内核启动时解析,动态创建出完全相同的struct platform_device对象
3、具体操作
把devices.c的信息都写到.dts中,然后把driver.c改写一下
4、设备树编写步骤
从内核中找到默认的设备树源文件进行修改,最好把默认的dts拷贝一份,在中间添加一个我们写的led设备树描述
修改设备树的makefile,让dts能够被编译进去
内核顶层目录输入make xxx.dtb编译设备树
把编译出来的设备树拷到tftp中启动
5、驱动中获取设备树信息
需要用到struct device_node *pnode
of_find_node_by_path():找到节点对应的设备树描述信息
of_property_read_string():读取字符串的值
of_property_read_u32_array():读取数组的值
6、测试
写好驱动后,在根文件系统中加载模块,使用应用层写好的ledapp测试
7、遇到的问题及解决办法
加载驱动模块时报错加载不进去:设备树源文件的设备节点不要写成别人的子节点,写到了别人的大括号里面成了子节点,所以无法加载模块
make dtb要到内核顶层目录执行