参考:linux-platform机制实现驱动层分离(详解)
本节目标:
学习platform机制,如何实现驱动层分离
1、回顾一下上一节的输入子系统
如上图,在输入子系统中,我们通过分层及通过核心层向设备驱动层,事件处理层提供各种接口,使各层都能专注于自己的事情。
对于设备驱动层,我们会硬件相关的抽象出来,构造一个input_dev结构体,对于事件处理层,会把纯软件的比较稳定的东西抽象出来。
有了分层之后,APP就可以保持稳定·,我们只需要关心驱动层的硬件操作,注册了之后,就可以直接使用了。
同样得,我们也可以将设备驱动层中,硬件相关的易变的数据与稳定算法(改动小)的两部分分离开来。
那我们如何实现设备驱动的分离?接下来就介绍platform机制及分离的概念
2.分离的概念
分离就是在驱动层中使用platform机制把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序作变动,如点哪一个灯)分离开来,
即要编写两个文件:dev.c和drv.c(platform设备和platform驱动)
- 把硬件相关的东西抽出来,即可变的数据,具体体现在设备的差异。
- 把相对稳定的东西也抽出来,即稳定的算法,控制逻辑,可以理解成总线协议(如I2C)
优点:
- 将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理
- 使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动
3.Platform 机制
platform会存在/sys/bus/里面
如下图所示, platform目录下会有两个文件,分别就是platform设备和platform驱动
1) device设备
挂接在platform总线下的设备, 使用结构体platform_device描述
2) driver驱动
挂接在platform总线下,是个与某种设备相对于的驱动, 使用结构体platform_driver描述
3) platform总线
是个全局变量,为platform_bus_type,属于虚拟设备总线,通过这个总线将设备和驱动联系起来,属于Linux中bus的一种
以下依次介绍其数据结构:
1)设备描述—platform_device
platform_device_register
注册设备文件,匹配后会调用驱动算法的probe
platform_device_unregister
卸载设备- 具体数据描述如下
1 struct platform_device { 2 const char * name; //设备名称,要与platform_driver的name一样,这样总线才能匹配成功 3 u32 id; //插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1 4 struct device dev; //具体的device结构体 5 //成员platform_data可以给平台driver提供各种数据(比如:GPIO引脚等等) 6 u32 num_resources;//资源数目 7 struct resource * resource;//资源描述,用来描述io,内存等 8 }; 9 10 //资源文件 ,定义在includelinuxioport.h 11 struct resource { 12 resource_size_t start; //起始资源,如果是地址的话,必须是物理地址 13 resource_size_t end; //结束资源,如果是地址的话,必须是物理地址 14 const char *name; //资源名 15 unsigned long flags; //资源类型,可以是io/irq/mem等 16 struct resource *parent, *sibling, *child; //链表结构,可以构成链表 17 }; 18 // type 19 #define IORESOURCE_IO 0x00000100 /* Resource type */ 20 #define IORESOURCE_MEM 0x00000200 21 #define IORESOURCE_IRQ 0x00000400 22 #define IORESOURCE_DMA 0x00000800
2).驱动算法—platform_driver
这里将其称为驱动算法,其实也是一个正常的驱动程序,区别在于可以获取到上面注册的设备文件信息.
当注册设备文件或者注册驱动文件的时候,都会调用驱动算法的 probe.
platform_driver_register
用来注册驱动,当注册设备文件或者注册驱动文件的时候,都会调用驱动算法的probe.platform_driver_unregister
用来卸载驱动-
platform
框架中的管理结构如下:
1 //driver 2 struct platform_driver { 3 //函数 4 (*probe) //匹配到设备文件的name与driver中的name一致将会在注册时执行 5 (*remove) //卸载驱动出口,卸载驱动设备的时候应该也会调用 6 (*shutdown) 7 (*suspend) 8 (*suspend_late) 9 (*resume_early) 10 (*resume) 11 //结构体 12 struct device_driver driver 13 { 14 .name //用作匹配 15 } 16 };
probe
,当有设备文件和驱动算法匹配的时候自动执行,一般用来获取资源文件信息等,注册驱动,ioremap
等,可以理解为执行驱动的第一个程序- 总线在匹配设备和驱动之后驱动要考虑一个这样的问题,设备对应的软件数据结构代表着静态的信息,真实的物理设备此时是否正常还不一定,因此驱动需要探测这个设备是否正常。我们称这个行为为probe,至于如何探测,那是驱动才知道干的事情,总线只管吩咐(摘自:嵌入式企鹅圈)
-
Driver->probe()
I.探测设备是否正常
II.cdev_add(struct file_operations)注册操作接口
III. device_create()创建设备文件
注意:
- 这里的
platform_driver
也是一个普通的驱动,可以用最原始的方式来注册驱动,创建class
类,再在下面添加具体的设备文件来使用mdev
机制自动创建所需要的文件. - 也可以使用输入子系统的 框架,输入子系统所需要的驱动程序需要一套约定的接口,我们可以将输入子系统中的左侧的设备驱动再分割成设备描述与驱动算法.
3)Platform 总线
platform_bus_type的结构体定义如下所示(位于drivers/base):
1 struct bus_type platform_bus_type = { 2 .name = "platform", //设备名称 3 .dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下 4 .match = platform_match, //匹配设备和驱动,匹配成功就调用driver的.probe函数 5 .uevent = platform_uevent, //消息传递,比如热插拔操作 6 .suspend = platform_suspend, //电源管理的低功耗挂起 7 .suspend_late = platform_suspend_late, 8 .resume_early = platform_resume_early, 9 .resume = platform_resume, //恢复 10 };
驱动、设备注册匹配图如下所示:
只要有一方注册,就会调用platform_bus_type的.match匹配函数,来找对方,成功就调用driver驱动结构体里的.probe函数来使总线将设备和驱动联系起来
注册机制(drivers/base/Platform.c)
1.device 使用 platform_device_register 注册
1 int platform_device_register(struct platform_device * pdev) 2 { 3 device_initialize(&pdev->dev); //初始化 4 return platform_device_add(pdev); 5 } //最终会进入device_add(&pdev->dev),放入平台总线的设备链表中
2.驱动算法使用platform_driver_register
来注册,如下:
1 int platform_driver_register(struct platform_driver *drv)
2 {
3 drv->driver.bus = &platform_bus_type; //(1)挂接到虚拟总线platform_bus_type上
4 if (drv->probe)
5 drv->driver.probe = platform_drv_probe;
6 if (drv->remove)
7 drv->driver.remove = platform_drv_remove;
8 if (drv->shutdown)
9 drv->driver.shutdown = platform_drv_shutdown;
10 if (drv->suspend)
11 drv->driver.suspend = platform_drv_suspend;
12 if (drv->resume)
13 drv->driver.resume = platform_drv_resume;
14
15 return driver_register(&drv->driver); //(2) 注册到driver目录下,创建/sys/bus/platform/driver
16 }
1). 从代码上看,platform
只是只是bus
中的一种,挂载到platform_bus_type
1 struct bus_type platform_bus_type = { 2 .nam = "platform", //设备名称 3 .dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下 4 .match = platform_match, //匹配函数,成功调用driver的.probe函数 5 .uevent = platform_uevent, //消息传递,比如热插拔操作 6 .suspend = platform_suspend, //电源管理的低功耗挂起 7 .suspend_late = platform_suspend_late, 8 .resume_early = platform_resume_early, 9 .resume = platform_resume, //恢复 10 };
2). 可以看到匹配函数match=platform_match
,是匹配dev
和driver
的name
1 static int platform_match(struct device * dev, struct device_driver * drv) 2 { 3 /*找到所有的device设备*/ 4 struct platform_device *pdev = container_of(dev, struct platform_device, dev); 5 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); 6 }
4. 使用platform机制,编写LED驱动层
首先创建设备代码和驱动代码:led_dev.c 、led_drv.c
led_dev.c用来指定灯的引脚地址,当更换平台时,只需要修改这个就行
led_drv.c用来初始化灯以及如何控制灯的逻辑,当更换控制逻辑时,只需要修改这个就行
4.1 led_dev.c
1)先写要注册的led设备:platform_device结构体
2)完成结构体中的函数
3)入口出口函数
1 /* 分配/设置/注册一个platform_device */ 2 #include <linux/module.h> 3 #include <linux/version.h> 4 5 #include <linux/init.h> 6 7 #include <linux/kernel.h> 8 #include <linux/types.h> 9 #include <linux/interrupt.h> 10 #include <linux/list.h> 11 #include <linux/timer.h> 12 #include <linux/init.h> 13 #include <linux/serial_core.h> 14 #include <linux/platform_device.h> 15 16 17 18 /* 可以在resource中更改寄存器与引脚 */ 19 static struct resource led_resource[] = { //资源数组 20 [0] = { 21 .start = 0x56000050, //led寄存器GPFCON的起始地址 22 .end = 0x56000050 + 8 - 1, //led寄存器GPFDAT的结束地址--为什么减1 23 .flags = IORESOURCE_MEM, //表示地址资源 24 }, 25 26 [1] = { 27 .start = 5, //表示GPF从第几个引脚开始 28 .end = 5, //结束引脚 29 .flags = IORESOURCE_IRQ, //表示中断资源 30 } 31 32 }; 33 34 static void led_release(struct device * dev) //释放函数 35 {} 36 37 38 /* 1)先写要注册的led设备:platform_device结构体 */ 39 static struct platform_device led_dev = { 40 .name = "myled", //对应的platform_driver驱动的名字 41 .id = -1, //表示只有一个设备 42 .num_resources = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量 43 .resource = led_resource, //资源数组led_resource 44 .dev = { 45 .release = led_release, //释放函数,必须向内核提供一个release函数, 、 46 //否则卸载时,内核找不到该函数会报错 47 }, 48 }; 49 50 51 static int led_dev_init(void) //入口函数,注册dev设备 52 { 53 platform_device_register(&led_dev); 54 return 0; 55 } 56 57 static void led_dev_exit(void) //出口函数,卸载dev设备 58 { 59 platform_device_unregister(&led_dev); 60 } 61 62 63 module_init(led_dev_init); //修饰入口函数 64 module_exit(led_dev_exit); //修饰出口函数 65 66 MODULE_LICENSE("GPL"); //声明函数
4.2 led_drv.c
1)先写要注册的led驱动:platform_driver结构体
2)写file_operations 结构体、以及成员函数(.open、.write)、.probe函数、
当驱动和设备都insmod加载后,然后bus总线会匹配成功,就进入.probe函数,
在.probe函数中便使用platform_get_resource()函数获取LED的地址和引脚,然后初始化LED,并注册字符设备和设备节点"led"
1 /* 分配/设置/注册一个platform_driver */ 2 #include <linux/module.h> 3 #include <linux/version.h> 4 5 #include <linux/init.h> 6 #include <linux/fs.h> 7 #include <linux/interrupt.h> 8 #include <linux/irq.h> 9 #include <linux/sched.h> 10 #include <linux/pm.h> 11 #include <linux/sysctl.h> 12 #include <linux/proc_fs.h> 13 #include <linux/delay.h> 14 #include <linux/platform_device.h> 15 #include <linux/input.h> 16 #include <linux/irq.h> 17 #include <asm/uaccess.h> 18 #include <asm/io.h> 19 20 static int major; 21 22 static struct class *cls; 23 static volatile unsigned long *gpio_con; 24 static volatile unsigned long *gpio_dat; 25 static int pin; 26 27 static int led_open(struct inode *inode, struct file *file) 28 { 29 //printk("first_drv_open "); 30 /* 配置为输出引脚 */ 31 *gpio_con &= ~(0x3<<(pin*2)); 32 *gpio_con |= (0x1<<(pin*2)); 33 return 0; 34 } 35 36 static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) 37 { 38 int val; 39 /*获取用户传进来的值*/ 40 copy_from_user(&val, buf, count); 41 42 if (val == 1) 43 { 44 //点灯 45 *gpio_dat &= ~(1<<pin); 46 } 47 else 48 { 49 //灭灯 50 *gpio_dat |= (1<<pin); 51 } 52 53 return 0; 54 } 55 58 static struct file_operations led_fops = { 59 .owner = THIS_MODULE, /* 宏,指向编译模块时自动创建的_this_moudle变量 */ 60 .open = led_open, 61 .write = led_write, 62 }; 63 64 /* probe实现的功能完全可以自定 */ 65 static int led_probe(struct platform_device *pdev) 66 { 67 struct resource *res; 68 /* 根据platform_device的资源进行ioremap */ 69 /* 获取寄存器资源 */ 70 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取寄存器地址 71 gpio_con = ioremap(res->start, res->end - res->start + 1); //起始地址,映射大小->获取虚拟地址 72 gpio_dat = gpio_con + 1; //指针加1 = 地址加4,指向另一个寄存器 73 74 /* 获取引脚资源 */ 75 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获取寄存器地址 76 pin = res->start; 77 78 /* 注册字符设备驱动程序 */ 79 printk("led_probe, found led "); 80 81 major = register_chrdev( 0, "myled", &led_fops); //注册告诉内核,注册后返回主设备号 82 83 cls = class_create(THIS_MODULE, "myled"); 84 class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ 85 86 return 0; 87 } 88 89 static int led_remove(struct platform_device *pdev) 90 { 91 /* 卸载字符设备驱动程序 */ 92 /* */ 93 printk("remove ok! "); 94 /* 与上面反着来 */ 95 class_device_destroy(cls, MKDEV(major, 0)); 96 class_destroy( cls); 97 unregister_chrdev(major, "myled"); 98 iounmap(gpio_con); 102 } 103 104 struct platform_driver led_drv = { 105 .probe = led_probe, //当与设备匹配,调用此函数 106 .remove = led_remove, //删除设备 107 .driver = { 108 .name = "myled", //名字相同才匹配 109 } 110 }; 111 112 113 static int led_drv_init(void) //入口函数,注册dev设备 114 { 115 platform_driver_register(&led_drv); 116 return 0; 117 } 118 119 static void led_drv_exit(void) //出口函数,卸载dev设备 120 { 121 platform_driver_unregister(&led_drv); 122 } 123 124 125 module_init(led_drv_init); //修饰入口函数 126 module_exit(led_drv_exit); //修饰出口函数 127 128 MODULE_LICENSE("GPL"); //声明函数
4.3 led_test.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 6 /* leddrvtest on 7 * leddrvtest off 8 */ 9 int main(int argc, char **argv) 10 { 11 int fd; 12 int val = 1; 13 fd = open("/dev/led", O_RDWR); 14 if (fd < 0) 15 { 16 printf("can't open! "); 17 } 18 if (argc != 2) 19 { 20 printf("Usage : "); 21 printf("%s <on|off> ", argv[0]); 22 return 0; 23 } 24 25 if (strcmp(argv[1], "on") == 0) 26 { 27 val = 1; 28 } 29 else 30 { 31 val = 0; 32 } 33 34 write(fd, &val, 4); 35 return 0; 36 }
- 注册两个驱动,会自动生成文件
/sys/bus/platform/
下的devices
和drives
生成驱动 - 测试使用
./led_test on
点灯, 使用 ./led_test off 关灯