Linux设备驱动开发-platform总线

platform总线

  • 4.1 概述
  • 4.2 platform 设备驱动简介
  • 4.3 platform设备
    • 4.3.1 platfrom_device结构体
    • 4.3.2 注册/注销platform设备
  • 4.4 platform 驱动
    • 4.4.1 注册/注销平台驱动
    • 4.4.2平台总线的注册和匹配方式
    • 4.4.3平台驱动获取设备信息


4.1 概述

bus:
总线作为主机和外设的连接通道,有些总线是比较规范的,形成了很多协议。如PCI,USB,1394,IIC等。任何设备都可以选择合适的总线连接到主机。当然主机也可能就是CPU本身。
driver:
驱动程序是在CPU运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。
device:
设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类(CLASS中)。如音频设备(和声音相关的都算),输入设备(鼠标,键盘,游戏杆等)
在linux驱动管理中,可以将驱动程序中用到的数据和代码分离开来:
设备是数据 驱动是代码
这种分离好处是:如果设备的参数变了,或者更改了同类的设备,那么只需要修改数据,二不需要修改代码,就能驱动设备了。而设备和驱动的关联靠的是总线。
Linux设备驱动开发-platform总线_第1张图片

4.2 platform 设备驱动简介

  • 在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于总线。
    也就是说在嵌入式CPU中,访问外设跟访问内存是一样的。
    基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
  • 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
  • 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。

4.3 platform设备

4.3.1 platfrom_device结构体

内核使用platform_device结构体来描述平台设备,内核源码定义在include/linux/device.h中,结构体原型如下:

struct platform_device {
     const char *name;
     int id;
     struct device dev;
     u32 num_resources;
     struct resource *resource;
     const struct platform_device_id *id_entry;
     /* 省略部分成员 */
 };
 参数:
    name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
    id: 指定设备的编号,Linux支持同名的设备,而同名设备之间则是通过该编号进行区分;
    dev: Linux设备模型中的device结构体,linux内核大量使用了面向对象思想,platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
    num_resources: 记录资源的个数,当结构体成员resource存放的是数组时,需要记录resource数组的个数,内核提供了宏定义ARRAY_SIZE用于计算数组的个数;
    resource: 平台设备提供给驱动的资源,如irq,dma,内存等等。
    id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这里的id_entry用于保存匹配的结果;

平台设备的工作是为驱动程序提供设备信息,设备信息包括硬件信息和软件信息两部分。

  • 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、IO口等等
  • 软件信息:以太网卡设备中的MAC地址、I2C设备中的设备地址、SPI设备的片选信号线等等

4.3.2 注册/注销platform设备

1.我们定义并初始化好platform_device结构体后,需要把它注册、挂载到平台设备总线上。我们使用platform_device_register的注册函数:

int platform_device_register(struct platform_device *pdev);
参数 :pdev是platform_device类型的结构体指针,成功返回0,失败返回<0

2.相反我们需要移除,卸载某个平台设备时,我们使用platform_device_unregister函数通知平台总线去移除这个设备:

void platform_device_unregister(struct platform_device *pdev)
参数:pdev: platform_device类型的结构体指针,无返回值

4.4 platform 驱动

内核中使用platform_driver结构体来描述平台驱动,platform_driver结构体在内核源码/include/platform_device.h中,结构体原型如下所示:

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;
		const struct platform_device_id*id_table;
};
参数:
    probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
    remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程。
    shutdown: 彻底关电
    suspend:休眠驱动设备,可能是低功耗状态
    resume:唤醒驱动设备
    driver: Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性;
    id_table: 表示该驱动能够兼容的设备类型。

4.4.1 注册/注销平台驱动

初始化platform_driver后,通过platform_driver_register()函数注册平台驱动:

int platform_driver_register(struct platform_driver *drv);
参数: drv: platform_driver类型结构体指针 返回值:成功0 失败负数

当卸载驱动模块时,需要注销已注册的平台驱动,platform_driver_unregister()函数用于注销已注册的平台驱动:

void platform_driver_unregister(struct platform_driver *drv);
参数: drv: platform_driver类型结构体指针

上面的内容是最基本的平台驱动框架,只需要实现probe函数、remove函数,初始化platform_driver结构体,并调用platform_driver_register进行注册即可。

4.4.2平台总线的注册和匹配方式

在Linux设备驱动模型中,总线是负责匹配设备和设备驱动的。当有新的设备或者新的驱动加入到中线中时,总线(bus)辉调用platform_match函数对新增的设备或驱动进行配对。

总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连。
总线上有两条链表klist_devices、klist_drivers;
每次出现一个设备就要向总线注册,添加到总线的klist_devices链表中;
每次出现一个驱动也要向总线注册,添加到总线的klist_drivers中。
比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量。把这些变量统统加入相应的链表,device 插入devices 链表,driver插入drivers链表。这样通过总线就能找到每一个设备,每一个驱动。
假如计算机里只有设备却没有对应的驱动,那么设备无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。

Linux内核用platform_bus_type函数来描述平台总线,定义在driver/base/platform.c中,总线在linux内核启动时自动注册:

struct bus_type platform_bus_type = {
    .name           = "platform",
    .dev_groups     = platform_dev_groups,
    .match          = platform_match,
    .uevent         = platform_uevent,
    .pm             = &platform_dev_pm_ops,
};

EXPORT_SYMBOL_GPL(platform_bus_type);

这里重点是platform总线的match函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线, 它都必须实例化该函数指针。
platform_match函数在内核源码/driver/base/platform.c中:

static int platform_match(struct device *dev, struct device_driver *drv)
{
     /*调用了to_platform_device()和to_platform_driver()宏*/
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

#define to_platform_device(x) (container_of((x), struct platform_device, dev)
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))

通过contain_of宏获取正在匹配的platform_device,platform_driver.
platform总线提供了四种匹配方式,优先级分别是:设备树机制>ACPI匹配模式>id_table方式>字符串比较

4.4.3平台驱动获取设备信息

平台设备通过结构体resource来抽象表示一些硬件的信息,软件信息利用设备结构体device中的platform_data保存。
platform_get_resource()函数通常会在驱动的probe函数中执行,用于获取平台的设备提供的资源结构体:

struct resource *platform_get_resource(struct platform_device *dev, 
unsigned int type, unsigned int num);
参数:
    dev: 指定要获取哪个平台设备的资源;
    type: 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
    num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,
    为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
返回值:
	成功: struct resource结构体类型指针
	失败: NUL

如果资源的类型是IORE类型的IORESOURECE_IRQ,平台设备驱动还会通过platform_get_req()函数获取中断引脚:

int platform_get_irq(struct platform_device *pdev, unsigned int num)
参数:
    pdev: 指定要获取哪个平台设备的资源;
    num: 指定要获取的资源编号。
返回值:
    成功: 可用的中断号
    失败: 负数

你可能感兴趣的:(linux,驱动开发,c语言)