Linux Platform驱动模型(二) _驱动方法【转】

转自:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/06/6367910.html

Linux设备树语法详解Linux Platform驱动模型(一) _设备信息中我们讨论了设备信息的写法,本文主要讨论平台总线中另外一部分-驱动方法,将试图回答下面几个问题:

  1. 如何填充platform_driver对象?
  2. 如何将驱动方法对象注册到平台总线中?

正文前的一点罗嗦

写驱动也有一段时间了,可以发现,其实驱动本质上只做了两件事:向上提供接口,向下控制硬件,当然,这里的向上并不是直接提供接口到应用层,而是提供接口给内核再由内核间接的将我们的接口提供给应用层。而写驱动也是有一些套路可寻的,拿到一个硬件,我们大体可以按照下面的流程写一个驱动:

  1. 确定驱动架构:根据硬件连接方式结合分层/分离思想设计驱动的基本结构
  2. 确定驱动对象:内核中的一个驱动/设备就是一个对象,1.定义,2.初始化,3.注册,4.注销
  3. 向上提供接口:根据业务需要确定提供cdev/proc/sysfs哪种接口
  4. 向下控制硬件:1.查看原理图确定引脚和控制逻辑,2.查看芯片手册确定寄存器配置方式,3.进行内存映射,4.实现控制逻辑

认识驱动方法对象

内核用platform_driver结构来表示一个驱动方法对象

//include/linux/device.h
173 struct platform_driver {
174         int (*probe)(struct platform_device *);
175         int (*remove)(struct platform_device *);
176         void (*shutdown)(struct platform_device *);
177         int (*suspend)(struct platform_device *, pm_message_t state);
178         int (*resume)(struct platform_device *);
179         struct device_driver driver;
180         const struct platform_device_id *id_table;
181         bool prevent_deferred_probe;
182 };

在这个结构中,我们主要关心以下几个成员

struct platform_driver
--174-->探测函数,如果驱动匹配到了目标设备,总线会自动回调probe函数,必须实现,下面详细讨论。
--175-->释放函数,如果匹配到的设备从总线移除了,总线会自动回调remove函数,必须实现
--179-->platform_driver的父类,我们接下来讨论
--180-->用于C语言写的设备信息,下面详细讨论。

platform_driver里面有些内容需要在父类driver中实现,

 //include/linux/device.h
 228 struct device_driver {
 229         const char              *name;
 230         struct bus_type         *bus;
 231
 232         struct module           *owner;
 233         const char              *mod_name;      /* used for built-in modules */
 234
 235         bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */
 236
 237         const struct of_device_id       *of_match_table;
 238         const struct acpi_device_id     *acpi_match_table;
 239
 240         int (*probe) (struct device *dev);
 241         int (*remove) (struct device *dev);
 242         void (*shutdown) (struct device *dev);
 243         int (*suspend) (struct device *dev, pm_message_t state);
 244         int (*resume) (struct device *dev);
 245         const struct attribute_group **groups;
 246
 247         const struct dev_pm_ops *pm;
 248
 249         struct driver_private *p;
 250 };

下面是我们关心的几个成员

struct device_driver
--229-->驱动名,如果这个驱动只匹配一个C语言的设备,那么可以通过name相同来匹配
--230-->总线类型,这个成员由内核填充
--232-->owner,通常就写THIS_MODULE
--237-->of_device_id顾名思义就是用来匹配用设备树写的设备信息,下面详细讨论
--249-->私有数据

driver与device的匹配

设备信息有三种表达方式,而一个驱动是可以匹配多个设备的,平台总线中的驱动要具有三种匹配信息的能力,基于这种需求,platform_driver中使用不同的成员来进行相应的匹配。

of_match_table

对于使用设备树编码的设备信息,我们使用其父类device_driver中的of_match_table就是用来匹配

//include/linux/mod_devicetable.h
220 /*
221  * Struct used for matching a device
222  */
223 struct of_device_id
224 {
225         char    name[32];
226         char    type[32];
227         char    compatible[128];
228         const void *data;
229 };

struct of_device_id
--225-->name[32]设备名
--226-->type[32]设备类型
--227-->重点!compatible[128]用于与设备树compatible属性值匹配的字符串
--228-->data驱动私有数据

对于一个驱动匹配多个设备的情况,我们使用struct of_device_id tbl[]来表示。

struct of_device_id of_tbl[] = {
    {.compatible = "xj4412,demo0",},
    {.compatible = "xj4412,demo1",},
    {},
};

id_table

对于使用C语言编码的设备信息,我们用platform_driver对象中的id_table就是用来匹配。我们使用struct platform_device_id ids[]来实现一个驱动匹配多个C语言编码的设备信息。

//include/linux/mod_deviceid.h
485 struct platform_device_id {
486         char name[PLATFORM_NAME_SIZE];
487         kernel_ulong_t driver_data;
488 }; 

struct platform_device_id
--486-->name就是设备名

下面这个例子就是用一个驱动来匹配两个分别叫"demo0"和"demo1"的设备,注意,数组最后的{}是一定要的,这个是内核判断数组已经结束的标志。

static struct platform_device_id tbl[] = {
    {"demo0"},
    {"demo1"},
    {},
};

name

如果platform_driver和C语言编码的platform_device是一一匹配的,我们还可以使用device_driver中的name来进行匹配

注册设备表

填充完platform_driver结构之后,我们应该将其中用到的设备表注册到内核,虽然不注册也可以工作,但是注册可以将我们表加入到相关文件中,便于内核管理设备。

MODULE_DEVICE_TABLE(类型, ID表);
设备树ID表
类型:of
C写的platform_device的ID表
类型:platform
C写的i2c设备的ID表
类型:i2c
C写的USB设备的ID表
类型:usb

匹配小结

细心的读者可能会发现,这么多方式都写在一个对象中,那如果我同时注册了三种匹配结构内核该用哪种呢?此时就需要我们搬出平台总线的匹配方式:

//drivers/base/platform.c
 748 static int platform_match(struct device *dev, struct device_driver *drv)
 749 {
 750         struct platform_device *pdev = to_platform_device(dev);
 751         struct platform_driver *pdrv = to_platform_driver(drv);
 752
 753         /* Attempt an OF style match first */
 754         if (of_driver_match_device(dev, drv))
 755                 return 1;
 756
 757         /* Then try ACPI style match */
 758         if (acpi_driver_match_device(dev, drv))
 759                 return 1;
 760
 761         /* Then try to match against the id table */
 762         if (pdrv->id_table)
 763                 return platform_match_id(pdrv->id_table, pdev) != NULL;
 764
 765         /* fall-back to driver name match */
 766         return (strcmp(pdev->name, drv->name) == 0);
 767 }

从中不难看出,这几中形式的匹配是有优先级的:of_match_table>id_table>name,了解到这点,我们甚至可以构造出同时适应两种设备信息的平台驱动:

static struct platform_driver drv = {
    .probe  = demo_probe,
    .remove = demo_remove,

    .driver = {
        .name = "demo",
#ifdef CONFIG_OF
        .of_match_table = of_tbl,
#endif
    },

    .id_table = tbl,
};

此外,如果你追一下of_driver_match_device(),就会发现平台总线的最终的匹配是compatible,name,type三个成员,其中一个为NULL或""时表示任意,所以我们使用平台总线时总是使用compatile匹配设备树,而不是节点路径或节点名

probe()

probe即探测函数,如果驱动匹配到了目标设备,总线会自动回调probe函数,下面详细讨论。并把匹配到的设备信息封装策划嗯platform_device对象传入,里面主要完成下面三个工作

  1. 申请资源
  2. 初始化
  3. 提供接口(cdev/sysfs/proc)

显然,remove主要完成与probe相反的操作,这两个接口都是我们必须实现的。

在probe的工作中,最常见的就是提取设备信息,虽然总线会将设备信息封装成一个platform_device对象并传入probe函数,我们可以很容易的得到关于这个设备的所有信息,但是更好的方法就是直接使用内核API中相关的函数

/**
 * platform_get_resource - 获取资源
 * @dev: 平台总线设备
 * @type:资源类型,include/linux/ioport.h中有定义
 * @num: 资源索引,即第几个此类型的资源,从0开始
 */
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)

注意,通过内核API(eg,上下这两个API)获取的resource如果是中断,那么只能是软中断号,而不是芯片手册/C语言设备信息/设备树设备信息中的硬中断号,但是此时获取的resource的flag是可以正确的反映该中断的触发方式的,只需要flag & IRQF_TRIGGER_MASK即可获取该中断的触发方式。

/**
 * platform_get_irq - 获取一个设备的中断号
 * @dev: 平台总线设备
 * @num: 中断号索引,即想要获取的第几个中断号,从0开始
 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
/**
 * dev_get_platdata - 获取私有数据
 */
static inline void *dev_get_platdata(const struct device *dev){
        return dev->platform_data;
}

注册/注销platform_driver对象

内核提供了两个API来注册/注销platform_driver对象到内核

/**
 * platform_driver_register - 注册
 */
int platform_driver_register(struct platform_driver *drv);
/**
 * platform_driver_unregister - 注销
 */
int platform_driver_unregister(struct platform_driver *drv);

在动态编译的情况下,我们往往在模块初始化函数中注册一个驱动方法对象,而在模块卸载函数中注销一个驱动方法对象,所以我们可以使用内核中如下的宏来提高代码复用

module_platform_driver(driver_name);

实例

这个实例同时使用了设备信息模块和设备树两种设备信息来源,不过最终使用的是设备树,需要注意的是,当我们用设备树的设备信息时,有一个成员platform_device.device.of_node来表示设备的节点,这样就允许我们使用丰富的设备树操作API来操作。

//#include "private.h"
/*
/{
    demo{
        compatible = "4412,demo0";
        reg = <0x5000000 0x2 0x5000008 0x2>;
        interrupt-parent = <&gic>;
        interrupts = <0 25 0>, <0 26 0>;
        intpriv = <0x12345678>;
        strpriv = "hello world";
    };
};
*/

struct privatedata {
    int val;
    char str[36];
};
static void getprivdata(struct device_node *np)
{
    struct property *prop;
    prop = of_find_property(np, "intpriv", NULL);
    if(prop)
        printk("private val: %x\n", *((int *)(prop->value)));

    prop = of_find_property(np, "strpriv", NULL);
    if(prop)
        printk("private str: %s\n", (char *)(prop->value) );
}

static int demo_probe(struct platform_device *pdev)
{
    int irq;
    struct resource *addr;
    struct privatedata *priv;

    printk(KERN_INFO "%s : %s : %d - entry.\n", __FILE__, __func__, __LINE__);

    priv = dev_get_platdata(&pdev->dev);
    if(priv){
        printk(KERN_INFO "%x : %s \n", priv->val, priv->str);
    }else{
        getprivdata(pdev->dev.of_node);
    }

    addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(addr){
        printk(KERN_INFO "0: %x : %d \n", addr->start, resource_size(addr));
    }
    addr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if(addr){
        printk(KERN_INFO "1: %x : %d \n", addr->start, resource_size(addr));
    }
    addr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    if(!addr){
        printk(KERN_INFO "No 2 resource\n");
    }

    irq = platform_get_irq(pdev, 0);
    if(0 > irq){
        return irq;
    }else{
        printk(KERN_INFO "irq 0: %d \n", irq);
    }
    irq = platform_get_irq(pdev, 1);
    if(0 > irq){
        return irq;
    }else{
        printk(KERN_INFO "irq 0: %d \n", irq);
    }

    irq = platform_get_irq(pdev, 2);
    if(0 > irq){
            printk(KERN_INFO "No 2 irq\n");
    }

    return 0;
}

static int demo_remove(struct platform_device *pdev)
{
    return 0;
}

static struct platform_device_id tbl[] = {
    {"demo0"},
    {"demo1"},
    {},
};
MODULE_DEVICE_TABLE(platform, tbl);

#ifdef CONFIG_OF
struct of_device_id of_tbl[] = {
    {.compatible = "4412,demo0",},
    {.compatible = "4412,demo1",},
    {},
};
#endif

//1. alloc obj
static struct platform_driver drv = {
    .probe  = demo_probe,
    .remove = demo_remove,

    .driver = {
        .name = "demo",
#ifdef CONFIG_OF
        .of_match_table = of_tbl,
#endif
    },

    .id_table = tbl,
};

static int __init drv_init(void)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - entry.\n",current->comm, current->pid, __FILE__, __func__, __LINE__);
    return platform_driver_register(&drv);
}
static void __exit drv_exit(void)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",current->comm, current->pid, __FILE__, __func__, __LINE__);

    platform_driver_unregister(&drv);
}
module_init(drv_init);
module_exit(drv_exit);

MODULE_LICENSE("GPL");
时间: 2024-09-20 10:26:39

Linux Platform驱动模型(二) _驱动方法【转】的相关文章

Linux驱动技术(二) _访问I/O内存

ARM是对内存空间和IO空间统一编址的,所以,通过读写SFR来控制硬件也就变成了通过读写相应的SFR地址来控制硬件.这部分地址也被称为I/O内存.x86中对I/O地址和内存地址是分开编址的,这样的IO地址被称为I/O端口.本文只讨论IO内存的访问. IO内存访问流程 我们知道,为了管理最重要的系统资源并让物理地址对进程透明,Linux使用了内存映射机制,就是一个进程如果想访问一个物理内存地址(eg.SFR地址),那么首先就是将其映射成虚拟地址.   IO内存申请/归还 Linux提供一组函数用于

linux RTC 驱动模型分析

最近学习RTC(real time clock)实时时钟,RTC实时时钟主要作用是给Linux系统提供时间.RTC因为是电池供电的,所以掉电后时间不丢失.Linux内核把RTC用作"离线"的时间 与日期维护器.当Linux内核启动时,它从RTC中读取时间与日期,作为基准值.在运行期间内核完全抛开RTC,以软件的形式维护系统的当前时间与日 期,并在需要时将时间回写RTC芯片.另外如果RTC提供了IRQ中断并且可以定时,那么RTC还可以作为内核睡眠时唤醒内核的闹钟.应用程序可以用 RTC提

linux RTC 驱动模型分析【转】

转自:http://blog.csdn.net/yaozhenguo2006/article/details/6824970         RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间.RTC因为是电池供电的,所以掉电后时间不丢失.Linux内核把RTC用作"离线"的时间与日期维护器.当Linux内核启动时,它从RTC中读取时间与日期,作为基准值.在运行期间内核完全抛开RTC,以软件的形式维护系统的当前时间与日期,并在需要时将时间回写RTC芯片

【Linux驱动】字符设备驱动

一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等. 2.块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备

【驱动】USB驱动实例&amp;#183;串口驱动&amp;#183;键盘驱动【转】

转自:http://www.cnblogs.com/lcw/p/3159370.html Preface USB体系支持多种类型的设备. 在 Linux内核,所有的USB设备都使用 usb_driver结构描述. 对于不同类型的 USB设备,内核使用传统的设备驱动模型建立设备驱动描述,然后映射到 USB设备驱动,最终完成特定类型的 USB设备驱动 USB驱动·入门:http://infohacker.blog.51cto.com/6751239/1226257 USB串口驱动 USB串口驱动关键

总线设备驱动模型---platform篇

  linux从2.6起就加入了一套新的驱动管理和注册的机制platform平台总线,是一条虚拟的总线,设备用platform_device表示,驱动用platform_driver进行注册.于传统的bus/device/driver机制相比,platform由内核进行统一管理,在驱动中使用资源,提高了代码的安全性和可移植性. 下面来看看内核时怎么注册platform总线的过程 点击(此处)折叠或打开 int __init platform_bus_init(void) {     int er

【Linux高级驱动】linux设备驱动模型之平台设备驱动机制【转】

[1:引言: linux字符设备驱动的基本编程流程] 转自:http://www.cnblogs.com/lcw/p/3802579.html1.实现模块加载函数  a.申请主设备号    register_chrdev(major,name,file_operations);  b.创建字符设备cdev,注册字符设备    cdev_alloc cdev_init cdev_add   c.创建设备文件    class_create device_create  d.注册中断    ret

Linux与Windows的设备驱动模型对比:架构、API 和开发环境比较

名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Binary Interface) 设备驱动是操作系统的一部分,它能够通过一些特定的编程接口便于硬件设备的使用,这样软件就可以控制并且运行那些设备了.因为每个驱动都对应不同的操作系统,所以你就需要不同的 Linux.Windows 或 Unix 设备驱动,以便能够在不同的计算机上使用你的设备.这就是为什么当你雇佣一个驱动开发者或者选择一个研发服务商

Linux 与 Windows 的设备驱动模型对比:架构、API 和开发环境比较

名词缩写: API 应用程序接口Application Program Interface ABI 应用系统二进制接口Application Binary Interface 设备驱动是操作系统的一部分,它能够通过一些特定的编程接口便于硬件设备的使用,这样软件就可以控制并且运行那些设备了.因为每个驱动都对应不同的操作系统,所以你就需要不同的 Linux.Windows 或 Unix 设备驱动,以便能够在不同的计算机上使用你的设备.这就是为什么当你雇佣一个驱动开发者或者选择一个研发服务商提供者的时