研究Linux内核中platform_device与platform_driver框架的设备驱动

        关于Linux平台设备驱动模型,并不是创建新的设备分类,是在原有的字符设备基础上使用,将设备和驱动分开,生成两个.ko文件。
        Linux内核维护一个全局设备链表,对应的总线会将驱动和设备链表里的设备名进行匹配,如果匹配成功就会将设备的信息传递给驱动的probe函数,probe函数得到设备的核心结构体platform_device的信息就可以进行对应的操作。
        我们只需实现平台驱动和平台设备即可,平台总线是内核实现的,常见的总线如IIC、SPI、CAN等,LED、KEY这类型的普通字符设备,linux内核就使用虚拟的平台总线struct bus_type  platform_bus_type来匹配这类设备。

首先来看platform_device平台设备层结构体。

我们只需要关注name、id、num_resources、resource、dev这几个成员。

struct platform_device{

           const char *name; //自定义的设备名,用于和驱动匹。

           int id;     //当设备和驱动一对一匹配时,所设值为-1

           struct device dev;     //下面列举其成员介绍。该结构体下的release函数是必须实现的

           u32 num_resources;  //设备的个数,即设备资源数组的元素个数

           struct resource   *resource; //设备所用资源数组的首地址

};

接下来看platform_device下的resources结构体。

我们需关注前四个成员,最后一个成员是由内核实现。

struct resource{

        resource_size_t start;   //所用资源的起始物理地址

        resource_size_t end;   //所用资源的结束物理地址

        const char *name;    //自定义的资源名称,此名称不与驱动相匹配,可以随便写,但要有意义

        unsigned long flags;   //所用资源的类型,例如中断IORESOURCE_IRQ,外设或者用于和设备通讯的支持直接寻址的地址空间IORESOURCE_MEM

};

接下来看platform_device结构体下的struct device,由于结构体过大,只展示重要的部分。

 struct  device{

      void  *platform_data;  //这个指针指向设属性信息,platform_driver可以获取该数据

      void *driver_data ;  //用来告诉驱动需要服务的设备的类型

      void (*release)(struct device *dev); //释放平台设备时会调用此函数,必须要实现

}

因此,编写平台设备的步骤大概如下:

1、分析硬件原理图,写出所使用的设备的占用资源情况,例如GPIO0寄存器的起始物理地址,GPIO0寄存器所占用的空间大小

2、定义并初始化一个资源结构数组struct resource,存放所有使用到的设备的信息。

3、编写一个release函数,在释放平台设备device时会调用此函数。

4、定义并初始化一个平台设备核心结构struct platform_device,其下的name是用来与driver下的name匹配的。

5、在模块初始化函数中调用注册平台设备函数,将平台设备核心结构体注册到内核中。

6、在模块卸载函数中调用注销平台设备函数,将平台设备核心结构体从内核中注销。

接下来实现平台驱动platform_driver核心结构体。

 

我们只需关注要用到的成员。

struct platform_driver {

        int (*probe)(struct platform_device *); //指向设备探测函数,当与设备相匹配时调用此函数,参数是将平台设备指针传入,通过操作平台设备指针可以初始化硬件。是很重要的函数

        int (*remove)(struct platform_device *);//指向移除设备函数,当总线上的设备和驱动匹配关系解除时会调用此函数。如果probe函数中申请了资源,就需要在此函数中按顺序释放

        struct device_driver driver; //此结构体下的name成员和设备相匹配,还有一个成员是用于和设备树相匹配,下面会列出

        const struct platform_device_id *id_table; /如果需要匹配多个设备,也可以使用此成员

};

接下来看platform_driver下的device_driver结构体。

 

我们只需关注重要的成员。

struct device_driver{

        const char *name; //此名字会和平台设备的名字相匹配

        const struct of_device_id *of_match_table; //此成员下的compatible是和设备的名字相匹配,下面会列出。是用于设备树的方式实现一个驱动匹配多个设备

}

接下来关注platform_driver下的platform_device_id。

struct platform_device_id {

        char name[PLATFORM_NAME_SIZE]; //此成员用于和设备名相匹配

        kernel_ulong_t driver_data; //此成员用于来保存设备的配置

};

因此,平台驱动有三种方式和平台设备相匹配。

第一种:也就是优先级最低的一种。platform_driver下的name成员和平台设备platform_device下的name成员匹配。

第二种:优先级第二高。平台设备platform_device下的name成员和platform_driver下的platform_device_id所指向的数组的每个元素的name匹配。

第三种:优先级最高,使用的是设备树方式。platform_driver下的device_driver下的of_device_id所指向的每个元素的compatible值与设备树中设备节点的compatibl值相匹配。

这里的优先级是指,当设备树方式实现时,会先用设备树方式匹配,当设备树无法匹配时会使用platform_device_id匹配,最后再使用platform_driver下的name成员匹配。

因此,平台驱动层的编写大概如下:

1、定义一个probe探测函数和remove删除函数。

2、如果需要用到platform_device_id,则需要定义一个platform_device_id的数组来存储相应的设备信息。如果不需要用到则跳过。

3、如果需要用到设备树匹配,则定义一个of_device_id数组来存储设备的信息和数据。如果不需要用到则跳过。

4、定义一个平台设备驱动核心结构体platform_driver并初始化里面需要用到的成员。

5、在模块初始化函数中调用注册平台驱动函数。

6、在模块卸载函数中调用注销平台驱动函数。

一般probe探测函数框架如下:

1、获取平台设备的属性信息、数据,利用传进来的指针可以得到。

2、探测资源。

3、申请资源

4、使用资源,映射寄存器、申请中断资源等。

5、初始化硬件设备。

6、注册input子系统设备、字符设备、块设备、网络设备等。

remove函数就将probe函数中所申请占用的资源按顺序进行释放。

 

 

 

 

物联沃分享整理
物联沃-IOTWORD物联网 » 研究Linux内核中platform_device与platform_driver框架的设备驱动

发表评论