Home 平台总线及设备和驱动
Post
Cancel

平台总线及设备和驱动

在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_IOIORESOURCE_MEMIORESOURCE_IRQIORESOURCE_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 目录下生成一个对应名称的新的目录项。 驱动的注册使用流程类比设备模型,继承而来的。

This post is licensed under CC BY 4.0 by the author.

内核-spinlock(自旋锁)

内核-rwlock(读写自旋锁)