在linux设备模型中,总线是联系设备和驱动的关键部分。对于那真实存在的物理总线,如I2C,SPI,USB,PCI等,的确符合linux的设备模型, 系统创建对应的总线驱动,之后对应的设备和驱动注册挂载在对应的物理总线驱动上。但是情况下,有很多设备并没有也不需要通过总线来访问。主要 是一些SOC自带的外设控制器等,还有其他一些简单的IO设备,特点就是不需要依赖特定总线。
针对实际情况和linux的设备模型,linux系统内核实现了一个虚拟的总线,即平台总线(platform bus),主要特点:是虚拟的,目的是管理那些 没有实际物理总线的设备,主要就是SOC芯片的各种外设控制器,当然,只要没有物理总线的设备都可以使用平台总线来实现linux设备模型。相应的设备和 驱动,就叫平台设备和平台驱动。 简单来说,平台总线是linux内核实现的一种虚拟总线,让没有物理总线的设备也能挂载到“总线”上,以符合Linux的设备模型,是软件层面上的一种统一抽象实现。
平台设备和字符设备,块设备,网络设备不是相同概念,平台设备是linux设备管理的一种新的方式,基于现代的linux设备模型,字符设备,块设备,网络设备是 设备实现的功能分类。一个设备可以使用平台设备模型管理,在分类上实现为字符设备。
大致实现原理
内核在启动过程会自动注册platform总线,之后系统中就可以使用该总线了,平台设备符合linux设备模型,当有平台设备或平台驱动注册到该总线时,会自动回调 platform_match
进行匹配判断,现代各个总线子系统都继承自linux设备模型,都是相似的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
// ......
bus_register(&platform_bus_type);
// ......
}
match匹配过程实现:匹配时有先后优先级,对于平台设备, 特定驱动名称强制匹配 > 开源固件设备树 > ACPI > id table > 驱动名称, (平台设备的id table是基于名称的,但是可以配置多个,优于驱动名称)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 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);
}
平台设备
平台设备继承自linux设备模型的设备结构体。 平台设备在内核中的表示结构体为 struct platform_device
,参考如下代码,常见的如名称,编号,id table,resources资源等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct platform_device {
const char *name; //设备名称,可以用于最低优先级的匹配方式
int id; //设备编号,linux支持同名设备,此时需要编号区分
bool id_auto;
struct device dev; //Linux设备模型中的 device 结构体,通用的继承思想,实现通用的设备模型管理
u32 num_resources; //资源及个数
struct resource *resource;
const struct platform_device_id *id_entry; //device id table 匹配机制使用
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 resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
resources 资源
resources是代表硬件资源,是一个非常重要的成员。 resources资源主要关注3个字段, start , end ,flags,flags字段表示资源类型,主要有IORESOURCE_IO
,IORESOURCE_MEM
, IORESOURCE_IRQ
,IORESOURCE_DMA
等等,参考 /include/linux/ioport.h
中的宏定义。每个成员的具体定义起始和类型是相关的, 相当于C语法的union机制。一般的MEM或IO资源,start和end就表示是地址范围;如果是IRQ资源,start和end表示中断号的开始和结束值。 如果只有一个中断,那么二者值就会相同。另外,一个设备的资源可以定义多个,如两段MEM空间,两个不连续的IRQ资源等。
在驱动中获取平台设备资源,可以使用相关API:(/include/linux/platform_device.h
),example:
1
2
3
4
5
//获取对应资源
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
//获取对应的中断号
int platform_get_irq(struct platform_device *dev, unsigned int num);
其他信息或资源
除了这些常见的resources资源,还可以自定义平台数据platform_data
,这是一个void *
类型的任意指针,可以保存任何特定的信息, 该成员即为 dev->platform_data
,是放在基础的device
结构体中的,有些奇怪。参考代码:
1
2
3
4
5
6
7
int dev_xxx_data=134;
struct platform_device somedev = {
.dev = {
.platform_data = &dev_xxx_data,
}
};
查看系统中的平台设备
1
2
3
ls -l /sys/devices/platform/
# 或者
ls -l /sys/bus/platform/devices/
注册平台设备
目前嵌入式系统主要使用设备树机制,在设备树文件中编写设备节点,设备节点在内核启动中自动解析为对应的设备并进行注册。
手动注册
可以进行测试。另外,在使用设备树之前,平台设备主要就是在板级BSP文件中主动注册平台设备。
相关API:
1
2
3
4
5
6
7
8
9
10
int platform_device_register(struct platform_device *pdev);
EXPORT_SYMBOL_GPL(platform_device_register);
void platform_device_unregister(struct platform_device *pdev);
EXPORT_SYMBOL_GPL(platform_device_unregister);
// 批量注册
int platform_add_devices(struct platform_device **devs, int num);
EXPORT_SYMBOL_GPL(platform_add_devices);
平台驱动
类似,平台驱动继承自linux设备模型的驱动结构体。定于在 /include/linux/platform_device.h
中的struct platform_driver
结构体。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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; //C语言,继承driver结构体及相关特性
const struct platform_device_id *id_table; //用于id table方式匹配设备
bool prevent_deferred_probe;
};
//id table中的匹配名称,还可以带一个自定义的驱动数据,帮助实现不同匹配名称的差异化,如寄存器地址。
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
注册平台驱动
1
2
3
4
5
6
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
extern void platform_driver_unregister(struct platform_driver *);
注册驱动成功后,会在 /sys/bus/platform/driver
目录下生成一个对应名称的新的目录项。 驱动的注册使用流程类比设备模型,继承而来的。