一、为什么需要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()