目录
device:负责提供硬件资源driver:负责初始化设备以及提供一些操作方式bus:负责管理挂载对应总线的设备以及驱动,匹配设备和驱动,它维护着两个链表,里面记录着各个已 经注册的设备和驱动,可以物理存在,也可以虚拟。
Linux设备模型为这三种对象各自定义了对应的类:struct bus_type代表总线,struct device代表设备,struct device_driver代表驱动,这三者都内嵌了strcut kobject或struct
kset,于是会生成对应的总线,设备,驱动的目录。
设备专门用来描述设备所占有的资源信息,而驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备的资源改变后,只是设备改变而已,驱动的代码可以不做任何修改,这就大大提高了驱动代码的通用性。总线就是联系两者的桥梁。
struct platform_device {const char * name ; // 设备名int id ;bool id_auto ;struct device dev ;u32 num_resources ; // 设备资源数量struct resource * resource ; // 设备资源const struct platform_device_id * id_entry ;struct mfd_cell * mfd_cell ;struct pdev_archdata archdata ;};
struct platform_driver {int ( * probe )( struct platform_device * ); // 匹配成功执行 probeint ( * remove )( struct platform_device * ); // 卸载时执行 removevoid ( * 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 ;bool prevent_deferred_probe ;};
struct of_device_id led_of_matches [] = {{. compatible = "fs" , "myled" }, // 通过设备树匹配};
struct platform_driver mydriver = {. probe = xxx_probe ,. remove = xxx_remove ,. driver = {. name = "mytest" , // 直接通过 name 匹配. of_match_table = of_match_ptr ( led_of_matches ), // 通过设备树进行匹配},};
struct platform_device mydev = {. name = "mytest" , // 需要与驱动中的名字匹配. num_resources = ARRAY_SIZE ( myresource ),. resource = myresource ,};
platform_driver_register(drv); //注册平台驱动
//drv:platform_driver对象
//返回值:成功返回0
void platform_driver_unregister(struct platform_driver *pdev); //注销平台驱动 //pdev:platform_driver对象
module_platform_driver( platform_driver); //(注销+注册)//platform_driver:platform_driver对象
int platform_device_register(struct platform_device *); //注册平台设备 //platform_device:platform_device对象
//返回值:成功返回0
void platform_device_unregister(struct platform_device *); //注销平台设备 //platform_device:platform_device对象
// 硬件资源描述struct resource xxxresouce [] = {[ 0 ] = {. start = 0x11000c40 ,. end = 0x11000c40 + 3 ,. flags = IORESOURCE_MEM ,},[ 1 ] = {. start = 168 ,. flags = IORESOURCE_IRQ ,}};
获取硬件资源
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
//platform_device:platform_device对象
//type:资源类型
//num: resource index
//返回值:成功返回资源,失败返回NULL
创建设备文件
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char fmt, ...);
//class:该设备依附的类
//parent:父设备
//devt:设备号(此处的设备号为主次设备号)
//drvdata:私有数据
//fmt:设备名。 (自动创建在 /dev目录下) //返回值:成功返回struct device, 失败返回 ERR_PTR() on error
创建class
class_create(owner, name)
//owner通常为THIS_MODULE
//name为类名 (自动创建在sys/class目录下)
//返回值:成功返回struct class*, 失败返回 ERR_PTR() on error
void device_destroy(struct class *dev, dev_t devt); //销毁device
//dev: calss对象
//devt:设备号
extern void class_destroy(struct class *cls); //销毁class
//dev: calss对象
通过这个模型我们可以对之前的字符设备驱动框架进行修改
平台驱动代码:
- ...........................
- //定义platform_driver对象
- static struct platform_driver mydriver ={
- .probe = my_probe,
- .remove = my_remove,
- .driver = {
- .name = "mytest", //通过名字匹配
- },
- };
-
- int my_probe(struct platform_device *pdev)
- {
- u32 val;
- int ret;
- //获取硬件资源
- rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
-
- //字符设备注册
- ret = alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号
- if(ret!=0){
- goto failed_alloc;
- }
- cdev_init(&mycdev,&myops); //2.cdev初始化
-
- ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核
- if(ret!=0){
- goto failed_add;
- }
- printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));
-
- myclass = class_create(THIS_MODULE,"myclass");
- if(IS_ERR(myclass)){
- goto failed_class;
- }
- mydevice = device_create(myclass,NULL,devnum,NULL,"led2");
- if(IS_ERR(mydevice)){
- goto failed_device;
- }
- //硬件操作
-
-
- }
- int my_remove(struct platform_device *pdev)
- {
- ........
- return 0;
-
- }
-
- ...........
-
- static int mod_init(void)
- {
- return platform_driver_register(&mydriver); //平台驱动注册
- }
- static void mod_exit(void)
- {
- platform_driver_unregister(&mydriver); //平台驱动注销
- }
-
- module_init(mod_init);
- module_exit(mod_exit);
- MODULE_LICENSE("GPL");
平台设备代码:
- //描述硬件资源
- struct resource myresource[]={
- [0] = {
- .start = 0x11000C20,
- .end = 0x11000C20+3,
- .flags = IORESOURCE_MEM
- },
- [1] = {
- .start = 0x11000C24,
- .end = 0x11000C24+3,
- .flags = IORESOURCE_MEM
- },
-
- };
- void my_release(struct device *dev)
- {
- printk("device release\n");
- return ;
- }
- //定义platform_device对象
- struct platform_device mydev = {
- .name = "mytest", //通过名字匹配
- .num_resources = ARRAY_SIZE(myresource),
- .resource = myresource,
- .dev = {
- .release = my_release,
- },
- };
-
-
- static int mod_init(void)
- {
- return platform_device_register(&mydev); //平台设备注册
- }
- static void mod_exit(void)
- {
- platform_device_unregister(&mydev); //平台设备注销
- }
- module_init(mod_init);
- module_exit(mod_exit);
- MODULE_LICENSE("GPL");
-
在后面引入了设备树的概念后我们平台设备代码这块就可以不再使用了。
在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):
Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。
设备树的基本单元是节点(node),这些node被组织成树状结构,除了root node,每个node都只有一个parent node,一个device tree文件中只能有一个root node。每个node中包含了若干的键值对(property/value)来描述该node的一些特性。每个node用节点名字标识。(类似于linux文件系统)
/{ //根节点
Property = value; //描述信息
[@ ]{ //子节点 Property = value //描述信息
};
……
};
节点名字的格式是[@]。如果该node没有reg属性,那么该节点名字中不能包括@unit-address。unit-address的具体格式是和设备挂在哪个bus上相关。例如对于CPU,其unit-address就是从0开始编址,以此加1,而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。根节点的节点名是确定的,必须是“/”。
设备树语法中已经定义好的,具有通用规范意义的属性如果是设备信息和驱动分离框架的设备节点,则能够在内核初始化找到节点时候,自动解析生成相应的设备信息。
常见属性的有: compatible、地址address、中断interrupt
简单举例:
compatible属性:用于匹配设备节点和设备驱动的属性,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致。 compatible=“厂商名,设备名” ;
设备树键值对相关语法
[1]. 字符串信息
compatible = "随风,飘落";
[2]. 32位无符号整形数组
word-array = <32 45 67 89>;
reg = <0x10001000 0x24 0x20001000 0x24>;
[3]. 二进制数组
bi-array = [0c 20 11 24];
mac = [FE 02 11 CB 40 58];
[4]. 字符数组
string-list = "aaa" , "bbb" , "ccc";
在引入设备树的概念后,我们可以对前面的代码进行优化了,不再需要编写平台设备的代码。
那接下来我展示一段从设备树获取资源,来操控led灯的代码,和之前接口编程类似,我们需要先通过芯片手册找到控制led灯的管脚和操纵2该管脚的寄存器地址,然后修改对应的设备树内容:
然后我们开始编写我们的平台驱动代码框架:
- int my_probe(struct platform_device *pdev);
- int my_remove(struct platform_device *pdev);
-
- static struct resource *rescon;//接收状态寄存器的资源
- static struct resource *resdata;//接收数据寄存器的资源
-
- unsigned int *gpx1con;
- unsigned int *gpx1data;
-
- //定义platform_driver对象
- struct of_device_id of_matches[]={
- {.compatible="fs,myled"}, //名字要与设备树中的一样
- {},
- };
- static struct platform_driver mydriver ={
- .probe = my_probe,
- .remove = my_remove,
- .driver = {
- .name = "myled",
- .of_match_table = of_matches, //通过设备树匹配
- },
-
- };
-
- int my_probe(struct platform_device *pdev)
- {
- u32 val;
- int ret;
- //通过设备树获取状态寄存器资源
- rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
- if(rescon==NULL){
- goto failed_getcon;
- }
- printk("%#x\n",rescon->start);
- gpx1con = ioremap(rescon->start,4);//映射
- //通过设备树获取数据寄存器资源
- resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
- if(resdata==NULL){
- goto failed_getdata;
- }
- printk("%#x\n",resdata->start);
- gpx1data = ioremap(resdata->start,4);
-
- //字符设备注册
-
- //硬件操作
-
-
- }
- int my_remove(struct platform_device *pdev)
- {
- printk("driver remove\n");
- //解除映射
- iounmap(gpx1con);
- iounmap(gpx1data);
- .................
- //注销字符设备
- //。。。。。。
- return 0;
- }
- static int mod_init(void)
- {
- return platform_driver_register(&mydriver); //平台驱动注册
- }
- static void mod_exit(void)
- {
- platform_driver_unregister(&mydriver); //平台驱动注销
- }
-
- module_init(mod_init);
- module_exit(mod_exit);
- MODULE_LICENSE("GPL");
1.实现入口函数 xxx_init() 和卸载函数 xxx_exit()
2.申请设备号 register_chrdev_region (与内核相关)
3.注册字符设备驱动 cdev_alloc,cdev_init,cdev_add (与内核相关)
4.利用 udev/mdev 机制创建设备文件(节点) class_create,device_create(与内核相关)
5.硬件部分初始化
IO 资源映射 ioremap ,内核提供 gpio库函数(与硬件相关)
注册中断(与硬件相关)
初始化等待队列(与内核相关)
初始化定时器(与内核相关)
6.构建 file_operation 结构(与内核相关)
7.实现操作硬件的方法 xxx_open,xxx_read,xxx_write…(与硬件相关)