Linux设备驱动模型之platform设备

Linux设备驱动模型之platform设备

上一章节介绍了Linux字符设备驱动,它是比较基础的,让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中,个人认为完全手写一个字符设备驱动的机会比较少,更多的都是基于前人的代码修修补补过三年。在内核驱动中,你会看到比较多的platform相关的字样,他们具体是什么呢,下面我们一起来看看。

platform bus

在嵌入式Linux中,可能会听到过I2C总线、SPI总线、USB总线等,但是platform总线是什么呢,它有什么规范呢?platform总线是一个虚拟的总线,它没有像上述介绍的总线有通信协议规范,它仅仅是为了更好的管理Linux内核设备驱动而虚化出来的。

项目开发过程中,会有很多的设备驱动需要注册,但是这些设备都会具有很多共同点,比如休眠唤醒时需要注意的操作,每个设备都会有差异,但是都会有需要,开关机时也是如此。为了方便管理这些设备驱动,Linux增加了platform设备和驱动,方便驱动在开发过程做到统一管理,将设备和驱动都挂载在总线上,当有新的设备或者驱动加入时,都会进行比较,当设备和驱动信息一致时,则进行相应的操作,完成资源申请和设备注册。

Linux内核提供了注册总线的接口,下面我们来看看platform的总线都提供了那些信息:

/* 从platform_bus的定义来看,虽然我们说platform总线,实际上它还是一个设备 */
struct device platform_bus = {
    
    
        .init_name      = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

/* 定义platform设备的默认属性 */
static struct attribute *platform_dev_attrs[] = {
    
    
        &dev_attr_modalias.attr,
        &dev_attr_driver_override.attr,
        NULL,
};
ATTRIBUTE_GROUPS(platform_dev);

/* 提供设备的休眠唤醒接口函数,platform设备的休眠唤醒都通过这个接口完成调用 */
static const struct dev_pm_ops platform_dev_pm_ops = {
    
    
        .runtime_suspend = pm_generic_runtime_suspend,
        .runtime_resume = pm_generic_runtime_resume,
        USE_PLATFORM_PM_SLEEP_OPS
};

/* 定义platform总线的一些总要接口 */
struct bus_type platform_bus_type = {
    
    
        .name           = "platform",
        .dev_groups     = platform_dev_groups,	/* 总线上设备的默认属性 */
        .match          = platform_match,		/* 前面我们说到注册分别注册设备和驱动之后,将会进行设备驱动匹配,platform设备就是通过该函数实现的 */
        .uevent         = platform_uevent,		/* 当添加、删除设备的时候,生成uevent添加到环境变量,实际上可以理解为有设备添加、删除时发送广播  */
        .pm             = &platform_dev_pm_ops,	/* 这个就是上面的休眠唤醒时的调用接口了 */
};
EXPORT_SYMBOL_GPL(platform_bus_type);

int __init platform_bus_init(void)
{
    
    
        int error;

        early_platform_cleanup();

    	/* 注册platform设备 */
        error = device_register(&platform_bus);
        if (error)
                return error;
    	/* 向系统注册platform总线 */
        error =  bus_register(&platform_bus_type);
        if (error)
                device_unregister(&platform_bus);
        of_platform_register_reconfig_notifier();
        return error;
}

bus_register函数主要进行下面几个操作:

  1. 将驱动自探测标志drivers_autoprobe设置为1,这样后续只要有device或driver加入bus,都将会触发设备驱动探测;
  2. 创建bus的默认属性节点;
  3. 创建bus的probe等文件节点;

向系统注册platform总线之后,又是怎么使用呢?下面我们一起看看platform设备和驱动的操作。

platform device

platform device一般在内核驱动中都是添加到各个SOC接口或者外设中出现的,作为一个对接内核设备的接口,使各自的设备驱动可更具灵活性。我们先看看内核是怎么定义这个platform device。

struct platform_device {
    
    
        const char      *name;			/* 设备属于什么名字的 */
        int             id;				/* 设备的索引,比如有多个类似的设备 */
        struct device   dev;			/* 内核设备的基础 */
        ...
        u32             num_resources;
        struct resource *resource;		/* 用于这个设备的资源保存了,比如中断和寄存器地址等 */
        ...
};

上面基本了解了platform device的类型之后,那么它又是怎么登记注册到Linux内核的呢?主要是通过下面的两个接口函数。

/* 登记注册platform device */
int platform_device_register(struct platform_device *pdev);
/* 登记注册num个platform device */
int platform_add_devices(struct platform_device **devs, int num);

那么platform_device_register()函数的操作主要是什么呢?

  1. 通过 device_initialize 初始化 pdev->dev,也就是初始化上述的struct device;
  2. 设置platform device的 DMA mask;
  3. 初始化pdev->dev的parent为platform_bus以及bus为platform_bus_type;
  4. 更新platform device资源;
  5. 通过device_add()向内核添加pdev->dev设备;

似乎完成了注册,现在只有设备,还没有驱动,接着看驱动又是怎样的。

platform driver

内核就是将设备与驱动分离的,设备可以是看得见、摸得着的实体,也可以是一些虚拟的、远端的设备,而驱动则是使设备可以正常工作的代码。platform driver的定义如下:

struct platform_device_id {
    
    
        char name[PLATFORM_NAME_SIZE];
        kernel_ulong_t driver_data;
};

struct platform_driver {
    
    
    	/* probe函数用于探测是否存在和该驱动一致的platform device,由各自driver实现,驱动加载时调用 */
        int (*probe)(struct platform_device *);
    	/* remove函数则是在卸载模块时调用,释放相关资源 */
        int (*remove)(struct platform_device *);
    	/* 系统关机时将会调用shutdown */
        void (*shutdown)(struct platform_device *);
    	/* suspend和resume则是系统进入休眠和唤醒时调用 */
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
    	/* 内核驱动的基础 */
        struct device_driver driver;
    	/* platform device和driver匹配信息 */
        const struct platform_device_id *id_table;
};

上面罗列了platform driver的基本成员,各自驱动将会实现,基础需要完成probe、remove和id_table信息的填充。而向内核注册驱动则是通过platform_driver_register完成驱动的注册,而platform_driver_register又主要是进行几个操作:

  1. 将driver的bus设置为platform_bus_type;
  2. 而driver为platform driver,所以device_driver的probe、remove、shutdown函数设置为plaform driver的函数;
  3. 通过driver_register函数向内核注册驱动;

probe的意义

单纯只有硬件机器而没有软件,或许它就是一堆废铜烂铁;而只有软件驱动,也是毫无用处,没有载体,发挥不出它们的作用。虽然我们有注册device、driver,但是如果没有匹配,也是不行,就像用一个ARM64的固件放在mips架构的芯片上运行失败一样,所以probe的意义就是通过driver确认当前的device是否与自身一致,一致则可进行系统节点的注册等。

上面在介绍platform device时有name成员,而platform driver中有id_table,什么时候会触发设备和驱动进行匹配呢?

回头看通过platform_driver_register()函数注册驱动时,driver_register()中会调用bus_add_driver()向总线注册driver,在bus_add_driver()中由因为总线已经设置了drivers_autoprobe,将会通过driver_attach()进行device、driver的匹配,这个过程如下:

platform_driver_register
	driver_register
		bus_add_driver
			driver_attach
				__driver_attach
					driver_match_device
						bus->match

而driver_attach中,将从bus的klist_devices(总线上的所有设备信息都在该链表上)逐个device与driver进行匹配,匹配是通过__driver_attach()函数完成,在__driver_attach中,通过driver_match_device()调用bus的match函数,platform bus的match函数为platform_match()。platform_match将会依次进行以下的对比:

  1. 如果platform device的driver_override置位,则对比pdev的driver_override和drv的name是否一致;
  2. 进行设备树的匹配,将对比drv的of_match_table和dev的of_node信息是否一致;
  3. 进行acpi的信息对比,这点有点不是很清晰,平常没有使用;
  4. 进行pdrv->id_table与pdev->name的对比,平常这个使用较多;
  5. 最后则是直接对比pdev->name和drv->name;

如果上述对比下来都不一致,则没有匹配,否则匹配成功。设备驱动匹配成功,将会通过device_driver_attach()函数进行匹配。

device_driver_attach将会进行以下的操作:

  1. 获取device所属的引脚配置信息;
  2. 在/sys目录下建立driver与device的链接;
  3. 优先调用设备所在bus的probe函数,如果没有则调用driver的probe函数;
  4. 将设备所有的引脚配置为默认状态;

像platform bus是没有实现probe函数的,所以将会调用在注册驱动时赋值的platform_drv_probe()函数,而在这里,实际上就是调用platform driver的probe函数,也就是驱动自己实现的probe函数了。

当调用到probe函数时,证明整个Linux内核的设备驱动模型已经跑通,但实际上这个设备是否存在,需要probe函数实现,probe函数确认device存在之后,将会可以进行资源的申请以及相关设备节点的注册操作。

设备驱动模型

platform设备驱动模型

猜你喜欢

转载自blog.csdn.net/weixin_41944449/article/details/132963909