《嵌入式操作系统》_设备驱动模型_总线式设备_20260329
1.什么叫linux设备驱动模型?
1.1 驱动模型有四个概念
类class
总线bus
设备device
驱动driver
1.2 kobject和对象生命周期
kobject:所有驱动抽象出来的基类
驱动中存在机制使得驱动不需要的时候可以自己释放掉自己
1.3 sysfs
将驱动中一些硬件信息以文件的形式展现出来,在用户空间就可以操控或者读取硬件
1.4 udev
为了实现内核空间与用户空间的通信,用户空间可以迅速知道内核空间发生的事件。
2. 为什么需要设备驱动模型?
早期内核(2.4)根本就没有驱动模型,但也能用。
后期内核(2.6)正式引入设备驱动模型,因为设备越来越多,而且有功耗的要设备驱动模型负责
我们需要统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施求
设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
3. 驱动开发的两个重点
1.驱动本身干活代码的编写,重点在硬件,有哪些寄存器需要修改和读取
2.驱动什么时候要被安装和调用的问题,重点在设备驱动模型。
4. 设备驱动的底层架构
4.1 kobject
该结构体为所有设备类驱动的对象的基类。位于linux/kobject.h

提供的服务:对象引用计数、维护对象链表、对象上锁、对用户空间的表示
list_head:内核链表,平行驱动的连接
kobject * parent:上下级驱动的连接
krf:引用计数,帮助维护对象的生命周期,归零释放。
kset:对象上锁
sd:对用户空间的表示
4.2 kobj_type
每个kobject都包绑定了一个kobj_type,提供/sys/目录下的操作

release:释放方法
sys_ops:操作方法
attribute:提供应用接口
4.3 kset
将kobject组织成一个目录关系
5. 总线式设备
usb、pcie、i2c
内核根本不管底层的usb设备,他负责usb总线和pcie总线之类的。
由usb总线驱动负责usb设备的安装卸载,驱动管理
5.1 总线bus_type
总线类结构体模版,依旧变量+函数指针

bus_attribute:总线本身自己的属性。
device_attribute:进入device文件夹后里面的属性
driver_attribute:进入driver文件夹后里面的属性
match:设备匹配驱动函数
dev_pm_ops:电源管理集合
5.2 设备device
struct device:硬件设备在内核驱动框架中的抽象类,一般会被包含,比如struct usb_devices

5.3 驱动device_driver
device_driver:设备驱动结构体。
名字(name),驱动与设备的匹配就是依靠名字。
探测函数(probe),用以检测一个设备是否可以被该驱动管理。

5.4 类class、class_devices
struct class 和struct class_devices,用以提供一些功能。对设备进行一个分类管理
设备被总线、类、多重管理。
6. platform平台总线
这个东西不像usb这种有真实的物理总线,他是虚拟的。这类设备没有专用的外设接口,使用cpu的地址总线连接的,所以我们虚拟出来一个platform平台总线,对应soc内部集成的各种内部外设
工作体系:

=========================================================================
cpu与外界的通信原理大致可以分两类:
1.地址总线接口,32位接口,0-(2^32)-1个地址,也就是4G。寄存器、内存、网卡。你直接访问地址就行。
2.专用类接口,硬盘,sd卡,usb
=========================================================================
6.1 设备结构体与驱动结构体
platform_device和platform_driver

dev:通用的设备属性
resource:用到的资源,比如中断号和gpio,是个指针,就是说有可能是数组(首地址)
u32 num_resource:用到的资源的个数
platform_device_id:支持的设备id表,放了兼容的设备。
archdata:自留地,存放专用数据,提供一种扩展性。

全是函数指针。
probe:驱动探索函数,其实干的设备初始化的工作,返回正常那就是能用
remove:卸载一个设备,sys目录下已经没有了
shutdown:关闭一个设备
suspend:挂起一个设备
resume:恢复设备,唤醒设备
device_driver:驱动的通用特性
platform_device_id:驱动支持的设备id表
6.2 注册函数
platform_device_register和platform_driver_register


6.3 平台总线的工作原理
1.内核系统启动时在bus系统中注册platform(不用我们干)
2.内核移植需要提供了platform_device(没人干我们就得干)一般在kernel/arch/arm/mach-xxx中
触摸屏ts设备:

lcd背光设备:

注册:

3.写驱动的提供platform_driver
背光的驱动和注册:

4.platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装。然后设备开始工作。
6.4 怎么写驱动
写一个设备结构体,写一个驱动结构体,注册设备、注册驱动
6.5 代码分析
platform系统注册
入口函数:在bus系统中注册platform总线系统

error = device_register(&platform_bus);
在设备文件夹中注册一个平台总线目录
error = bus_register(&platform_bus_type);
在bus系统中注册platform总线系统
platform_bus_type:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
每种总线都会有自己的匹配(match)方法。但一般都是看名字匹配的。
ok不分析了,直接写代码
7. 平台总线实现
无敌麻烦,兄弟!!
设备(修改内核):
1.结构体头文件
自己写一个用来描述设备非通用信息的结构体,由于device结构体中的platform成员是void*类型,该指针可以指向任意数据类型。选好路径放好。
#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"
#define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */
struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
#endif /* __ASM_ARCH_LEDSGPIO_H */
2.定义设备描述
在mach-x210中有对platform设备的数据信息。我们需要将自己的platform设备写在这里。先添加设备,注意设备名一定要与驱动的名字相同。其dev成员的platform_data成员连接到下一步添加的非通用信息。
static struct platform_device x210_led1 = {
.name = "lxy210_led",
.id = 0,
.dev = {
.platform_data = &(x210_led1_platdata),
},
};
static struct platform_device x210_led2 = {
.name = "lxy210_led",
.id = 1,
.dev = {
.platform_data = &(x210_led2_platdata),
},
};
static struct platform_device x210_led3 = {
.name = "lxy210_led",
.id = 2,
.dev = {
.platform_data = &(x210_led3_platdata),
},
};
3.填充设备自留数据/属性
这些都是设备的非通用信息。
static struct s5pv210_led_platdata x210_led1_platdata = {
.name = "led1",
.gpio = S5PV210_GPJ0(3),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct s5pv210_led_platdata x210_led2_platdata = {
.name = "led2",
.gpio = S5PV210_GPJ0(4),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct s5pv210_led_platdata x210_led3_platdata = {
.name = "led3",
.gpio = S5PV210_GPJ0(5),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
4.添加进初始化表

这个指针数组包含了内核初始化时初始化的platform设备,我们将下面自己的设备添加到这个数组中。完成设备的添加。
&x210_led1,
&x210_led2,
&x210_led3,
5.头文件
别忘了把自己写的头文件包含进来。
#include <mach/leds-gpio.h>
驱动(修改外部代码):
1. 修改驱动初始化、退出函数
static int lxy210_led_init(void)
{
return platform_driver_register(&lxy210_led_driver);
}
static void lxy210_led_exit(void)
{
platform_driver_unregister(&lxy210_led_driver);
}
2. 定义驱动结构体
这个驱动需要支撑3个led设备
static struct platform_driver lxy210_led_driver = {
.probe = lxy210_led_probe,
.remove = lxy210_led_remove,
.driver = {
.name = "lxy210_led",
.owner = THIS_MODULE,
},
};
3. 创建led结构体
考虑到一个驱动需要带3个led,考虑给lxy_led_set和gpio_request函数的输入输出。
我们需要新定义一个结构体包含cdev和platdata(自留数据)。
to_gpio函数用以在设置led电平的时候获取gpio号,卸载驱动时获取gpio号
pdev_to_gpio函数用以注销led_class时获取cdev
struct s5pv210_gpio_led{
struct led_classdev cdev; // 包含一个led_classdev结构体
struct s5pv210_led_platdata *platdata; // 指向平台数据的指针
};
static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}
4. 填充驱动初始化函数
static int lxy210_led_probe(struct platform_device *dev)
{
struct s5pv210_led_platdata *platdata = dev->dev.platform_data;
printk(KERN_INFO "Probing LED device: %s\n", platdata->name);
struct s5pv210_gpio_led *led_gpio;
led_gpio = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
if (!led_gpio) {
printk(KERN_ERR "Failed to allocate memory for LED GPIO structure\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led_gpio); // 将led_gpio结构体指针存储在平台设备的驱动数据中
if (gpio_request(platdata->gpio, platdata->name) < 0) {
printk(KERN_ERR "Failed to request GPIO for %s\n", platdata->name);
return -EBUSY;
}
else{
printk(KERN_INFO "Successfully requested GPIO for %s\n", platdata->name);
}
gpio_direction_output(platdata->gpio, 1); // 将GPIO设置为输出,并初始值为0(LED打开)
led_gpio->cdev.name = platdata->name; // 设置LED设备的名称
led_gpio->cdev.brightness = LED_OFF; // 设置LED设备的初始亮度为关闭
led_gpio->cdev.brightness_set = lxy_led_set; // 设置LED亮度设置函数
led_gpio->platdata = platdata; // 存储平台数据指针
if (led_classdev_register(&dev->dev, &led_gpio->cdev) < 0) { // 注册LED设备
printk(KERN_ERR "Failed to register LED device\n");
gpio_free(platdata->gpio); // 释放GPIO资源
kfree(led_gpio); // 释放分配的内存
return -EINVAL;
}
else{
printk(KERN_INFO "Successfully registered LED device\n");
}
return 0;
}
5. 填充卸载函数
记得要free内存
static int lxy210_led_remove(struct platform_device *dev)
{
struct s5pv210_gpio_led *led_gpio = pdev_to_gpio(dev);
struct s5pv210_led_platdata *platdata = led_gpio->platdata;
gpio_free(platdata->gpio); // 释放GPIO资源
kfree(led_gpio); // 释放分配的内存
led_classdev_unregister(&led_gpio->cdev); // 注销LED设备
printk(KERN_INFO "Removed LED device: %s\n", platdata->name);
return 0;
}
6.头文件
#include <linux/platform_device.h> // platform_driver_register platform_driver_unregister
#include <mach/leds-gpio.h> // s5pv210_led_platdata结构体定义
#include <linux/slab.h> // kzalloc kfree
效果(点灯工程师石锤):
设备:
[root@LXY210 lxy210_led.0]# ls /sys/bus/platform/devices/
lxy210_led.0
lxy210_led.1
lxy210_led.2
驱动:
[root@LXY210 lxy210_led.0]# ls /sys/bus/platform/drivers/lxy210_led/
lxy210_led.0 lxy210_led.1 lxy210_led.2
设备文件:
[root@LXY210 lxy210_led.0]# ls /sys/class/leds/
led1 led2 led3 mmc0:: mmc1:: mmc2:: mmc3::
至此,linux平台总线设备驱动学习完美完成
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)