Linux设备驱动模型

驱动程序在计算机系统中有两个作用,一是直接控制硬件,并给上层提供操作硬件的接口,二是主动向上层上报数据,总的来说是承上启下。

Linux内核是大内核,所谓大内核是指内核中包含了设备驱动程序;而windows内核是小内核,不包含驱动。

驱动中module_init和module_exit两个宏只是在kernel的编译阶段起作用,作用是将驱动的init函数和exit函数指针分别放到两个指定的section代码段里面。Kernel初始化驱动的时候会从这个section代码段开头依次调用放到此处的所有驱动初始化函数指针。这两个宏一般放在驱动代码的最下面。某些驱动之间可能存在依赖关系,所以驱动的初始化需要有先后顺序。内核中提供了设定驱动模块初始化优先级的宏。

#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn

#define pure_initcall(fn)       __define_initcall(fn, 0)

#define core_initcall(fn)       __define_initcall(fn, 1)
#define core_initcall_sync(fn)      __define_initcall(fn, 1s)
#define postcore_initcall(fn)       __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
#define device_initcall(fn)     __define_initcall(fn, 6)
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)
#define late_initcall(fn)       __define_initcall(fn, 7)
#define late_initcall_sync(fn)      __define_initcall(fn, 7s)

Linux在启动过程中会开一个内核线程去初始化所有编译到内核里面的驱动程序,流程是:

start_kernel --> rest_init --> kernel_thread --> kernel_init --> kernel_init_freeable --> do_basic_setup。
static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    do_initcalls();
    random_int_secret_init();
}
void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();
    devices_init();
    buses_init();
    classes_init();
    firmware_init();
    hypervisor_init();

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    platform_bus_init();
    cpu_dev_init();
    memory_dev_init();
    container_dev_init();
    of_core_init();
}

设备驱动程序初始化入口是static int __init xxx_init(void)函数,卸载函数为static void __exit yyy_exit(void)。这两个函数一般位于驱动代码的module_initmodule_exit两个宏之前。
Linux设备驱动模型的三个核心概念:bus、device和driver,即总线、设备和驱动。下面详细说一下我的理解。

总线bus:一般指数据传输的通道,比如I2C总线就是I2C master跟I2C slave数据相互的通道,其他的还有I2S总线,SPI总线,MMC总线等等等等,这些总线都是物理中实实在在的线路。内核中还有一种是虚拟总线,纯软件的东西,最重要的一个就是platform总线,为啥要引入这个虚拟的platform总线呢?原因就是很多设备并没有挂载在任何一种总线上,比如键盘,内核为了更好的管理设备和驱动,将这些不是总线设备的设备和它的驱动也当做一种总线设备和驱动去处理。总线在内核中对应的数据结构是struct bus_type,这是所有总线的一个抽象。所有总线的结构体变量都用这个类型定义。

设备device:一般指功能独立的硬件模块,控制器master是设备,被控制的slave也是设备。比如每一个I2C控制器都是一个设备,同时,每一个I2C总线上的slave也都是一个设备。内核中也有一种虚拟设备,纯软件的东西,这种虚拟设备挺多的,随便都可以创建一个。设备在内核中对应的数据结构是struct device,这是所有设备的一个抽象,也是所有具体设备数据类型的基类。这个结构体里面都是一些描述设备属性的成员。

驱动driver:一般指控制设备的软件,驱动和设备是孪生兄弟,谁也离不开谁。每一种设备都对应一套驱动程序,不同设备的驱动是不一样的。比如现在的ARM架构的芯片都有3个左右的I2C控制器,所有I2C控制器共用一套驱动程序i2c_driver。驱动在内核中对应的数据结构是struct device_driver,这个数据类型里面大多都是一些函数的指针,写设备驱动代码主要就是去实现里面的这些函数。所有设备驱动的类型都是这个数据类型的派生类。比如struct i2c_driver里面包含struct device_driver

下面以platform总线为例来说明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,
};
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;
    char *driver_override; /* Driver name to force a match */

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

    /* arch specific additions */
    struct pdev_archdata    archdata;
};
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函数,是驱动加载的入口函数,对于大部分设备驱动是必须的,简单的设备驱动可能不需要,直接在init里面把probe的事情都做了。之前说到的init函数是驱动初始化的入口函数,两个概念是不一样的。Init肯定是在probe之前调用,init肯定会被调用,但是probe不一样会调用。Probe在什么情况下才会被调用呢?Init函数一般是向总线中去注册设备或驱动,把xxx_driverxxx_device注册到总线上。platform_driver_register(&xxx_driver),这个函数是注册platform总线驱动的API函数,相应的还有platform_device_register(&xxx_device),注册设备的API函数。这里涉及到Linux驱动模型的核心思想,即注册到总线上的设备和注册到该总线上的驱动要match起来,然后probe才会被调用。所以,probe函数不是手动去调用的,而是match成功之后自动调用的。如果match不成功probe就不会被调用,驱动就不会被加载,然后设备和驱动就都无法使用。无论是真实的总线还是虚拟的platform总线,都分别使用两条链表去管理挂载在其上的设备和驱动,链表上的每个节点都代表一个设备模块或者一个驱动模块。这两条链表的位置分别是struct bus_type --> struct subsys_private --> struct klist klist_devicesstruct bus_type --> struct subsys_private --> struct klist klist_drivers

struct bus_type {
    const char      *name;
    const char      *dev_name;
    struct device       *dev_root;
    struct bus_attribute    *bus_attrs;
    struct device_attribute *dev_attrs;
    struct driver_attribute *drv_attrs;

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    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 dev_pm_ops *pm;

    struct iommu_ops *iommu_ops;

    struct subsys_private *p;
    struct lock_class_key lock_key;
};

struct subsys_private {
    struct kset subsys;
    struct kset *devices_kset;
    struct list_head interfaces;
    struct mutex mutex;

    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;

    struct kset glue_dirs;
    struct class *class;
};

设备结构体中代表链表节点的是struct platform_device --> struct device dev --> struct device_private *p –-> struct klist_node knode_bus,这个knode_bus就是这个设备在设备链表里面的节点。驱动结构体中代表链表节点的是struct platform_driver --> struct device_driver driver --> struct driver_private *p --> struct klist_node knode_bus,这个knode_bus就是这个驱动在驱动链表里面的节点。无论是注册设备,还是注册驱动,过程中都会去遍历另一条链表(注册设备时遍历驱动链表,注册驱动时遍历设备链表),首先要检查之前有没有注册过,如果注册过了就直接返回,目的就是要去寻找自己的另一半。寻找的依据是什么呢?看看match函数。

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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);

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

Match函数里面提供了5种方法,任何一种匹配成功都算OK。其实主要就是对比device结构体和driver结构体里面几个关键成员是否一致。所以写驱动的时候要注意这些成员,不能随便写。
驱动注册和加载的流程是这样的:
platform_driver_register –> driver_register –> bus_add_driver –> driver_attach –> bus_for_each_dev –> __driver_attach –> driver_match_device? –> driver_probe_device –> really_probe –> bus->probe/driver->probe。
最后如果bus的probe函数不为NULL,就执行bus的probe,其实bus的probe里面也会调用drv的probe,所以最后都会调到drv的probe。
设备注册的流程是这样的:
platform_device_register –> platform_device_add –> device_add –> bus_probe_device –> device_initial_probe –> __device_attach –> bus_for_each_drv –> __device_attach_driver –> driver_match_device? –> driver_probe_device –> really_probe –> bus->probe/driver->probe。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值