Linux驱动的platform机制

来源:互联网 发布:mac艺术字体下载大全 编辑:程序博客网 时间:2024/06/08 02:48

一、Platform概述

    从Linux2.6起,内核引入了一套新的驱动管理和注册机制:Platform_device和 Platform_driver。现在Linux中大部分的设备驱动都可以使用这套机制,总线为platform_bus,设备用platform_device表示,驱动用platform_driver进行注册。

    Linux的这种platform driver机制和传统的device_driver机制相比,一个十分明显的优势在于platform机制将本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform_device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。下面是SPI驱动层次示意图,Linux中的SPI总线可理解为SPI控制器引出的总线:


和传统的驱动一样,platform机制也分为三个步骤:

1、总线注册阶段:

    内核启动初始化时linux3.12/init/main.c文件中的: kernel_init()→kernel_init_freeable()->do_basic_setup()→driver_init()→platform_bus_init()→bus_register(&platform_bus_type), 注册了一条platform总线(虚拟总线,platform_bus)。

分析流程:

    platform_bus_init ->bus_register,调用bus_register注册总线platform_bus_type,其名字为"platform",并且在platform目录下创建devices和drivers目录.

int __init platform_bus_init(void)
{
...
early_platform_cleanup();
 
error = device_register(&platform_bus);
error = bus_register(&platform_bus_type);
return error;
}
 
struct bus_type platform_bus_type = {
.name= "platform",
.dev_groups= platform_dev_groups,
.match= platform_match,
.uevent= platform_uevent,
.pm= &platform_dev_pm_ops,
};
分析bus_register:
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv; //重点
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
priv->bus = bus; //指定 priv中的bus_type;
bus->p = priv; //指定bus中的priv;
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //将名字"platform"赋值给kobject
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype; //初始化为bus_ktype
priv->drivers_autoprobe = 1;
 
retval = kset_register(&priv->subsys); //注册kset
retval = bus_create_file(bus, &bus_attr_uevent);
 
priv->devices_kset = kset_create_and_add("devices", NULL, //在sys/bus/platform目录创建devices
&priv->subsys.kobj);
 
priv->drivers_kset = kset_create_and_add("drivers", NULL, //在sys/bus/platform目录创建drivers
&priv->subsys.kobj);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
 
retval = add_probe_files(bus);
retval = bus_add_attrs(bus);
retval = bus_add_groups(bus, bus->bus_groups);
return 0;
}
static struct kobj_type bus_ktype = {
.sysfs_ops= &bus_sysfs_ops,
};

总线注册流程图如下所示:


2、添加设备阶段:

    设备注册: platform_device_register()→platform_device_add()→(pdev→dev.bus = &platform_bus_type)→device_add(),就这样把设备给挂到虚拟的总线上。

int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
首先调用device_initialize对device进行初始化,代码如下:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
然后platform_device_add:
int platform_device_add(struct platform_device *pdev)
{
int i, ret;
 
if (!pdev)
return -EINVAL;
 
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
 
pdev->dev.bus = &platform_bus_type; //初始化struct device中的bus_type
... 
ret = device_add(&pdev->dev);
}
}
struct device platform_bus = {
.init_name= "platform", //初始化device的name
};
device_add:
int device_add(struct device *dev)
{
...
dev = get_device(dev); //调用container_ofcontainer_of(kobj, struct device, kobj)得到dev
...
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
 
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
 
error = device_create_file(dev, &dev_attr_uevent); //创建设备属性文件
error = device_add_attrs(dev);
error = bus_add_device(dev); //添加设备到bus
 
kobject_uevent(&dev->kobj, KOBJ_ADD);
 //probe drivers for a new device ->device_attach->device_bind_driver or ->__device_attach->driver_probe_device->really_probe->dev->bus->probe or ->drv->probe
bus_probe_device(dev);
... 
}

struct platform_device {
const char*name;
intid;
boolid_auto;
struct devicedev;
u32num_resources;
struct resource*resource;
 
const struct platform_device_id*id_entry;
 
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
 
/* arch specific additions */
struct pdev_archdataarchdata;
};

3、驱动注册阶段:

__platform_driver_register()→driver_register()→bus_add_driver()→driver_attach()→bus_for_each_dev(), 对在每个挂在虚拟的platform bus的设备作__driver_attach()→driver_probe_device(),判断drv→bus→match()是否执行成功,此时通过指针执行platform_match→strncmp(pdev→name , drv→name , BUS_ID_SIZE),如果相符就调用really_probe(实际就是执行相应设备的 platform_driver→probe(platform_device)。)开始真正的探测,如果probe成功,则绑定设备到该驱动。

int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &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;
 
return driver_register(&drv->driver);
}
driver_register:
int driver_register(struct device_driver *drv)
{
...
ret = bus_add_driver(drv);
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
 
return ret;
}
bus_add_driver:
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
 
bus = bus_get(drv->bus);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
}
return 0;
}
driver_attach->__driver_attach:
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
if (!driver_match_device(drv, dev))
return 0;
 
if (dev->parent)/* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
 
return 0;
}
 
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
 
if (!device_is_registered(dev))
return -ENODEV;
 
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
 
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev);
 
return ret;
}
 
static int really_probe(struct device *dev, struct device_driver *drv)
{
...
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
 
driver_bound(dev);
... 
}
驱动注册流程图如下所示:


    小结:platform机制最后还是调用了bus_register() , device_add() , driver_register()这三个关键的函数。

二、Platform分析

1.何谓platform bus?


        Linux系统中许多部分对设备是如何链接的并不感兴趣,但是他们需要知道哪些类型的设备是可以使用的。设备模型提供了一种机制来对设备进行分类,在更高的功能层面上描述这些设备,并使得这些设备对用户空间可见。因此从2.6内核开始引入了设备模型。

        总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。

        Platform总线是2.6 kernel中最近引入的一种虚拟总线,主要用来管理CPU的片上资源,具有更好的移植性,因此在2.6 kernel中,很多驱动都用platform改写了。


总线bus_type 定义如下:



   platform 总线 platform_bus_type 定义如下:



    总线名称是"platform",它只是bus_type的一种,定义了总线的属性,同时platform_bus_type还有相关操作方法,如挂起、中止、匹配及hotplug事件等。总线bus是联系driver和device的中间枢纽。Device通过所属的bus找到driver,由match操作方法进行匹配。

函数 platform_match定义如下:


2.Bus、driver及devices的连接关系

    plarform device会有一个名字用于driver binding(在注册driver的时候会查找driver的目标设备的bus位置,这个过程称为driver binding),另外IRQ以及地址空间等资源也要给出 。

    platform_device结构体用来描述设备的名称、资源信息等。该结构被定义在:    



其中:

   const char  * name;     //定义平台设备的名称,此处设备的命名应和相应驱动程序命名一致

   struct resource * resource;  //定义平台设备的资源

  在这个结构里封装了struct device及struct resource。可知:platform_device由device派生而来,是一种特殊的device。

   struct resource定义如下:kernel/include/linux/ioport.h

 struct resource {
   resource_size_t start;    //定义资源的起始地址
   resource_size_t end;     //定义资源的结束地址
  const char *name;             //定义资源的名称
  unsigned long flags;       //定义资源的类型,比如MEM,IO,IRQ,DMA类型
  struct resource *parent, *sibling, *child;

};

这个结构表示设备所拥有的资源,即I/O端口、I/O映射内存、中断及DMA等。这里的地址指的是物理地址。另外还需要注意platform_device中的device结构,它详细描述了设备的情况,其为所有设备的基类.device结构体定义如下:



3.函数device_register和platform_device_register

 函数 device_register定义如下:



  该函数首先初始化一个设备,然后加入到系统中。


函数platform_device_register定义如下:



同样:先初始化一个设备,然后使用device_add注册到系统中.



分析上面代码:

第271行:初始化设备的parent为platform_bus,初始化设备的总线为platform_bus_type

if (!pdev->dev.parent)
    pdev->dev.parent = &platform_bus;
 pdev->dev.bus = &platform_bus_type;

    由platform_device_register和platform_device_add的实现可知,device_register()和 platform_device_register()都会首先初始化设备,区别在于第二步:其实platform_device_add()包括 device_add(),不过要先注册resources,然后将设备挂接到特定的platform总线。

    注册一个platform device分为两部分,初始化platform_device,然后将platform_device添加到platform总线中。输入参数platform_device可以是静态的全局设备。

    另外一种机制就是动态申请platform_device_alloc一个platform_device设备,然后通过platform_device_add_resources及platform_device_add_data等添加相关资源和属性。

    无论哪一种platform_device,最终都将通过platform_device_add注册到platform总线上。

4. device_driver和platform driver

  platform device是一种device,自己是不会做事情的,要有人为它做事情,那就是platform driver。platform driver遵循linux系统的driver model。对于device的discovery/enumerate都不是driver自己完成的而是由由系统的driver注册机制完成。 driver编写人员只要将注册必须的数据结构初始化并调用注册driver的kernel API就可以了。

    platform_driver定义:    



    可见,它包含了设备操作的几个功能函数,同时包含了一个device_driver结构,说明device_driver是 platform_driver的基类。驱动程序中需要初始化这个变量。下面看一下这个变量的定义:



    device_driver提供了一些操作接口,但其并没有实现,相当于一些虚函数,由派生类platform_driver进行重载,无论何种类 型的 driver都是基于device_driver派生而来的,具体的各种操作都是基于统一的基类接口的,这样就实现了面向对象的设计。

    device_driver需要注意这两个变量:name和owner。其作用主要是为了和相关的platform_device关联起来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。

    platform_driver从字面上来看就知道是设备驱动。设备驱动是为谁服务的呢?当然是设备了。内核正是通过这个一致性来为驱动程序找到资源,即 platform_device中的resource。

5 .driver_register 和platform_driver_register

    内核提供的platform_driver结构体的注册函数为platform_driver_register,其原型定义:



第472行: drv->driver.bus = &platform_bus_type;
    设置成platform_bus_type这个很重要,因为driver和device是通过bus联系在一起的,具体在本例中是通 过 platform_bus_type中注册的回调例程和属性来是实现的, driver与device的匹配就是通过 platform_bus_type注册的回调例程platform_match ()来完成的。*/

第473行:if (drv->probe)
                            drv->driver.probe = platform_drv_probe;   //在really_probe函数中,回调了platform_drv_probe函数



    platform_drv_probe函数将struct device转换为struct platform_device和struct platform_driver,然后调用platform_driver中的相应接口函数。那为什么不直接调用platform_drv_XXX等接口 呢?这就是Linux内核中面向对象的设计思想。



第171行:如果总线的方法和设备自己的方法同时存在,将打印告警信息,对于platform bus,其没有probe等接口.

第184行:将驱动挂接到总线上,通过总线来驱动设备。

bus_add_driver定义:

第720行:如果总线上的driver是自动probe的话,则将该总线上的driver和device绑定起来。


扫描该总线上的每一个设备,将当前driver和总线上的设备进行match,如果匹配成功,则将设备和driver绑定起来。

__driver_attach定义:


第447行:如果该设备尚没有匹配的driver,则尝试匹配。





对于platform总线,其匹配过程如下:


第674行:简单的进行字符串匹配,这也是我们强调platform_device和platform_driver中的name属性需要一致的原因。匹配成功后,则调用probe接口。

如果bus和driver同时具备probe方法,则优先调用总线的probe函数。否则调用device_driver的probe函数,此 probe 函数是经过各种类型的driver重载的函数,这就实现了利用基类的统一方法来实现不同的功能。对于platform_driver来说,其就是:


    然后调用特定platform_driver所定义的操作方法,这个是在定义某个platform_driver时静态指定的操作接口。至此,platform_driver成功挂接到platform bus上了,并与特定的设备实现了绑定,并对设备进行了probe处理。

6  bus、device及driver三者之间的关系

    在数据结构设计上,总线、设备及驱动三者相互关联。platform device包含device,根据device可以获得相应的bus及driver。设备添加到总线上后形成一个双向循环链表,根据总线可以获得其上挂接的所有device,进而获得了 platform device。根据device也可以获得驱动该总线上所有设备的相关driver。

    platform driver包含driver,根据driver可以获得相应的bus,进而获得bus上所有的device,进一步获得platform device,根据name对driver与platform device进行匹配,匹配成功后将device与相应的driver关联起来,即实现了platform device和platform driver的关联。匹配成功后调用driver的probe进而调用platform driver的probe,在probe里实现驱动特定的功能。

 

7.哪些适用于plarform驱动?

    platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,这样拥有更好的可移植性。platform机制的本身使用并不复杂,由两 部分组成:platform_device和platfrom_driver。Platform driver通过platform bus获取platform_device。

    通常情况下只要和内核本身运行依赖性不大的外围设备,相对独立的,拥有各自独立的资源(地址总线和IRQs),都可以用 platform_driver来管理,而timer,irq等小系统之内的设备则最好不用platfrom_driver机制。

    platform_device最大的特定是CPU直接寻址设备的寄存器空间,即使对于其他总线设备,设备本身的寄存器无法通过CPU总线访问,但总线的controller仍然需要通过platform bus来管理。

    总之,platfrom_driver的根本目的是为了统一管理系统的外设资源,为驱动程序提供统一的接口来访问系统资源,将驱动和资源分离,提高程序的可移植性。

8. 基于platform总线的驱动开发流程

基于Platform总线的驱动开发流程如下:
•    定义初始化platform bus
•    定义各种platform devices
•    注册各种platform devices
•    定义相关platform driver
•    注册相关platform driver
•    操作相关设备 





0 0
原创粉丝点击