深入理解Linux设备驱动模型

一、为什么需要linux设备驱动模型

如果没有linux设备驱动模型,那么我们写了一个驱动后,如果硬件进行了修改,这样驱动还能适用吗?是不是需要重新根据硬件修改一版驱动?这样做,驱动就没有可复用性,不用的硬件需要用不同版本的驱动,非常麻烦。

那么,Llinux 系统要考虑到驱动的可复用性,因此提出了设备和驱动分离的设计思路。这种设计思路将硬件描述和驱动设计分离,极大的提高了驱动的普遍适用性。

二、Linux设备驱动模型里面有哪些对象?

Linux里面有四个核心对象。

Bus:管理Device和Driver。例如Platform Bus,I2C Bus,SPI Bus,USB Bus,PCI Bus。

Bus负责Device--->Driver--->Match()--->Probe()

Device:表示硬件设备,例如UART2,I2C0,SPI1,GPIO,LED等

Driver:Driver负责:初始化,配置,控制,读写

Class:Class主要给用户空间用。例如/sys/class/leds/

三、Platform Bus

为了达到所有硬件都可以按照总线设备驱动模型来实现驱动,内核中建立一条虚拟的总线platform,它可以将那些没有真正挂在具体总线上的硬件, 虚拟的认为挂在了platform总线上,达到统一。

而其中用户最需要做的就是填充 platform_driver 驱动 和 platform_device设备。

linux负责将device和driver注册到platform bus上,platform bus则负责将platform device和platform driver进行匹配,匹配成功后,就会调用probe()。

四、Device Tree是什么?

Device Tree就是用来描述硬件信息的文件,有dts和dtsi两种,dts中的节点可以覆盖dtsi中的节点。一个标准的dts节点大致是这样的

cpp 复制代码
uart2: serial@ff1a0000 {

    compatible = "rockchip,rk3399-uart";

    reg = <0xff1a0000>;

    interrupts = <101>;

};

其中compatible字段是device和driver能匹配成功的关键。

dts经过dtc编译后的产物就是dtb。kernel在启动的时候会解析dtb文件,扫描节点,将节点注册到paltform bus上,也就是platform device。platform deivce就包含了dts节点的所有属性。

五、platform device和platform driver的匹配

上面已经讲了platform device是kernel解析dtb后注册到platform bus上面的。那driver是如何注册到platform bus上的呢?platform driver 调用 platform_driver_register(),然后 platform bus 保存这个 driver,并尝试和已有的 platform device 进行匹配。

cpp 复制代码
static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.shutdown	= gpio_led_shutdown,
	.driver		= {
		.name	= "leds-gpio",
		.of_match_table = of_gpio_leds_match,
	},
};

module_platform_driver(gpio_led_driver);

在驱动中,你都能找到类似上面的代码片段,

module_platform_driver() 做了什么?

很多人以为这是一个函数。

其实它是一个

展开后大概是:

复制代码
static int __init led_init(void)
{
    return platform_driver_register(&led_driver);
}

module_init(led_init);

所以驱动模块加载时:

Linux 自动执行:

复制代码
led_init()

↓

platform_driver_register()

真正完成注册的是:

复制代码
platform_driver_register()

platform_driver_register() 又做了什么?

它位于:

复制代码
drivers/base/platform.c

源码(简化):

复制代码
int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;

    return driver_register(&drv->driver);
}

注意这一句:

复制代码
drv->driver.bus = &platform_bus_type;

这就是:

告诉 Linux:

这个 Driver 属于 Platform Bus。

所以:

原来 Driver:

复制代码
platform_driver

里面实际上还有:

复制代码
driver

↓

bus

↓

platform_bus_type

结构关系:

复制代码
platform_driver
        │
        ▼
 device_driver
        │
        ▼
 bus = platform_bus_type

platform_bus_type 是什么?

Platform Bus 在 Linux 中其实也是一个对象:

复制代码
struct bus_type platform_bus_type = {

    .name = "platform",

    .match = platform_match,

    .probe = platform_probe,

};

可以看到:

Platform Bus 里面最重要的是:

复制代码
match()

probe()

以后所有 Platform Driver:

都会挂到:

复制代码
platform_bus_type

下面。


driver_register()

继续往下。

复制代码
platform_driver_register()

↓

driver_register()

driver_register() 位于:

复制代码
drivers/base/driver.c

里面继续:

复制代码
bus_add_driver()

bus_add_driver()

这是真正注册到 Bus 的地方。

可以理解为:

复制代码
Platform Bus

──────────────

Driver A

Driver B

Driver C

Driver D

bus_add_driver()

就是:

把:

复制代码
Driver

↓

插入

↓

Platform Bus

维护的 Driver 链表。

Linux 内部维护:

复制代码
platform_bus

│

├── device list

│

└── driver list

Driver 注册后:

进入:

复制代码
driver list

为什么 probe() 会执行?

Driver 注册以后。

bus_add_driver()

继续:

复制代码
driver_attach()

driver_attach()

会遍历:

复制代码
所有 Platform Device

例如:

Platform Bus:

复制代码
Device List

UART2

SPI0

I2C1

LED

KEY

然后:

一个一个:

复制代码
match()

↓

成功?

↓

probe()

例如:

LED:

复制代码
compatible

↓

mycompany,myled

Driver:

复制代码
mycompany,myled

匹配:

成功。

于是:

复制代码
probe()

立即调用。

整个调用链:

复制代码
module_platform_driver()

        │

        ▼

module_init()

        │

        ▼

platform_driver_register()

        │

        ▼

driver_register()

        │

        ▼

bus_add_driver()

        │
        ├───────────────┐
        │               │
        ▼               │
加入Platform Bus        │
Driver List             │
        │               │
        ▼               │
driver_attach()         │
        │               │
遍历所有Platform Device │
        │               │
        ▼               │
platform_match()        │
        │               │
compatible一致?─────────┘
        │
      Yes
        │
        ▼
platform_probe()

        │

        ▼

led_probe()
相关推荐
ye150127774551 小时前
220V转5V1A隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程
代码村新手1 小时前
Linux的基本指令
linux·运维·服务器
武汉万象奥科2 小时前
AGV导航控制器方案 | 基于HD-RK3576系列核心板的算控融合一体化平台
arm开发·嵌入式硬件·arm核心板·单板机
ye150127774553 小时前
DC24V-100V转3.3V500mA隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程
Lumos6fly3 小时前
51单片机从零到实战(15)——数字时钟温度计
单片机·嵌入式硬件·51单片机
gwf2163 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
撩得Android一次心动3 小时前
Linux编程笔记3【个人用】
linux·笔记
OpenPomeloxCommunity3 小时前
3. Linux 同步机制之 spinlock 设计与实现(一):通用层
linux
念恒123064 小时前
网络基础
linux·网络·c++