LINUX PLATFORM总线、设备及驱动接口分析

 前面几章我们分析了LINUX设备驱动模型,说明了总线注册、设备注册、驱动注册以及它们之间是如何绑定的。借助之前的分析基础,本文以platform模块为例,介绍系统中如何创建总线、设备以及驱动的实例。

 

platform总线是一个虚拟的总线,该模块的出现实现了资源与驱动的隔离。一般没有驱动程序

仅实现platform-driver,该模型一般用于各具体的设备驱动模块(如i2c、spi、pcie、gpio、watchdog等),这样即可通过platform-device将各具体设备驱动相关的资源信息(如中断号、寄存器地址等)从驱动中抽离出来,使得驱动模块更通用。

如我们实现一个I/O引脚模拟jtag引脚的字符驱动程序,在不使用platform模块时,那我们在

该字符驱动文件中定义使用的具体I/O值,这就使得该字符设备驱动不通用了(若下一个硬件版本修改了I/O引脚值,是否需要修改该字符设备驱动呢?)。若借助platform模块实现该字符设备驱动,则可以达到字符设备驱动的通用性,我们将引脚信息通过platform-device传递,而在platform-driver的probe接口中,我们进行字符设备驱动的创建,并传递I/O引脚的信息。通过platform-driver、cdev-add这两个模块,就使我们的I/O模拟jtag引脚的字符设备驱动更加通用,不需要依赖硬件开发板的改变而更新驱动。

platform模块与linux设备树的结合,真的是linux内核的一大特点,将之前linux板级文件更改

需要重新编译内核的步骤彻底摆除了。在linux系统内核中,只需要在内核启动时解析设备树文件,并调用platform的接口,为设备树中的节点创建platform设备,然后在具体的platform设备驱动加载时,解析注册的platform-device设备的资源信息,获取这些资源信息,并进行具体模块驱动的注册(misc_register、i2c_add_numbered_adapter、input_register_device、pci_register_driver等)。

 

本次platform模块,我们依旧从如下几个方面分析:

  1. 相关结构体分析及关联图说明
  2. Platform bus定义
  3. platform模块初始化
  4. platform总线相关接口分析
  5. platform设备相关接口分析
  6. platform驱动相关接口分析

 

相关结构体分析及关联图说明

        既然platform模块的实现时继承自device-bus-driver模型,因此其device、driver结构体定义应该也是包含device-driver、device类型的成员变量,从而实现继承功能。此处我们从platform模块的功能入手,来说明platform相关结构体。

针对platform模块而言相关的结构体包括struct platform_driver、struct platform_device、struct platform_device_info 、struct platform_device_id 、struct resource等。

struct platform_driver

该结构体可以理解为struct device_driver的子类,该结构体在包含device_driver类型变量的

基础上,还定义了probe、remove、suspend等函数指针(有一点点类似于面向对象的多态,通过调用device_driver->probe,最终调用platform_driver->probe),而id_table主要用于说明该驱动可匹配的设备名称,用于platform的device-driver匹配时使用。

该结构体的定义如下,

 

	struct platform_driver {
	    /*驱动的probe、remove等,在在device-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 *);
	    /*可以将device_driver理解为基类,则platform_driver为子类*/
		struct device_driver driver;
	    /*标识该驱动所识别的device信息(主要是根据name值进行platform device-driver的绑定)*/
		const struct platform_device_id *id_table;
	};

   相对来说就是包裹了device_driver类型的变量,同时增加该driver支持设备的id_table,以及针对device_driver的probe、remove、suspend、resume接口的自定义内容(类似于面向对象中子类重写父类的方法)。

 

 

 

struct platform_device

该结构体用于表征一个platform device,其也内嵌了struct device类型的变量,以便继承

device类型的特性,同时完成与driver、bus的关联。该结构体还包含了资源类型的变量,以便传递给platform驱动,由驱动进行申请资源,同时也实现了资源与驱动的隔离。

 

	struct platform_device {
		const char	*name;
		int		id;
		bool		id_auto;
		struct device	dev;
		u32		num_resources;
	    /*该platform设备对应的资源,通过该变量实现设备资源与设备驱动的抽离,使驱动更加通用*/
		struct resource	*resource;
	    /*该变量为对应driver中的id_table。
	    针对不支持设备树的linux,则通过id_table中的name并与platform->name进行匹配;
	    针对支持设备树的linux,则会根据device->of_node与device_driver->of_match_table中的参数匹配。
	    设备驱动模型通过device_driver、device->of_node实现了对设备树的支持。
	    */
		const struct platform_device_id	*id_entry;
	
		/* MFD cell pointer */
		struct mfd_cell *mfd_cell;
	
		/* arch specific additions */
		struct pdev_archdata	archdata;
};

struct resource结构体

在上面说明中,我们知道platform模块很好的将资源与驱动抽离,因此必然存在表示资源的结

构体变量,且与platform_device关联。该结构体的定义如下,其可以表示寄存器地址及区间,也可以表示中断信息等。

/*资源信息,包含寄存器地址空间、中断等*/
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

struct platform_device_info

该结构体用于描述一个platform device的信息,根据该结构体变量传递的信息,可以创建一个platform device。该结构体主要和platform device的创建有关,和platform device-bus-driver的关联倒是不大。

/*该结构体主要用于描述待一个platform device的信息,包括该platform-device对应的parent-device
该platform-device的名称、该platform-device所包含的资源、dma信息等,根据该
信息可以创建一个platform device*/
struct platform_device_info {
		struct device *parent;
		struct acpi_dev_node acpi_node;

		const char *name;
		int id;

		const struct resource *res;
		unsigned int num_res;

		const void *data;
		size_t size_data;
		u64 dma_mask;
};

platform bus-device-driver的关联

    以上即是platform模块相关的结构体,主要说明了一个platfom 模块如何使用linux设备驱动模型中的结构体及函数指针实现具体的总线模块(可以理解为面向对象中的多态)。下面我们基于之前对linux设备驱动模型的学习基础,将platform device、driver与设备驱动模块的关联描述一下(我们之前在设备驱动模型中已经介绍过,此处还是给出它们三者的关联图,希望对设备驱动模型不熟悉的人也可以快速理解)。

 

如下图所示,platform-device、platform-driver借助其中的device类型以及device_driver

类型的成员变量,实现bus_type、device、device_driver等设备模型关联图的建立,从而可以借助设备驱动模型中定义的接口,实现设备驱动的绑定等功能。下图在细节上可能需要斟酌,但对于描述platform-device、platform-driver、bus_type、device、device_driver、kobject等的关联来说,也已经足够了,基本上理解了下图间结构体的关联,基本上也可以理解platform 总线注册与否、driver注册与注销、device的注册与注销的实现了。(关于设备驱动模型、sysfs文件系统等内容的实现,我已经在之前的文档中分析过,需要了解的童鞋可查看我之前的分析文档)。

 

platform总线的定义

platform总线的定义如下,platform总线没有定义probe接口,因此在进行device-driver的绑定时,将直接调用device类型变量的probe接口进行探测操作。

其中也定义了platform设备的默认device属性,针对platform类型的设备,均会定义一个默

认属性,即在每一个platform设备对应的目录下,均会创建一个名为“modalias”的文件。

而match接口platform_match,则定义了platform模块device-driver匹配的通用接口,该接

口中包含了针对支持设备树以及不支持设备树的匹配方式:

若不支持设备树,则根据id_table中的变量进行device-driver的匹配;

若支持设备树,则借助device->of_node与device_driver->of_match_table中的参数进行device-driver的匹配验证。

而uevent接口则主要将该platform总线的uevent事件,当该总线上增加设备或驱动时,会调用该接口增加该platform相关的event变量,并发送给应用层。

总线名称为platform,默认的设备属性为platform_dev_attrs*/
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
static struct device_attribute platform_dev_attrs[] = {
	__ATTR_RO(modalias),
	__ATTR_NULL,
};

Platform模块的初始化

针对platform模型的初始化接口platform_bus_init,包括了platform总线的注册、platforom总线对应设备的注册等。其函数实现如下:

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}

该函数其实就是调用device_register、bus_register实现了platform总线注册以及总线对应设备的注册(关于这几个接口在之前的设备驱动模型分析中已经说明过,此处不赘述)。

 

 

platform设备注册

platform设备的注册,主要也就是调用device_register接口,将platform device注册到platform总线上,并试图完成platform device-driver的绑定操作。

针对platform设备注册的接口包括platform_driver_register、platform_device_add等,其中platform_register就是调用device_initialize、platform_device_add实现对device的初始化及注册操作,而针对platform_device_add接口,其实现流程如下,在完成针对platform相关的设备名称、依附总线、所包含资源的链接外,即调用device_add实现设备的注册操作。

针对device_add接口,我们在之前已经分析过,主要完成device-bus的绑定(包括对应kobject之间的关联、属性创建、uevent变量的添加、class模块的关联等等),此处我们仅细说下device-driver的绑定以及设备探测相关的内容,如下图所示,通过一系统的函数调用,最终通过调用接口driver_probe_device,实现设备的探测,通过调用device_driver->probe完成对设备的探测操作(此处没有画出是调用bus->probe,因为platform总线没有实现probe接口),而driver->probe最终是调用platform_driver->probe,实现对platform设备的探测操作。

 

Platform 驱动注册

platform驱动的注册接口为platform_driver_register,该接口的定义如下,主要是设置该驱动所依附的总线为platform_bus_type,同时设置该platform的probe、remove、shutdown接口(若platform driver设置了这几个函数指针,则通过将platform_driver->driver对应的接口为platform_drv_probe、platform_drv_remove、platform_drv_shutdown等,而这几个接口最终还是会调用platform_driver中的具体接口,如上图所示)。

然后调用driver_register进行驱动的注册,所谓驱动注册主要也就是完成driver-bus的绑定(包括kobject相关的绑定、属性文件的创建、uevent设置等等),并调用bus_add_driver,实现driver-device的绑定,如上图所示,针对platform_driver->driver->probe指针,要么为空,要么为platform_drv_probe,而在platform_drv_probe接口中,则调用platform_driver->probe,调用关系如上图中虚线箭头所指。

int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

Platform device、driver的注销

关于它们注销的接口,分别为platform_driver_unregister、platform_device_unregister,与上述的注册接口实现的功能刚好相反,也就是调用device_del、driver_unregister实现注销操作,此处不再分析device_del、driver_unregister。此处主要说明一下注销时detach的过程。

不管是设备的注销还是驱动的注销,通过一系统的系统调用,最终会调用drv->remove接口,实现remove操作,而remove接口即为platform_drv_remove,该接口会调用具体platform_driver->remove接口进行实现的remove操作,调用关系如下图虚线箭头所指。

以上即是detach的过程。

 

 

 

本文主要介绍了platform的概念以及platform总线存在的意义、platform模块相关的结构体、platform总线的注册、platform设备的注册与注销、platform驱动的注册与注销等。platform总线的出现,有效的将driver与设备资源进行隔开,同时也为设备树在内核的推广提供了一个较好的方式。

发布了140 篇原创文章 · 获赞 30 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/lickylin/article/details/103002569
今日推荐