Linux驱动-LINUX设备驱动模型之PLATFORM(平台)总线详解

在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

 

以并列的方式展示内核相关的设备描述、硬件相关的驱动描述两部分内容。

内核相关的设备描述硬件相关的驱动描述

结构体platform_device

struct platform_device {           //  platform总线设备
    const char    * name;          //  平台设备的名字
    int        id;                 //   ID 是用来区分如果设备名字相同的时候
    struct device    dev;          //   内置的device结构体
    u32        num_resources;      //   资源结构体数量
    struct resource    * resource; //   指向一个资源结构体数组

    const struct platform_device_id    *id_entry; //  用来进行与设备驱动匹配用的id_table表

    /* arch specific additions */
    struct pdev_archdata    archdata;             //  自留地    添加自己的东西
};

 

struct resource {      // 资源结构体
    resource_size_t start;      // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
    resource_size_t end;        // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
    const char *name;           // 资源名
    unsigned long flags;        // 资源的标示,用来识别不同的资源
    struct resource *parent, *sibling, *child;   // 资源指针,可以构成链表
};

排版有问题,凑合看吧

结构体platform_driver

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 (*resume)(struct platform_device *);
    struct device_driver driver;                //   内置的device_driver 结构体
    const struct platform_device_id *id_table;  //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};
//  这个probe函数其实和device_driver中的是一样的功能,但是一般是使用device_driver中的那个

 

接口函数:

int platform_device_register(struct platform_device *);      // 用来注册我们的设备      

void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备

int platform_driver_register(struct platform_driver *);       // 用来注册我们的设备驱动    

void platform_driver_unregister(struct platform_driver *);  // 用来卸载我们的设备驱动

为了完成在板文件中添加device,platform设备的工作,需要在板文件arch/arm/mach-<soc名>/mach-<板名>.c)中添加相应的代码,将这个设备添加注册到系统中。并最终通过类似于platform_add_devices()的函数把这个platform_device注册进系统。如果一切顺利,我们会在/sys/devices/platform目录下看对应设备名字的子目录platform_driver_register()、platform_driver_unregister()函数进行platform_driver的注册与注销,而原先
注册和注销字符设备的工作已经被移交到platform_driver的probe()和remove()成员函数中。
  

通过device和driver的名字,两者进行匹配。

*****************************************************************************************************************************

当我们注册platform平台设备时进行自动匹配的代码在哪里呢?

platform_device_add

    device_add

        bus_probe_device     //  关键就在这个函数

void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;             //  获取设备中的总线类型   bus_type
    int ret;

    if (bus && bus->p->drivers_autoprobe) {       //  如果总线存在  并且  设置了自动进行设备与设备驱动匹配标志位
        ret = device_attach(dev);                           //  则调用这个函数进行匹配
        WARN_ON(ret < 0);
    }
}


int device_attach(struct device *dev)
{
    int ret = 0;

    device_lock(dev);
    if (dev->driver) {    //    如果我们的设备早就绑定了设备驱动     那么执行下面的
        ret = device_bind_driver(dev);
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {     //   我们就分析这条
        pm_runtime_get_noresume(dev);
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);   //  遍历总线的链表匹配对应的设备驱动
        pm_runtime_put_sync(dev);
    }
    device_unlock(dev);
    return ret;
}
//  到这里之后就和上面的其实是一样的了

总结:  所以由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,

匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动,所以由此可以看出来,这个东西的设计其实是很美的。

static int platform_match(struct device *dev, struct device_driver *drv) // 总线下的设备与设备驱动的匹配函数
{
    struct platform_device *pdev = to_platform_device(dev);      //  通过device  变量获取到 platform_device
    struct platform_driver *pdrv = to_platform_driver(drv);      //   通过 driver 获取  platform_driver

    /* match against the id table first */
    if (pdrv->id_table)    //   如果pdrv中的id_table 表存在
        return platform_match_id(pdrv->id_table, pdev) != NULL;  //  匹配id_table

    /* fall-back to driver name match */   //  第二个就是指直接匹配 pdev->name     drv->name  名字是否形同
    return (strcmp(pdev->name, drv->name) == 0);
}


static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {  //  循环去比较id_table数组中的各个id名字是否与pdev->name 相同
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;       // 将id_table数组中的名字匹配上的 这个数组项 指针赋值给 pdev->id_entry
            return id;         //  返回这个指针
        }
        id++;
    }
    return NULL;
}

总结: 由上面可知platform总线下设备与设备驱动的匹配原理就是通过名字进行匹配的,先去匹配platform_driver中的id_table表中的各个名字与platform_device->name

名字是否相同,如果相同表示匹配成功直接返回,否则直接匹配platform_driver->name与platform_driver->name是否相同,相同则匹配成功,否则失败。

------------------------------------------------------------------------------------------------------------------------------

整体原理分析

首先相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的,但是用法基类似。

从无到有分4步进行:

(1)第一步:系统启动时在bus系统中注册platform

platform.c文件内int __init platform_bus_init(void)函数负责初始化。通过函数device_register(&platform_bus);注册,平台总线是作为设备注册进系统的
(2)第二步:内核移植的人负责提供platform_device

mach-smdkv210.c中static void __init smdkv210_machine_init(void)向系统添加设备。

platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));

 

//形参为

static struct platform_device *smdkv210_devices[] __initdata = {
       &s5pv210_device_iis0,
       &s5pv210_device_ac97,
       &s3c_device_adc,
       &s3c_device_ts,
       &s3c_device_wdt,
   };
//以IIS总线为例
struct platform_device s5pv210_device_iis0 = {
	.name		  = "s3c64xx-iis",     //总线名
	.id		  = 0,
	.num_resources	  = ARRAY_SIZE(s5pv210_iis0_resource),
	.resource	  = s5pv210_iis0_resource,
	.dev = {
		.platform_data = &s3c_i2s_pdata,
	},
};

//.platform_data = &s3c_i2s_pdata,这个数据是device结构体里的子项原型为void        *platform_data;,是一个无类型的指           针,目的是在这个框架内添加关于设备自定义的内容,可以是一个结构体指针,也可以是一个函数指针。
       /**
       ①如果是结构体指针,这个结构体可用于描述某个平台设备,比如描述一个GPIO的端口号、寄存器地址、操作函数指针等。
       ②如果是函数指针,可在指定函数内进行对设备的操作
       **/


(3)第三步:写驱动的人负责提供platform_driver

比如这个s3c64xx-iis平台总线,搜索这个总线名,即可轻松找到使用次总线的驱动,以下面的内容为例,这个总线用于声音数据的传输。

static struct platform_driver s3c64xx_iis_driver = {
	.probe  = s3c64xx_iis_dev_probe,
	.remove = s3c64xx_iis_dev_remove,
	.driver = {
		.name = "s3c64xx-iis",
		.owner = THIS_MODULE,
	},
};

s3c64xx_iis_dev_probe是这个设备驱动的初始化函数。初始化之后设备就正常运行了
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了

 

那么这个总线实现的分层结构是什么样的呢?

————————————————————————————————————————————————————

以下内容暂时作废

举例说明:

S5PV210的uart驱动

首先是初始化平台设备E:\v210\x210_src\arch\arm\plat-samsung\init.c文件 的s3c_arch_init(void)函数调用platform_add_devices(s3c24xx_uart_devs, nr_uarts);添加平台设备,

E:\v210\x210_src\drivers\serial\s5pv210.c添加驱动

static int __init s5p_serial_init(void)
{
    return s3c24xx_serial_init(&s5p_serial_driver, *s5p_uart_inf);
}

参考:https://www.cnblogs.com/deng-tao/p/6026373.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值