platform驱动架构初探

时间:2022-04-04 05:35:48

platform总线是Linux2.6引入的虚拟总线,这类总线没有对应的硬件结构。与之相反,USB总线和PCI总线在内核中是有对应的bus(USB-bus和PCI-bus)的。为了统一管理CPU这些既不属于USB又不属于PCI总线的外设资源,采用了platform虚拟总线。和字符设备不同,在platform架构中,整个驱动分为了device和driver两部分,提高了系统的可移植性。
在学习platform架构时,我们可以借助一点面向对象的思想,注意关注一些重要的结构体,将属性和行为分开学习,再联系起来。
本文所有代码基于linux3.9.5

platform总线驱动架构概览

可以分为如下三层:

  1. 设备struct platform_device : 资源分配
  2. 驱动struct platform_driver :初始化
  3. 总线struct platform_bus :device和driver的匹配,管理

linux内核启动流程和platform总线的注册

kernel在进入C语言阶段,会进入start_kernel函数(init/main.c),进行一些内存管理,调度。该函数的最后会执行rest_init();
下面是rest_init(init/main.c)源码

static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}

我们可以看到,该函数做了三件事:

  • 首先创建了一个线程执行kernel_init函数,该函数读取根文件系统下的init程序。这个操作完成了从内核态到用户态的转变。init进程作为所以用户态进程的父进程,将永远存在,PID是1
  • kthreadd是一个守护进程,PID是2
  • idle是空闲进程,cpu空闲时启动

我们进入kernel_init函数,在进入其中的kernel_init_freeable函数,继续进入do_basic_setup函数,这里我们就可以看到对驱动的初始化函数driver_init();
driver_init函数中,倒数第三个执行的函数platform_bus_init就是我们想找的platform总线的注册函数,位于drivers/base/platform.c

int __init platform_bus_init(void)
{
    int error;

    early_platform_cleanup();

    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

除了最后的platform_bus_init位于drivers/base,其余函数都位于init/main.c中。下面我们将注意力从内核启动转移到platform设备上。

platform架构总线

platform_bus是一种设备

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

从上面的结构体我们可以看到,platform_bus是一个名字为“platform”的device。device结构体是内核中设备的基本结构体。其他是设备,例如USB,都和device有关。这些设备的结构体或包含device成员,或实现device的部分成员。C中没有面向对象的继承特性,所以通过这种方式,我们变相的实现了“继承”

platform_bus_type实现总线的管理

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_attrs  = platform_dev_attrs,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,  //电源管理
};

我们关注下platform_match这个函数

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* 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);
}

我们可以看到,这个函数的作用是将platform的device和driver名字相比较,相同则返回True表示匹配。

总线注册

上文linux启动过程中已经分析,不赘述

int __init platform_bus_init(void)
{
    int error;

    early_platform_cleanup();

    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

platform设备

platform_device结构体

这里我们要关注下platform_device结构体,位于include/linux/platform_device.h

struct platform_device {
    const char  * name;
    int     id;
    bool        id_auto;
    struct device   dev;
    u32     num_resources;
    struct resource * resource;

    const struct platform_device_id *id_entry;

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

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

platform_device 封装了device。

  • name:设备的名称
  • dev:真正有用的设备,通过contain_of,能找到整个platform_device
  • num_resources, resource: 系统使用的资源。Linux系统资源包括IO,寄存器,DMA,Bus,Memory等。

设备的注册和卸载

int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}

void platform_device_unregister(struct platform_device *pdev)
{
    platform_device_del(pdev);
    platform_device_put(pdev);
}

platform driver

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;
    const struct platform_device_id *id_table;
};

由device_driver结构体封装而来

struct device_driver {
    const char      *name;
    struct bus_type     *bus;

    struct module       *owner;
    const char      *mod_name;  /* used for built-in modules */

    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */

    const struct of_device_id   *of_match_table;
    const struct acpi_device_id *acpi_match_table;

    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;

    const struct dev_pm_ops *pm;

    struct driver_private *p;
};
  • probe:将driver绑定到device上调用该函数
  • remove:系统卸载设备的时候,将driver和device解绑
  • shutdown:关机时使设备静默
  • suspend:使设备进入睡眠模式
  • resume: 唤醒设备