在设备驱动模型中, 引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们SOC中物理总线的概念并不严格相等:
物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。
一般对于I2C、SPI、USB这些常见类型的物理总线来说,Linux内核会自动创建与之相应的驱动总线,因此I2C设备、SPI设备、 USB设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。 它们也就没有相应的物理总线,比如led、rtc时钟、蜂鸣器、按键等等,Linux内核将不会为它们创建相应的驱动总线。
为了使这部分设备的驱动开发也能够遵循设备驱动模型,Linux内核引入了一种虚拟的总线——平台总线(platform bus)。 平台总线用于管理、挂载那些没有相应物理总线的设备,这些设备被称为平台设备,对应的设备驱动则被称为平台驱动。 平台设备驱动的核心依然是Linux设备驱动模型,平台设备使用platform_device结构体来进行表示,其继承了设备驱动模型中的device结构体。 而平台驱动使用platform_driver结构体来进行表示,其则是继承了设备驱动模型中的device_driver结构体。
内核使用platform_device结构体来描述平台设备,结构体原型如下:
platform_device结构体在内核源码/include/linux/platform_device.h中
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/* 省略部分成员 */
};
name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
id: 指定设备的编号,Linux支持同名的设备,而同名设备之间则是通过该编号进行区分; dev:Linux设备模型中的device结构体,linux内核大量使用了面向对象思想,platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
num_resources: 记录资源的个数,当结构体成员resource存放的是数组时,需要记录resource数组的个数,内核提供了宏;
内核中使用platform_driver结构体来描述平台驱动,结构体原型如下所示:
platform_driver结构体在内核源码/include/platform_device.h中
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
probe:函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
remove:函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程。
driver:Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性;
id_table:表示该驱动能够兼容的设备类型。
现在,如果写platform驱动,第一反应应该是先写设备树,就不要想着platform_device了,就当它被设备树替代了吧。
在代码中添加of_device_id类型结构体数组。
platform驱动是如何被加载的。就是通过.compatible匹配完成的。如下所示
static const struct of_device_id my_of_ids[] = {
{.compatible = "my_platform"},
{},
};
MODULE_DEVICE_TABLE(of,my_of_ids);
##注意两点:
1.这个MODULE_DEVICE_TABLE宏一定不能忘记。不写不会报错。但是匹配不到。
2.my_of_id是一个数组,且最后一项必须空值。否则一定出错。
修改设备树
在根节点下添加这个东西,简单就是美呀。
hehe{
compatible = "my_platform";
};
MODULE_DEVICE_TABLEmy_of_ids注册到一个总线设备表中,如果设备树中有与它的compatible一致的节点,就是两个字符串相同,就匹配成功。platform_driver的probe就会被调用。没有匹配成功,platform_driver的probe就不会被调用。我们这里都是"my_platform"字符串,所以肯定匹配成功。
在代码里我故意将driver的name("hello pd")和设备树的节点("hehe")写的不一样,主要是说明以下几点。
1.这两个东西可以不同。
2.根据后面的测试结果可知,sys目录出现的是driver的name属性值,而不是设备树的"hehe"。
3.driver的name值和设备树的compatible的值也没有关系。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/time.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
static const struct of_device_id my_of_ids[] = {
{.compatible = "my_platform"},
{},
};
MODULE_DEVICE_TABLE(of,my_of_ids);
static int my_probe(struct platform_device *device)
{
printk("probe\n");
return 0;
}
static int my_remove(struct platform_device *device)
{
printk("remove\n");
return 0;
}
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hello pd",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
},
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
root@host:/server# insmod helloworld.ko
[ 301.880475] probe
root@host:/server#
匹配成功,probe函数执行。
那么.driver的name属性是干什么的?这是容易混淆的地方,看看下面,这个name属性是sys目录下的驱动的名字。位于/sys/bus/platform/drivers下。
root@host:/sys/bus/platform/drivers# ls -ls
0 drwxr-xr-x 2 root root 0 Jan 1 05:41 hello pd
/*略*/
从下面可以看出,name 字符串中间有空格是很不好的习惯。加了也没错。
root@host:/sys/bus/platform/drivers# ls hello\ pd/
bind hehe module uevent unbind
root@host:/sys/bus/platform/drivers#
从这里的结果可知,"hehe"作为"hello pd"的子文件夹出现
遍历hehe文件夹
root@host:/sys/bus/platform/drivers/hello pd# ls hehe
driver modalias power uevent
driver_override of_node subsystem
再看of_node文件夹
root@host:/sys/devices/platform/hehe# ls -ls
total 0
0 lrwxrwxrwx 1 root root 0 Jan 1 06:13 driver -> ../../../bus/platform/drivers/hello pd
0 -rw-r--r-- 1 root root 4096 Jan 1 05:36 driver_override
0 -r--r--r-- 1 root root 4096 Jan 1 05:36 modalias
0 lrwxrwxrwx 1 root root 0 Jan 1 05:36 of_node -> ../../../firmware/devicetree/base/hehe
0 drwxr-xr-x 2 root root 0 Jan 1 05:36 power
0 lrwxrwxrwx 1 root root 0 Jan 1 05:36 subsystem -> ../../../bus/platform
0 -rw-r--r-- 1 root root 4096 Jan 1 05:36 uevent
root@host:/sys/devices/platform/hehe#
of_node被链接到了…/…/…/firmware/devicetree/base/hehe也就是/sys/firmware/devicetree/base/hehe目录,从这里得到一个信息,/sys/firmware/devicetree里面是设备树。在目录只有一个base目录
在该base目录下有如下内容,他们都是设备树的根节点的子节点。如果想知道设备树里有什么就来这里找。
root@host:/sys/firmware/devicetree/base# ls
#address-cells model
#size-cells myled
aliases name
chosen pxp_v4l2
clocks regulators
compatible reserved-memory
cpus sii902x-reset
hehe soc
interrupt-controller@00a01000 spi4
leds wsinput
memory wskey
root@host:/sys/firmware/devicetree/base#