linux驱动开发8设备驱动模型
1.什么是设备驱动模型
1)类class、总线bus、设备device、驱动driver。驱动模型中四个概念,也是四个结构体。不管是什么设备,都可以用struct device来表示。用这些结构体来代码设备的实例。
2)kobject和对象生命周期,Kobject是非常抽象的一个结构体,是所有的东西形成一个对象,抽象成一个共同的东西。声明周期的自动释放。
3)sysfs
4)udev
上面的四个东西就是设备驱动模型!
2.为什么需要设备驱动模型
1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用
2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施
4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
3.驱动开发的2个点
1)驱动源码本身编写、调试。重点在于对硬件的了解。
2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。
4.设备驱动模型的底层架构
struct kobject {
const char *name; kobject的名称,它将以一个目录的形式出现在sysfs文件系统中
struct list_head entry; list_head入口,用于将该kobject链接到所属kset的链表,内核链表
struct kobject *parent; kobject结构的父节点
struct kset *kset; kobject所属的kset
struct kobj_type *ktype; kobject相关的操作函数和属性。
struct sysfs_dirent *sd; kobject对应的sysfs目录
struct kref kref; kobject的引用计数,本质上是atomic_t变量
unsigned int state_initialized:1; 为1代表kobject已经初始化过了
unsigned int state_in_sysfs:1; kobject是否已经在sysfs文件系统建立入口
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
linux存在一个虚拟文件系统/sysfs,内核启动挂接文件系统后会将sysfs文件系统挂接到/sys下。内核启动时会初始化并注册一些总线、设备,这些总线、设备等会在sys下创建目录,来存储,如/sys/bus/platform 下存储了平台设备信息。如何组织管理设备,并创建目录呢,通过设备驱动模型的几个重要结构体:Kobject、kobj_type、kset来组织和管理目录及文件结构。
5.kobject
1)定义在linux/kobject.h中
2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示
3)设备驱动模型中的各种对象其内部都会包含一个kobject
4)地位相当于面向对象体系架构中的总基类
6.kobj_type
1)很多书中简称为ktype,每一个kobject都需要绑定一个ktype来提供相应功能
2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)
3)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口
7.kset
1)kset的主要作用是做顶层kobject的容器类
2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
8.总线式设备驱动组织方式
8.1总线
1)物理上的真实总线及其作用(英文bus)
2)驱动框架中的总线式设计,插上USB之后,会去USB总线框架下去找驱动,如果找到,注册。
3)bus_type结构体,关键是match函数和uevent函数。 总线对应一个bus类型的结构体变量。
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p;
};
8.2设备
1)struct device是硬件设备在内核驱动框架中的抽象
2)device_register用于向内核驱动框架注册一个设备
3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
8.3驱动
1)struct device_driver是驱动程序在内核驱动框架中的抽象
2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
3)关键元素2:probe,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理
8.4类
1)相关结构体:struct class 和 struct class_device
2)udev的使用离不开class
3)class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。
8.5总结
1)模型思想很重要,其实就是面向对象的思想
2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高
9.何为平台总线
1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的。
2)回顾裸机中讲的,CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。
3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?
有些设备没有总线的,有些有,比较杂乱,不便于管理。因此发明一个平台总线,来整体管理有无总线的设备。如果没有总线的设备可以虚拟出来一个总线。便于管理。平台总线主要借用总线的思路,与其他总线的分析和思路是一样的。
10.平台总线下管理的2员大将
1)platform工作体系都定义在drivers/base/platform.c中
2)两个结构体:platform_device和platform_driver
3)两个接口函数:platform_device_register和platform_driver_register
struct platform_device {
const char * name; // 平台总线下设备的名字
int id;
struct device dev; // 所有设备通用的属性部分
u32 num_resources; // 设备使用到的resource的个数
struct resource * resource; // 设备使用到的资源数组的首地址
const struct platform_device_id *id_entry; // 设备ID表
/* arch specific additions */
struct pdev_archdata archdata; // 自留地,用来提供扩展性的
};
struct platform_driver {
int (*probe)(struct platform_device *); // 驱动探测函数
int (*remove)(struct platform_device *); // 去掉一个设备
void (*shutdown)(struct platform_device *); // 关闭一个设备
int (*suspend)(struct platform_device *, pm_message_t state);//挂起一个设备
int (*resume)(struct platform_device *);
struct device_driver driver; // 所有设备共用的一些属性
const struct platform_device_id *id_table; // 设备ID表
};
11.平台总线体系的工作流程
1)第一步:系统启动时在bus系统中注册platform
2)第二步:内核移植的人负责提供platform_device
3)第三步:写驱动的人负责提供platform_driver
4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
13.代码分析:platform本身注册
1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的。
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
2)platform_match函数就是平台总线的匹配方法。该函数的工作方法是:如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了,如果找完id_table都还没找到就说明每匹配上;如果没有id_table或者每匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* match against the id table first */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
14.以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
1)platform_driver_register
2)platform_device_register
15.platdata怎么玩
1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称••••)
2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。
16.match函数的调用轨迹
17.probe函数的功能和意义
18.先初步改造添加platform_driver
1)第1步:先修改原来的代码到只有led1
2)第2步:添加probe和remove
19.检查mach-x210.c中是否有led相关的platform_device
20.参考mach-mini2440.c中添加led的platform_device定义
21.测试只有platform_device没有platform_driver时是怎样的
22.测试platform_device和platform_driver相遇时会怎样
23.probe函数
1)probe函数应该做什么
2)probe函数的数据从哪里来
3)编程实践
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0 // 所以1是灭,0是亮
struct s5pv210_gpio_led {
struct led_classdev cdev;
struct s5pv210_led_platdata *pdata;
};
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);
}
// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct s5pv210_gpio_led *p = to_gpio(led_cdev);
printk(KERN_INFO "s5pv210_led_set\n");
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
if (value == LED_OFF)
{
// 用户给了个0,希望LED灭
gpio_set_value(p->pdata->gpio, X210_LED_OFF);
}
else
{
// 用户给的是非0,希望LED亮
gpio_set_value(p->pdata->gpio, X210_LED_ON);
}
}
static int s5pv210_led_probe(struct platform_device *dev)
{
// 用户insmod安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
int ret = -1;
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
struct s5pv210_gpio_led *led;
printk(KERN_INFO "----s5pv210_led_probe---\n");
led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led);
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(pdata->gpio, pdata->name))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 设置为输出模式,并且默认输出1让LED灯灭
gpio_direction_output(pdata->gpio, 1);
}
// led1
led->cdev.name = pdata->name;
led->cdev.brightness = 0;
led->cdev.brightness_set = s5pv210_led_set;
led->pdata = pdata;
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static int s5pv210_led_remove(struct platform_device *dev)
{
struct s5pv210_gpio_led *p = pdev_to_gpio(dev);
led_classdev_unregister(&p->cdev);
gpio_free(p->pdata->gpio);
kfree(p); // kfee放在最后一步
return 0;
}
static struct platform_driver s5pv210_led_driver = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston <[email protected]>"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息