《驱动学习 - platform机制实现驱动层分离》

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):

struct bus_type platform_bus_type = {
.name              = "platform",             //设备名称
.dev_attrs        = platform_dev_attrs,                 //设备属性、含获取sys文件名,该总线会放在/sys/bus下
.match              = platform_match,   //匹配设备和驱动,匹配成功就调用driver的.probe函数
.uevent             = platform_uevent,  //消息传递,比如热插拔操作
.suspend         = platform_suspend,     //电源管理的低功耗挂起
.suspend_late          = platform_suspend_late,
.resume_early         = platform_resume_early,
.resume           = platform_resume,    //恢复
};

  驱动、设备注册匹配图如下所示:

  只要有一方注册,就会调用platform_bus_type的.match匹配函数,来找对方,成功就调用driver驱动结构体里的.probe函数来使总线将设备和驱动联系起来。

4.实例-分析driver驱动 

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

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

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

struct platform_driver gpio_keys_device_driver = {  //定义一个platform_driver类型驱动

    .probe      = gpio_keys_probe,                //设备的检测,当匹配成功就会调用这个函数(需要自己编写)
    .remove     = __devexit_p(gpio_keys_remove), //删除设备(需要自己编写)
    .driver     = {
            .name  = "gpio-keys",               //驱动名称,用来与设备名称匹配用的
         }
};

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

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

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

static int __init gpio_keys_init(void)    //init出口函数
{
   return platform_driver_register(&gpio_keys_device_driver);   //注册driver驱动
}

static void __exit gpio_keys_exit(void)   //exit出口函数
{
   platform_driver_unregister(&gpio_keys_device_driver);   //注销driver驱动
}

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

  platform_driver_register()函数如下:

int platform_driver_register(struct platform_driver *drv)
{
   drv->driver.bus = &platform_bus_type;                //(1)挂接到虚拟总线platform_bus_type上
   if (drv->probe)
         drv->driver.probe = platform_drv_probe;
   if (drv->remove)
        drv->driver.remove = platform_drv_remove;
   if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
   if (drv->suspend)
        drv->driver.suspend = platform_drv_suspend;
    if (drv->resume)
        drv->driver.resume = platform_drv_resume;

   return driver_register(&drv->driver);              //(2) 注册到driver目录下
}

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

  

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

static int platform_match(struct device * dev, struct device_driver * drv)
{
/*找到所有的device设备*/
struct platform_device *pdev = container_of(dev, struct platform_device, dev);

return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次
}

  若名字匹配成功,则调用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结构体如下:

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

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

struct resource {
         resource_size_t start;                    //起始资源,如果是地址的话,必须是物理地址
         resource_size_t end;                      //结束资源,如果是地址的话,必须是物理地址
         const char *name;                         //资源名
         unsigned long flags;                      //资源的标志
         //比如IORESOURCE_MEM,表示地址资源, IORESOURCE_IRQ表示中断引脚... ...

         struct resource *parent, *sibling, *child;   //资源拓扑指针父、兄、子,可以构成链表
};

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

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

6.2接下来开始写代码

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

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>

static struct resource led_resource[] = {               //资源数组
    [0] = {
        .start = 0x56000050,                     //led的寄存器GPFCON起始地址
        .end   = 0x56000050 + 8 - 1,     // led的寄存器GPFDAT结束地址
        .flags = IORESOURCE_MEM,      //表示地址资源
    },
    [1] = {
        .start =  5,                                   //表示GPF第几个引脚开始
        .end   = 5,                            //结束引脚
        .flags = IORESOURCE_IRQ,     //表示中断资源
    }
};

static void led_release(struct device * dev)       //释放函数
{}

static struct platform_device led_dev = {
    .name         = "myled",                    //对应的platform_driver驱动的名字
    .id       = -1,                                    //表示只有一个设备
    .num_resources    = ARRAY_SIZE(led_resource),        //资源数量,ARRAY_SIZE()函数:获取数量
    .resource     = led_resource,      //资源数组led_resource
    .dev = {
    .release = led_release,                 //释放函数,必须向内核提供一个release函数, 、
                                       //否则卸载时,内核找不到该函数会报错
       },
};

2)最后写出口入口函数

static int led_dev_init(void)    //入口函数,注册dev设备
{
  platform_device_register(&led_dev);
  return 0;
}

static void led_dev_exit(void) //出口函数,注销dev设备
{
  platform_device_unregister(&led_dev);
}
module_init(led_dev_init);   //修饰入口函数
module_exit(led_dev_exit);  //修饰出口函数
MODULE_LICENSE("GPL");   //声明函数

7.编写led.drv.c

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

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 (*suspend_late)(struct platform_device *, pm_message_t state);
       int (*resume_early)(struct platform_device *);
       int (*resume)(struct platform_device *);           //唤醒
       struct device_driver driver;       //内嵌的driver,其中的name成员要等于设备的名称才能匹配

};

int platform_driver_register(struct platform_driver *drv);     //注册驱动
platform_driver_unregister(struct platform_driver *drv);    //卸载驱动

struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num);
//获取设备的某个资源,获取成功,则返回一个resource资源结构体
//参数:
// *dev :指向某个platform device设备
// type:获取的资源类型
// num: type资源下的第几个数组

7.2接下来开始写代码

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

/*函数声明*/
static  int  led_remove(struct platform_device *led_dev);
static  int led_probe(struct platform_device *led_dev);

struct platform_driver led_drv = {
       .probe           = led_probe,        //当与设备匹配,则调用该函数
       .remove         = led_remove,             //删除设备

       .driver            = {
              .name     = "myled",           //与设备名称一样
       }
};

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

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

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

static struct class *cls;                                      //类,用来注册,和注销
static volatile unsigned long *gpio_con;         //被file_operations的.open函数用
static volatile unsigned long *gpio_dat;          //被file_operations的.write函数用
static int pin;                                                 //LED位于的引脚值

static int led_open(struct inode *inode, struct file  *file)
 {
       *GPFcon&=~(0x03<<(LED_PIN*2));
       *GPFcon|=(0x01<<(LED_PIN*2));
       return 0;
 } 

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
       int val=0;
       if(count!=1)
              return -EINAL;
       copy_from_user(&val,buf,count);      //从用户(应用层)拷贝数据       

       if(val)      //开灯
       {
       *GPFdat&=~(0x1<<LED_PIN);
       }
       else
       {
       *GPFdat |= (0x1<<LED_PIN);
       }
       return 0 ;
}

static struct  file_operations led_fops= {
    .owner  =   THIS_MODULE,     //被使用时阻止模块被卸载
    .open   =   led_open,
    .write   =   led_write,
  };

static int led_probe(struct platform_device *pdev)
{
       struct resource      *res;
       printk("enter probe\n");

       /* 根据platform_device的资源进行ioremap */
       res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取寄存器地址
       gpio_con = ioremap(res->start, res->end - res->start + 1); //获取虚拟地址
       gpio_dat = gpio_con + 1;

       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);   //获取引脚值
       pin = res->start;

       /* 注册字符设备驱动程序 */
       major = register_chrdev(0, "myled", &led_fops);   //赋入file_operations结构体
       cls = class_create(THIS_MODULE, "myled");
       class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
       return 0;
}

3)写.remove函数

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

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

static int led_remove(struct platform_device *pdev)
{
       /* 卸载字符设备驱动程序 */
       printk("enter remove\n");
       class_device_destroy(cls, MKDEV(major, 0));
       class_destroy(cls);
       unregister_chrdev(major, "myled");

       iounmap(gpio_con);       //注销虚拟地址
       return 0;
}

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

static int led_drv_init(void)           //入口函数,注册驱动
{
       platform_driver_register(&led_drv);
       return 0;
}
static void led_drv_exit(void)       //出口函数,卸载驱动
{
       platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

8.测试运行

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

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

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

原文地址:https://www.cnblogs.com/zhuangquan/p/11659863.html

时间: 2024-10-10 21:29:05

《驱动学习 - platform机制实现驱动层分离》的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

Yii源码阅读笔记 - 日志组件

?使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category);Yii::trace($message, $category); 两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG: defined('YII_DEBUG') or define('YII_DEBUG', true); Yii::log方法的调用需要指定message的level和category.category是格式为“xxx.yyy.z

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

Apache Storm源码阅读笔记

欢迎转载,转载请注明出处. 楔子 自从建了Spark交流的QQ群之后,热情加入的同学不少,大家不仅对Spark很热衷对于Storm也是充满好奇.大家都提到一个问题就是有关storm内部实现机理的资料比较少,理解起来非常费劲. 尽管自己也陆续对storm的源码走读发表了一些博文,当时写的时候比较匆忙,有时候衔接的不是太好,此番做了一些整理,主要是针对TridentTopology部分,修改过的内容采用pdf格式发布,方便打印. 文章中有些内容的理解得益于徐明明和fxjwind两位的指点,非常感谢.

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

dubbo源码阅读笔记--服务调用时序

上接dubbo源码阅读笔记--暴露服务时序,继续梳理服务调用时序,下图右面红线流程. 整理了调用时序图 分为3步,connect,decode,invoke. 连接 AllChannelHandler.connected(Channel) line: 38 HeartbeatHandler.connected(Channel) line: 47 MultiMessageHandler(AbstractChannelHandlerDelegate).connected(Channel) line: