Linux设备模型(十一) - platform设备

一,platform device概述

在Linux2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,

会寻找与之匹配的驱动;相反的,在系统每注册一个设备的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的Linux设备和驱动通常都需要挂接在一种总线上,而对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,

但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。基于这一背景,Linux

发明了一种虚拟总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。

所谓的platform_device并不是与字符设备、块设备和网络设备并存的概念,而是linux系统提供的一种附加手段,例如,我们通常把在

SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而他们本身就是字符设备。这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。

二,platform模块的软件架构

内核中Platform设备有关的实现位于include/linux/platform_device.h和drivers/base/platform.c两个文件中,它的软件架构如下:

由图片可知,Platform设备在内核中的实现主要包括三个部分:

Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;

Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;

Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。

其中Platform Device和Platform Driver会给其它Driver提供封装好的API,具体可参考后面的描述。

三,platform bus/platform device/platform driver结构体

1,platform_driver

// msm_kernel\include\linux\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; /* 另外这里有一个id_table的指针,该指针和of_match_table、acpi_match_table的功能类似:提供其它方式的设备probe */
    bool prevent_deferred_probe;

    ANDROID_KABI_RESERVE(1);
};

2,platform_device

// msm_kernel\include\linux\platform_device.h
struct platform_device {
    const char    *name; /* 设备的名称,和struct device结构中的init_name意义相同。实际上,该名称在设备注册时,会拷贝到dev.init_name中 */
    int        id; /* 用于标识该设备的ID。内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。
因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个*/
    bool        id_auto; /* 指示在注册设备时,是否自动赋予ID值 */
    struct device    dev; /* 真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现) */
    u64        platform_dma_mask;
    struct device_dma_parameters dma_parms;
    u32        num_resources;
    struct resource    *resource; /* 该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象 */

    const struct platform_device_id    *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

3,platform bus

// msm_kernel\drivers\base\platform.c
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_groups    = platform_dev_groups,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .dma_configure    = platform_dma_configure,
    .pm        = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

四,platform bus/platform device/platform driver注册

1,platform bus注册

struct device platform_bus = {
    .init_name    = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

int __init platform_bus_init(void)
{
    int error;

    early_platform_cleanup(); /* 清除所有和Early device/driver相关的代码。因为执行到这里的时候,证明系统已经完成了Early阶段的启动,转而进行正常的设备初始化、启动操作,所以不再需要Early Platform相关的东西。 */

    error = device_register(&platform_bus); /* 在sysfs中创建/sys/devices/platform目录,所有的platform设备都会包含在此目录下 */
    if (error) {
        put_device(&platform_bus);
        return error;
    }
    error =  bus_register(&platform_bus_type); /* 在sysfs中创建/sys/bus/platform目录并在此目录中创建如下attributes和devices目录,drivers目录 */
    /*
    lynkco:/sys/bus/platform # ls
    devices  drivers  drivers_autoprobe  drivers_probe  uevent
    */
    if (error)
        device_unregister(&platform_bus);
    of_platform_register_reconfig_notifier();
    return error;
}

2,platform device注册

platform_device_register - add a platform device to device hierarchy

// msm_kernel\drivers\base\platform.c
platform_device_register(struct platform_device *pdev)
----device_initialize(&pdev->dev);
----platform_device_add(pdev);
--------pdev->dev.parent = &platform_bus; //该设备的sysfs目录/sys/devices/platform/xxx_device
--------pdev->dev.bus = &platform_bus_type; //该设备的bus type定义为platform_bus_type
--------dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id); //对于多个同名的设备,可以使用ID区分,在这里将实际名称修改为“name.id”的形式
--------insert_resource(p, r); /* 调用resource模块的insert_resource接口,将该设备需要使用的resource统一管理起来(我们知道,在这之前,只是声明了本设备需要使用哪些resource,但resource模块并不知情,也就无从管理,因此需要告知)。 */
--------device_add(&pdev->dev); // 将内嵌的struct device变量添加到内核中
            ......

3,platform driver注册

platform_driver_register - register a driver for platform-level devices

// msm_kernel\drivers\base\platform.c
platform_driver_register(drv)
----__platform_driver_register(drv, THIS_MODULE)
--------drv->driver.bus = &platform_bus_type; //该driver的bus type设置为platform_bus_type
------------drv->driver.probe = platform_drv_probe; /* 如果该platform driver提供了probe、remove、shutdown等回调函数,将该它内嵌的struct driver变量的probe、remove、shutdown等指针,设置为platform模块提供函数,包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因为probe等动作会从struct driver变量开始,经过platform_drv_xxx等接口的转接就可以到达platform diver自身的回调函数中。*/
------------drv->driver.remove = platform_drv_remove;
------------drv->driver.shutdown = platform_drv_shutdown;
--------driver_register(&drv->driver); //将内嵌的struct driver变量添加到内核中
            ......

五,platform device/platform driver提供的API

1,platform driver提供的API

extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *); //platform driver的注册、注销接口

extern int platform_driver_probe(struct platform_driver *driver,
                 int (*probe)(struct platform_device *)); //主动执行probe动作

static inline void *platform_get_drvdata(const struct platform_device *pdev);
static inline void platform_set_drvdata(struct platform_device *pdev,
                                         void *data); //设置或者获取driver保存在device变量中的私有数据

2,platform device提供的API

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *); //Platform设备的注册/注销接口,和底层的device_register等接口类似

extern struct resource *platform_get_resource(struct platform_device *,
                                               unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *,
                                                      unsigned int,
                                                      const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *); //通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等

extern int platform_device_add_resources(struct platform_device *pdev,
                                          const struct resource *res,
                                          unsigned int num); //向platform device中增加资源描述
                                          
extern int platform_device_add_data(struct platform_device *pdev,
                                     const void *data, size_t size); //向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)

六,platform device resource/platform data的定义与获取

1,struct resource结构体介绍

在platform device结构体的定义中关于device resource的定义如下

    u32        num_resources;
    struct resource    *resource;

它们描述了platform_device的资源,资源本身由resource结构体描述

/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    unsigned long desc;
    struct resource *parent, *sibling, *child;

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
};

我们通常关心start、end和flags这3个字段,它们分别标明了资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。

start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;

当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了一个中断号,开始值和结束值相同。

对于同种类型的资源而言,可以有多份,例如说某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。

系统中所有的platform_device,都可以在/sys/devices/platform/路径下查看。另外,系统中所有的platform_device,有来自设备树的,也有来自.c文件中注册的。那么,我们怎么知道哪些platform_device是来自设备树,哪些是来自.c文件中注册的?

可以查看该platform_device的相关目录下,是否有of_node,如果有of_node,那么这个platform_device就来自于设备树;否则,来自.c文件。

2,来自.c文件中注册的platform_device

2.1 example
// msm_kernel\arch\arm\mach-ep93xx\core.c

static struct usb_ohci_pdata ep93xx_ohci_pdata = {
    .power_on    = ep93xx_ohci_power_on,
    .power_off    = ep93xx_ohci_power_off,
    .power_suspend    = ep93xx_ohci_power_off,
};

static struct resource ep93xx_ohci_resources[] = {
    DEFINE_RES_MEM(EP93XX_USB_PHYS_BASE, 0x1000),
    DEFINE_RES_IRQ(IRQ_EP93XX_USB),
};

static struct platform_device ep93xx_ohci_device = {
    .name        = "ohci-platform",
    .id        = -1,
    .num_resources    = ARRAY_SIZE(ep93xx_ohci_resources),
    .resource    = ep93xx_ohci_resources,
    .dev        = {
        .dma_mask        = &ep93xx_ohci_dma_mask,
        .coherent_dma_mask    = DMA_BIT_MASK(32),
        .platform_data        = &ep93xx_ohci_pdata,
    },
};
platform_device_register(&ep93xx_ohci_device);

设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存等标准资源以外,还可能有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此platform也提供了platform_data的支持,platform_data的形式是由每个驱动自定义的,如对于usb ohci设备而言,platform_data为一个usb_ohci_pdata结构体,完成定义后将可以将PM operation相关的接口信息放入platform_data中。

在usb ohci驱动msm-kernel/drivers/usb/host/ohci-platform.c的probe()函数中,通过如下方式就拿到了platform_data:

static int ohci_platform_probe(struct platform_device *dev)    
{
    struct usb_hcd *hcd;
    struct resource *res_mem;
    struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev);
    struct ohci_platform_priv *priv;
    ... ...
}
2.2 memory resource资源的定义
#define DEFINE_RES_MEM(_start, _size)                    \
    DEFINE_RES_MEM_NAMED((_start), (_size), NULL)

#define DEFINE_RES_MEM_NAMED(_start, _size, _name)            \
    DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_MEM)

/* helpers to define resources */
#define DEFINE_RES_NAMED(_start, _size, _name, _flags)            \
    {                                \
        .start = (_start),                    \
        .end = (_start) + (_size) - 1,                \
        .name = (_name),                    \
        .flags = (_flags),                    \
        .desc = IORES_DESC_NONE,                \
    }
2.3 irq resource资源的定义
#define DEFINE_RES_IRQ(_irq)                        \
    DEFINE_RES_IRQ_NAMED((_irq), NULL)

#define DEFINE_RES_IRQ_NAMED(_irq, _name)                \
    DEFINE_RES_NAMED((_irq), 1, (_name), IORESOURCE_IRQ)
2.4 IO resources flags
/*
* IO resources have these defined flags.
*
* PCI devices expose these flags to userspace in the "resource" sysfs file,
* so don't move them.
*/
#define IORESOURCE_BITS        0x000000ff    /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS    0x00001f00    /* Resource type */
#define IORESOURCE_IO        0x00000100    /* PCI/ISA I/O ports */
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_REG        0x00000300    /* Register offsets */
#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

3,来自设备树的platform_device

在Linux内核启动时,内核通过 of_platform_populate() 函数,将dts中的device node创建成platform device。为后续和各类驱动的platform driver匹配做准备。

of_platform_device_create_pdata
----of_device_alloc
--------of_address_to_resource(np, num_reg, &temp_res)
--------num_irq = of_irq_count(np);
--------dev->num_resources = num_reg + num_irq;
--------dev->resource = res;
--------of_address_to_resource(np, i, res);
--------of_irq_to_resource_table(np, res, num_irq); //将dts中的address和irq等信息转化到resource结构体中
--------dev->dev.bus = &platform_bus_type;
----dev->dev.platform_data = platform_data;
----of_device_add(dev)
--------device_add(&ofdev->dev);

具体解析转化过程会在后续的dts章节中详细分析。

4,get resource API实现及使用

4.1 get resource API实现

platform_get_resource_byname:

/**
* platform_get_resource_byname - get a resource for a device by name
* @dev: platform device
* @type: resource type
* @name: resource name
*/
struct resource *platform_get_resource_byname(struct platform_device *dev,
                          unsigned int type,
                          const char *name)
{
    u32 i;

    for (i = 0; i < dev->num_resources; i++) {
        struct resource *r = &dev->resource[i];

        if (unlikely(!r->name))
            continue;

        if (type == resource_type(r) && !strcmp(r->name, name)) //匹配type和name
            return r;
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource_byname);

platform_get_resource:

/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*
* Return: a pointer to the resource or NULL on failure.
*/
struct resource *platform_get_resource(struct platform_device *dev,
                       unsigned int type, unsigned int num)
{
    u32 i;

    for (i = 0; i < dev->num_resources; i++) {
        struct resource *r = &dev->resource[i];

        if (type == resource_type(r) && num-- == 0) //匹配type和index
            return r;
    }
    return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);
4.2 get resource API使用示例

get memory resource使用示例:

struct resource *res;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
    dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
    return -ENODEV;
}

adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
                 resource_size(res));
if (!adata->acp3x_base)
    return -ENOMEM;

get irq resource 使用示例:

res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
    dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
    return -ENODEV;
}
adata->i2s_irq = res->start;

status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
              irqflags, "ACP3x_I2S_IRQ", adata);
if (status) {
    dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
    return -ENODEV;
}

get resource by name 使用示例:

struct resource *r;

/* card: irq assigned to the card itself. */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "card");
sock->card_irq = r ? r->start : 0;

/* stschg: irq which trigger on card status change (optional) */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "stschg");
sock->stschg_irq = r ? r->start : -1;

/* 36bit PCMCIA Memory area address */
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-mem");
if (!r) {
    printk(KERN_ERR "pcmcia%d has no 'pseudo-mem' resource!\n",
        sock->nr);
    goto out0;
}
sock->phys_mem = r->start;

由以上分析可知,在设备驱动中引入platform的概念至少有如下好处:

1)使得设备被挂接在一个总线上,符合Linux2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。

2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

3)让一个驱动支持多个设备实例。

参考链接:

Linux设备模型(8)_platform设备

dts展开为platform_device结构过程分析-腾讯云开发者社区-腾讯云

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值