• Linux platform总线驱动基础知识


    Linux platform总线驱动基础知识


    platform总线驱动模型

      在Linux2.6版本以后的设备驱动编写时,一般需关心三部分内容,即 总线(bus)设备(device)驱动(driver) 三个实体。总线是设备和驱动之间的桥梁,Linux通过总线将设备与驱动两部分绑定起来。当向系统注册一个驱动时,总线会寻找与之匹配的设备,如果有的话会将两者联系起来;相反的,当向系统注册一个设备时,总线会在驱动中查找有没有与之匹配的设备,如果有的话也将两者联系起来。

      在真实的项目开发中,一般Linux设备和驱动通常挂接在一类总线上,但还是有一部分外设不依赖于类似I2C、SPI、USB等此类总线。基于此,产生了称为platform的一类总线,相应的设备称为platform_device,驱动称为platform_driver。platform_device并不是和Linux字符设备、块设备、网络设备并列的概念,而是一种附加手段。类似于将SoC内部集成的I2C、PWM、LCD等控制器归纳为platform_device。

    平台总线注册和匹配方式

      在Linux设备驱动中,总线相对比较重要,它主要完成匹配设备和驱动,每当有新的设备或新的驱动加入到platform总线上时,总线便会调用platform_math函数对新增的设备进行配对。

      Linux内核通过platform_bus_type来描述总线。

    \drivers\base\platform.c
    struct bus_type platform_bus_type = {
    	.name		= "platform",
    	.dev_groups	= platform_dev_groups,
    	.match		= platform_match,
    	.uevent		= platform_uevent,
    	.probe		= platform_probe,
    	.remove		= platform_remove,
    	.shutdown	= platform_shutdown,
    	.dma_configure	= platform_dma_configure,
    	.pm		= &platform_dev_pm_ops,
    };
    EXPORT_SYMBOL_GPL(platform_bus_type);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      在上述结构体中,match函数指针相对重要,该函数指针指向的函数将负责实现platform总线驱动和playform总线设备的匹配过程。对于每个驱动总线,他都必须实例化该函数指针。platform_match函数原型如下所示

    \drivers\base\platform.c
    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);
    }
    
    • 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

      platform总线在Linux内核启动时自动进行注册,即内核自动调用函数platform_bus_init完成注册。

      platform总线提供了四种存在先后顺序的匹配方式:设备树>ACPI匹配模式>id_table方式>字符串比较

    \drivers\base\platform.c
    int __init platform_bus_init(void)
    {
    	int error;
    
    	early_platform_cleanup();
    
    	error = device_register(&platform_bus);
    	if (error) {
    		put_device(&platform_bus);
    		return error;
    	}
    	error =  bus_register(&platform_bus_type);
    	if (error)
    		device_unregister(&platform_bus);
    	of_platform_register_reconfig_notifier();
    	return error;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

      在上述platform总线初始化中,bus_register函数完成向Linux内核注册platform平台总线。

    平台设备(platform_device

      platform_device主要是为驱动程序提供设备信息,设备信息包括硬件信息软件信息两部分。

    • 硬件信息

      硬件信息主要是驱动程序使用到的寄存器、中断号、内存、IO资源等。

    • 软件信息

      包括I2C设备的涉笔地址、片选设备的片选信号、以太网的MAC地址等信息。

      platform设备主要维护platform_device结构体,结构体原型如下所示。

    \include\linux\platform_device.h
    struct platform_device {
    	const char	*name;
    	int		id;
    	bool		id_auto;
    	struct device	dev;
    	u64		platform_dma_mask;
    	struct device_dma_parameters dma_parms;
    	u32		num_resources;
    	struct resource	*resource;
    
    	const struct platform_device_id	*id_entry;
    	char *driver_override; /* Driver name to force a match */
    
    	/* MFD cell pointer */
    	struct mfd_cell *mfd_cell;
    
    	/* arch specific additions */
    	struct pdev_archdata	archdata;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结构体常用成员含义:

    • name

      设备名称,需要于platform_driver的name值一致,否则纵向将匹配不成功。

    • id

      id号,用于给插入该总线相同name的设备进行编号,如果只有一个设备,填入-1。

    • dev

      内嵌的device结构体。Linux内核采用大量的面向对象的编程思想,platform_device通过继承该结构体,可复用他的相关代码,方便管理平台设备。其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。

    • num_resources

      表示资源的数量,当结构体成员resources存放的是数组时,一般记录resources数组的个数,内核通过宏定义ARRAY_SIZE计算数组的个数。

    • resources

      平台设备提供给驱动的资源、信息,例如寄存器。

    • id_entry

      平台总线提供的另一种匹配方式,原理是比较字符串。

    platform_device 涉及的常用函数

    \drivers\base\platform.c
    /*
     * 平台设备注册,将设备挂接在platform总线上
     * [pdev] platform_device类型结构体指针
     */
    int platform_device_register(struct platform_device *pdev)
    {
    	device_initialize(&pdev->dev);
    	setup_pdev_dma_masks(pdev);
    	return platform_device_add(pdev);
    }
    
    /*
     * 平台设备注销
     * [pdev] platform_device类型结构体指针
     */
    void platform_device_unregister(struct platform_device *pdev)
    {
    	platform_device_del(pdev);
    	platform_device_put(pdev);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

      在platform_device结构体中resource结构体相对比较重要,这是实现驱动和设备分离的关键,它主要用于描述硬件信息,包括终端号、寄存器物理地址等,其结构体原型如下:

    \include\linux\ioport.h
    struct resource {
    	resource_size_t start;
    	resource_size_t end;
    	const char *name;
    	unsigned long flags;
    	unsigned long desc;
    	struct resource *parent, *sibling, *child;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结构体常用成员变量含义:

    • start

      资源的开始值,对于I/O内存来说是起始的内存地址,对于中断资源来说是起始的中断号,对于DMA资源来说是起始的DMA通道号

    • end

      资源的结束值。

    • flags

      用于指定该资源的类型,在Linux中,资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型,最常见的有以下几种:

    资源宏定义描述
    IORESOURCE_IO用于IO地址空间,对应于IO端口映射方式
    IORESOURCE_MEM用于外设的可直接寻址的地址空间
    IORESOURCE_IRQ用于指定该设备使用某个中断号
    IORESOURCE_DMA用于指定使用的DMA通道

      当有的设备可能有多个资源,通常使用platform_get_resource函数来获取资源,原型如下:

    \drivers\base\platform.c
    /*
     * 获取设备信息
     * [dev] 指定要获取哪个平台设备的资源;
     * [type] 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
     * [num] 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
     */
    struct resource *platform_get_resource(struct platform_device *dev,
    				       unsigned int type, unsigned int num)
    {
    	u32 i;
    
    	for (i = 0; i < dev->num_resources; i++) {
    		struct resource *r = &dev->resource[i];
    
    		if (type == resource_type(r) && num-- == 0)
    			return r;
    	}
    	return NULL;
    }
    EXPORT_SYMBOL_GPL(platform_get_resource);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    平台驱动(platform_driver

      platform驱动核心为platform_driver结构体,结构体内部包含多个函数指针,用于对platform驱动进行管理,如下所示:

    \include\linux\platform_device.h
    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;
    	const struct platform_device_id *id_table;
    	bool prevent_deferred_probe;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结构体常用成员含义:

    • probe

      函数指针,当驱动与设备匹配成功后probe函数将会执行。驱动开发者需要在驱动程序中初始化该函数指针,一般通过该函数对设备进行一系列的初始化操作,类似于字符设备驱动中的init函数。

    • remove

      函数指针,用于移除某个平台时调用,驱动开发者需要在驱动程序中初始化该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程,类似于字符设备驱动中的exit函数。

    • driver

      Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性。

    • id_table

      id_table是个表,元素类型为platform_device_id类型,表示该驱动能够兼容的设备类型。

    platform_driver常用函数

    \include\linux\platform_device.h
    #define platform_driver_register(drv) \
    	__platform_driver_register(drv, THIS_MODULE)
    
    • 1
    • 2
    • 3
    \driver\base\platform.c
    /*
     * 注册平台驱动
     * [drv] platform_driver类型结构体指针
     * [owner] 一般为THIS_MODULE
     */
    int __platform_driver_register(struct platform_driver *drv,
    				struct module *owner)
    {
    	drv->driver.owner = owner;
    	drv->driver.bus = &platform_bus_type;
    
    	return driver_register(&drv->driver);
    }
    
    /*
     * 注销平台驱动
     * [drv] platform_driver类型结构体指针
     */
    void platform_driver_unregister(struct platform_driver *drv)
    {
    	driver_unregister(&drv->driver);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    参考


    《Linux设备驱动开发详解 基于最新的Linux4.0内核》

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.pdf》

    《[野火]i.MX Linux开发实战指南》

    [驱动注册]platform_driver_register()与platform_device_register()

    🎪

    🔊

  • 相关阅读:
    RBTree的删除
    spring-cloud集成数据库版本迁移工具flyway
    相机摄影入门技巧,数码摄影技巧大全
    脚手架开发流程
    招投标系统软件源码,招投标全流程在线化管理
    Django 简单入门(一)
    【程序员面试金典】01.05. 一次编辑
    EasyExcel 复杂数据导出
    java异常处理
    乒乓球游戏-第12届蓝桥杯Scratch选拔赛真题精选
  • 原文地址:https://blog.csdn.net/qq_41596356/article/details/125553281