1.总线设备驱动的概念以及总线上注册设备和驱动(非设备树)
Linux kernel 字符驱动框架
总线、设备、驱动 => 增加驱动的可扩展性和可移植性
思想:
将设备的信息从驱动中分离出来,我们需要在操作系统中,添加设备和驱动两部分。
设备中包含是设备的信息(资源),驱动中包含的是操作设备函数接口。
为了能让驱动最终能操作我们的硬件设备,我们在驱动中必须获取设备的信息(资源)。
问题:设备和驱动是分离的,那么驱动是如何获取具体设备信息的呢?
回答:
设备和驱动都会注册到总线上,当注册设备的时候,会去寻找同名的驱动
当注册驱动的时候,也会去找同名的设备。相互查找。一旦匹配成功,操作
系统就会自动调用驱动提供的probe函数。我们只需要在probe函数中,使用
操作系统提供的通用API获取硬件的信息即可。
问题:Linux 系统中"总线"如何理解?
回答:
在实际的硬件上,总线种类可以分成两大类
1.平台总线 (CPU核与硬件控制器之间的通信),挂载都是控制器设备
platform bus
2.边缘设备之间通信的总线,挂载是符合总线时序的外围设备
i2c , spi ,usb , uart ....
不同的边缘设备之间通信的总线,总线时序是不一样的,
对于这些总线,Linux 内核是单独实现的
总线在操作系统中本质就是两个链表:挂载设备的链表和挂载驱动的链表
问题:在Linux 2.6内核之后,应该如何写驱动?
回答:
根据驱动的设备,给予总线来写驱动
问题:如何给予总线来写驱动?
回答:
(1)根据自己的设备,来确定总线的类型
platform bus / i2 bus /usb bus/....
(2)根据总线的类型, 确定设备在总线上如何描述(结构体)
struct platform_device{
设备名;
设备的资源
通用的设备描述(struct device : platform_data->记录设备私有的信息)
id_entry:当设备和驱动的id_table中某一个成员匹配上的时候,这个成员就会记录id_table中匹配上的成员地址
};
struct resource{
资源的开始
资源的结束
资源的类型:IO资源(寄存器地址) 中断资源(中断号) DMA资源(通道)
};
(3)根据总线的类型, 确定驱动在总线上如何描述
struct platform_driver{
probe函数 :设备和驱动匹配的时候,操作系统自动调用
remove函数:设备和驱动分离的时候,操作系统自动调用
通用的驱动描述(struct device_driver : 这里面可以记录驱动的名字)
id_table : 当前驱动支持平台设备(记录支持的平台设备名字)
};
(4)根据总线的类型,确定在总线上如何注册设备 i2c控制器驱动
int platform_device_register(struct platform_device *pdev);
(5)根据总线的类型,确定在总线上如何注册驱动 i2c_add_driver
int platform_driver_register(struct platform_driver *pdriver);
(6)根据总线的类型, 确定设备和驱动匹配原则
如果驱动提供了id_table,那就拿设备的名字和id_table中记录的名字匹配
如果驱动没有提供id_table,那就拿设备的名字和驱动的名字进行匹配
(7)一旦设备和驱动匹配后,操作系统就会调用驱动提供的probe函数。
在这个函数中,一般需要做两件事情:
<1>获取匹配的硬件资源
<2>注册字符设备(可选)
问:驱动和设备匹配上之后,驱动如何从能获取设备的信息?
答:驱动和设备匹配上之后,操作系统会自动调用驱动提供的probe函数,并且会把设备的结构体传递过来
,这个结构体中记录设备的所有信息
--------------------------------------------------------------------------------------
1.fs4412 上的LED设备对应的总线类型是什么?
platform bus
2.在这个总线上,设备用什么结构体描述?
struct platform_device{
char *name;
int id;
struct device dev;//platform_data 这个指针设备的私有资源信息
int number_resources;
struct resource *resource;
....
};
struct resource{
int start;
int end;
int flags;
...
};
例如:fs4412 LED资源描述
struct resource fs4412_led_resource[] = {
[0] = {
.start = 0x11000C40,
.end = 0x11000C40 + 8 - 1,
.flags = IORESOURCE_MEM,
},
};
struct platform_device fs4412_device_led = {
.name = "fs4412-led",
.id = -1,
.num_resources = ARRAY_SIZE(fs4412_led_resource),
.resource = fs4412_led_resource,
};
3.在这个总线上,驱动用什么结构体描述?
struct platform_driver{
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_table,
.driver = {
.name = "fs4412-led",
.owner = THIS_MODULE,
},
};
struct platform_device_id xxx_driver_ids[] = {
{
.name = "xxx",
.driver_data = 0;
},
....
{},
};
4.在这个总线上,如何注册设备?
int platform_device_register(struct platform_device *dev);
5.在这个总线上,如何注册驱动?
int platform_driver_register(struct platform_driver *drv);
6.在这个总线上,驱动和设备的匹配规则是怎样的?
如果驱动有id_table,则用设备的名字和id_table记录的名字比较
如果驱动没有id_table,则用设备的名字和驱动的名字进行比较
问:id_table是什么鬼?
答:驱动可以支持设备名字列表,实际上一个数组,记录是一些字符串
问:如果设备和id_table的成员匹配上,这个时候做一些以什么?
答:platform_device结构体中的id_entry记录id_table成员的地址。
7.在这个总线上,驱动和设备匹配后,调用的probe函数中,如何获取硬件资源?
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type,int num);
@dev 平台设备的结构体
@type 资源的类型
@num 同类型资源的编号
练习:基于platform bus编写LED驱动
(1)注册设备
1.定义设备结构体
struct platform_device fs4412_led_device
2.填充设备结构体
<1>设备的名字
<2>设备的资源
3.注册
platform_device_register
(2)注册驱动
1.定义驱动结构体
struct platform_driver led_driver
2.填充驱动结构体
<1>驱动名字
<2>probe函数
[1]获取设备的资源
[2]注册字符设备
<3>remove函数
<4>id_table (可选)
3.注册
platform_driver_register
-----------------------------------------------------------------------------------------------------
给予总线写驱动思路
1.模块化编程
<1>头文件
<2>许可权限
<3>模块的入口和出口
2.在总线上注册驱动
<1>填充结构体
struct platform_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_table,
.driver = {
.owner = THIS_MODULE,
.name = "xxxx",
},
};
<2>注册结构体
在模块的入口函数注册结构体
platform_driver_register
在模块的出口函数注销结构体
platform_driver_unregister
3.总线上的设备和驱动相互匹配后,操作系统自动调用驱动提供的probe函数
实现probe函数
int xxx_probe(struct platform_device *pdev)
{
1.获取资源
platform_get_resource
2.注册字符设备
[1]初始化cdev结构体 (让cdev结构体记录设备的操作函数接口)
cdev_init
[2]申请设备号
第一种方式 (静态):
register_chrdev_region
第二种方式 (动态):
alloc_chrdev_region
[3]添加字符设备
cdev_add
----------------自动创建设备节点------------------------------------------
[4]创建类
class_create
[5]创建设备
device_create
}
4.总线上的设备和驱动分离后,操作系统自动调用驱动提供的remove函数
实现remove函数
int xxx_remove(struct platform_device *pdev)
{
核心思想:将probe函数申请的资源,释放掉
<1>删除设备
device_destory
<2>删除类
class_destory
<3>删除字符设备
cdev_del
<4>释放设备号
unregister_chrdev_region