1.先来看看我们之前分析输入子系统的分层概念,如下图所示:

  

 

 

  如上图所示,分层就是将一个复杂的工作分成了4层, 分而做之,降低难度,每一层专注于自己的事情, 系统只将其中的核心层和事件处理层写好了,所以我们只需要来写驱动层即可,接下来我们来分析platform机制以及分离概念。

  

2.分离概念 

优点:

  • 将所有设备挂接到一个虚拟的总线上,方便sysfs节点和设备电源的管理
  • 使得驱动代码,具有更好的扩展性和跨平台性,就不会因为新的平台而再次编写驱动

介绍:

  分离就是在驱动层中使用platform机制把硬件相关的代码(固定的,如板子的网卡、中断地址)和驱动(会根据程序作变动,如点哪一个灯)分离开来,即要编写两个文件:dev.c和drv.c(platform设备和platform驱动)。

 

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的一种。

 

  该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函数来使总线将设备和驱动联系起来。

 

4.实例分析driver驱动 

 

  我们以/drivers/input/keybard/gpio_keys.c内核自带的示例程序为例,

  它的代码中只有driver驱动,因为是个示例程序,所以没有device硬件设备代码。

4.1发现在gpio_keys.c中有1个全局变量driver驱动:

  1. struct platform_driver gpio_keys_device_driver = { //定义一个platform_driver类型驱动
  2. .probe = gpio_keys_probe, //设备的检测,当匹配成功就会调用这个函数(需要自己编写)
  3. .remove = __devexit_p(gpio_keys_remove), //删除设备(需要自己编写)
  4. .driver = {
  5. .name = "gpio-keys", //驱动名称,用来与设备名称匹配用的
  6. }
  7. };

 

4.2然后来找找这个gpio_keys_device_driver被谁用到

  发现在驱动层init入口函数中通过platform_driver_register()来注册diver驱动。

  在驱动层exit出口函数中通过platform_driver_unregister()函数来注销diver驱动。

  1. static int __init gpio_keys_init(void) //init出口函数
  2. {
  3. return platform_driver_register(&gpio_keys_device_driver); //注册driver驱动
  4. }
  5. static void __exit gpio_keys_exit(void) //exit出口函数
  6. {
  7. platform_driver_unregister(&gpio_keys_device_driver); //注销driver驱动
  8. }

 

4.3我们进来platform_driver_register(),看它是如何注册diver的,注册到哪里?

  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. return driver_register(&drv->driver); //(2) 注册到driver目录下
  15. }

 

(1) 挂接到虚拟总线platform_bus_type上,然后会调用platform_bus_type下的platform_match匹配函数,来匹配device和driver的名字,其中driver的名字如下图所示:

  

platform_match()匹配函数如下所示:

  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); //找BUS_ID_SIZE次
  6. }

  若名字匹配成功,则调用device的.probe成员函数。

(2)然后放到/sys/bus/platform/driver目录下,其中driver_register()函数就是用来创建dirver目录的。

 

5. 使用platform机制,编写LED驱动层

 

  首先创建设备代码和驱动代码:led_dev.c 、led_drv.c。

  led_dev.c用来指定灯的引脚地址,当更换平台时,只需要修改这个就行。

  led_drv.c用来初始化灯以及如何控制灯的逻辑,当更换控制逻辑时,只需要修改这个就行 。

 

6.编写led.dev.c

  6.1编写led_dev.c之前先来看看platform_device结构体和要使用的函数:

   platform_device结构体如下:

  1. struct platform_device {
  2. const char * name; //设备名称,要与platform_driver的name一样,这样总线才能匹配成功
  3. u32 id; //id号,插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1
  4. struct device dev; //内嵌的具体的device结构体,其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)
  5. u32 num_resources; //资源数量,
  6. struct resource * resource; //资源结构体,保存设备的信息
  7. };

  其中resource资源结构体,如下:

  1. struct resource {
  2. resource_size_t start; //起始资源,如果是地址的话,必须是物理地址
  3. resource_size_t end; //结束资源,如果是地址的话,必须是物理地址
  4. const char *name; //资源名
  5. unsigned long flags; //资源的标志
  6. //比如IORESOURCE_MEM,表示地址资源, IORESOURCE_IRQ表示中断引脚... ...
  7.  
  8. struct resource *parent, *sibling, *child; //资源拓扑指针父、兄、子,可以构成链表
  9. };

  要用的函数如下,在dev设备的入口出口函数中用到

  1. int platform_device_register(struct platform_device * pdev); //注册dev设备
  2. int platform_device_register(struct platform_device * pdev); //注销dev设

6.2接下来开始写代码

1)先写要注册的led设备:platform_device结构体

  1. #include <linux/module.h>
  2. #include <linux/version.h>
  3. #include <linux/init.h>
  4. #include <linux/kernel.h>
  5. #include <linux/types.h>
  6. #include <linux/interrupt.h>
  7. #include <linux/list.h>
  8. #include <linux/timer.h>
  9. #include <linux/init.h>
  10. #include <linux/serial_core.h>
  11. #include <linux/platform_device.h>
  12.  
  13. static struct resource led_resource[] = { //资源数组
  14. [0] = {
  15. .start = 0x56000050, //led的寄存器GPFCON起始地址
  16. .end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT结束地址
  17. .flags = IORESOURCE_MEM, //表示地址资源
  18. },
  19. [1] = {
  20. .start = 5, //表示GPF第几个引脚开始
  21. .end = 5, //结束引脚
  22. .flags = IORESOURCE_IRQ, //表示中断资源
  23. }
  24. };
  25. static void led_release(struct device * dev) //释放函数
  26. {}
  27. static struct platform_device led_dev = {
  28. .name = "myled", //对应的platform_driver驱动的名字
  29. .id = -1, //表示只有一个设备
  30. .num_resources = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量
  31. .resource = led_resource, //资源数组led_resource
  32. .dev = {
  33. .release = led_release, //释放函数,必须向内核提供一个release函数, 、
  34. //否则卸载时,内核找不到该函数会报错
  35. },
  36. };

2)最后写出口入口函数

  1. static int led_dev_init(void) //入口函数,注册dev设备
  2. {
  3. platform_device_register(&led_dev);
  4. return 0;
  5. }
  6. static void led_dev_exit(void) //出口函数,注销dev设备
  7. {
  8. platform_device_unregister(&led_dev);
  9. }
  10. module_init(led_dev_init); //修饰入口函数
  11. module_exit(led_dev_exit); //修饰出口函数
  12. MODULE_LICENSE("GPL"); //声明函数

 

7.编写led.drv.c

7.1编写led_dev.c之前先来看看platform_device结构体和要使用的函数:

  1. struct platform_driver {
  2. int (*probe)(struct platform_device *); //查询设备的存在
  3. int (*remove)(struct platform_device *); //删除
  4. void (*shutdown)(struct platform_device *); //断电
  5. int (*suspend)(struct platform_device *, pm_message_t state); //休眠
  6. int (*suspend_late)(struct platform_device *, pm_message_t state);
  7. int (*resume_early)(struct platform_device *);
  8. int (*resume)(struct platform_device *); //唤醒
  9. struct device_driver driver; //内嵌的driver,其中的name成员要等于设备的名称才能匹配
  10. };
  11. int platform_driver_register(struct platform_driver *drv); //注册驱动
  12. platform_driver_unregister(struct platform_driver *drv); //卸载驱动
  13.  
  14. struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num);
  15. //获取设备的某个资源,获取成功,则返回一个resource资源结构体
  16. //参数:
  17. // *dev :指向某个platform device设备
  18. // type:获取的资源类型
  19. // num: type资源下的第几个数组

7.2接下来开始写代码

1)先写要注册的led驱动:platform_driver结构体

  1. /*函数声明*/
  2. static int led_remove(struct platform_device *led_dev);
  3. static int led_probe(struct platform_device *led_dev);
  4. struct platform_driver led_drv = {
  5. .probe = led_probe, //当与设备匹配,则调用该函数
  6. .remove = led_remove, //删除设备
  7. .driver = {
  8. .name = "myled", //与设备名称一样
  9. }
  10. };

2)写file_operations 结构体、以及成员函数(.open、.write)、.probe函数、

  当驱动和设备都insmod加载后,然后bus总线会匹配成功,就进入.probe函数,

  在.probe函数中便使用platform_get_resource()函数获取LED的地址和引脚,然后初始化LED,并注册字符设备和设备节点”led”。  

  1. static struct class *cls; //类,用来注册,和注销
  2. static volatile unsigned long *gpio_con; //被file_operations的.open函数用
  3. static volatile unsigned long *gpio_dat; //被file_operations的.write函数用
  4. static int pin; //LED位于的引脚值
  5.  
  6. static int led_open(struct inode *inode, struct file *file)
  7. {
  8. *GPFcon&=~(0x03<<(LED_PIN*2));
  9. *GPFcon|=(0x01<<(LED_PIN*2));
  10. return 0;
  11. }
  12. static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
  13. {
  14. int val=0;
  15. if(count!=1)
  16. return -EINAL;
  17. copy_from_user(&val,buf,count); //从用户(应用层)拷贝数据
  18.  
  19. if(val) //开灯
  20. {
  21. *GPFdat&=~(0x1<<LED_PIN);
  22. }
  23. else
  24. {
  25. *GPFdat |= (0x1<<LED_PIN);
  26. }
  27. return 0 ;
  28. }
  29. static struct file_operations led_fops= {
  30. .owner = THIS_MODULE, //被使用时阻止模块被卸载
  31. .open = led_open,
  32. .write = led_write,
  33. };
  34. static int led_probe(struct platform_device *pdev)
  35. {
  36. struct resource *res;
  37. printk("enter probe\n");
  38. /* 根据platform_device的资源进行ioremap */
  39. res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取寄存器地址
  40. gpio_con = ioremap(res->start, res->end - res->start + 1); //获取虚拟地址
  41. gpio_dat = gpio_con + 1;
  42. res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获取引脚值
  43. pin = res->start;
  44. /* 注册字符设备驱动程序 */
  45. major = register_chrdev(0, "myled", &led_fops); //赋入file_operations结构体
  46. cls = class_create(THIS_MODULE, "myled");
  47. class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
  48. return 0;
  49. }

3)写.remove函数

  如果驱动与设备已联系起来,当卸载驱动时,就会调用.remove函数卸载设备

  和.probe函数一样,注册了什么就卸载什么便可

  1. static int led_remove(struct platform_device *pdev)
  2. {
  3. /* 卸载字符设备驱动程序 */
  4. printk("enter remove\n");
  5. class_device_destroy(cls, MKDEV(major, 0));
  6. class_destroy(cls);
  7. unregister_chrdev(major, "myled");
  8. iounmap(gpio_con); //注销虚拟地址
  9. return 0;
  10. }

4)最后写drv的入口出口函数

  1. static int led_drv_init(void) //入口函数,注册驱动
  2. {
  3. platform_driver_register(&led_drv);
  4. return 0;
  5. }
  6. static void led_drv_exit(void) //出口函数,卸载驱动
  7. {
  8. platform_driver_unregister(&led_drv);
  9. }
  10. module_init(led_drv_init);
  11. module_exit(led_drv_exit);
  12. MODULE_LICENSE("GPL");

 

8.测试运行

1)如下图,我们先挂载dev设备模块,和我们之前分析的一样,它在platform/devices目录下生成一个”myled”设备

 

2)如下图,我们再来挂载drv驱动模块,同样的在platform/drivers目录下生成一个”myled”驱动,devices目录下的”myled”设备匹配成功,进入.probe函数创建设备,接下来就可以使用应用程序来控制led灯了 

 

 3)如下图,卸载驱动时,也会进入.remove函数卸载设备

 

版权声明:本文为zhuangquan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zhuangquan/p/11659863.html