Platform 驱动

Last edited
Last updated July 17, 2023
Pages
Tags

1. 总线驱动框架

notion image
Linux 内核中编写挂载在总线上的设备的驱动时,将一个驱动分为三部分:当前总线成总线驱动,当前设备的设备信息,当前设备的设备驱动。总线驱动是内核内部已经封装好的驱动程序。
设备的设备信息存放在内核中的一张 klist_device 链表中进行管理,设备驱动信息是放在内核中的 klist_driver 链表中进行管理。由总线驱动复杂完成设备的设备信息和驱动的匹配( match 函数)。
当设备和驱动的匹配完成之后,驱动中会执行一个 probe 函数,在 probe 函数中实现驱动的注册,设备节点的创建,硬件的相关初始化工作等。

2. platform 总线的引入以及框架

2.1 为什么引入 platform

platform 是内核抽象化出来的一个软件代码,在现实中并没有真实的中线和它对应。引入 platform 的目的是让没有挂载在总线上的设备也能够按照总线驱动模型实现驱动的编写。统一板子上各种设备驱动的编写操作。

2.2 platform 总线驱动模型

编写 platform 设备的驱动原理是,将设备信息加载在一个 platform_device 的空间中,驱动会在 platform_device 中,由 platform 总线驱动完成 platform_device 和 platform_driver 的匹配,匹配成功之后会执行 platform_driver 中的 probe 函数,在 probe 函数中实现驱动的注册、设备节点的创建以及相关硬件的控制工作。

3. platform 相关 API

3.1 设备端

#include<linux/platform_device.h> 1.设备端对象结构体 struct platform_device { const char *name;//设备名字,可以用于和驱动端的匹配 int id;//总线编号 PLATFORM_DEVID_AUTO 内核自动分配总线编号 bool id_auto; struct device dev;//是platform_device结构体的父类 u32 num_resources;//用于记录保存的设备信息的个数 struct resource *resource;//存放设备信息的数组首地址 }; 2.platform_device的父类结构体struct device struct device{ //用于释放设备中申请的资源,在卸载设备信息时执行 void (*release)(struct device *dev); }; 3.资源结构体struct resource struct resource { resource_size_t start; //资源的起始值 0X50006000 0X50006000 71 resource_size_t end; //资源的结束值 0X50006000+3 0X50006000+0X400 71 const char *name; //资源的名字 unsigned long flags;//资源的类型 IORESOURCE_IO|IORESOURCE_MEM|IORESOURCE_IRQ struct resource *parent, *sibling, *child;//当前ressourcs的父节点、兄弟节点、子节点 };
注册设备对象步骤:
1.分配设备对象并初始化 //release函数 void pdev_release(struct device *dev) { } //资源数组 struct resource res[]={ [0]={ .start=0X12345678, .end=0X12345678+59, .flags=IORESOURCE_MEM, }, [1]={ .start=71, .end=71, .flags=IORESOURCE_IRQ, }, }; //定义并初始化 struct platform_device pdev={ .name="aaaaa", .id=PLATFORM_DEVID_AUTO, .dev={ .release=pdev_release, }, .num_resources=ARRAY_SIZE(res), .resource=res, }; 2.将设备对象注册进内核 int platform_device_register(struct platform_device *pdev) 3.注销设备对象 void platform_device_unregister(struct platform_device *pdev)

3.2 驱动端

1.platform驱动信息对象结构体 struct platform_driver { int (*probe)(struct platform_device *);//当驱动和设备匹配成功后执行 int (*remove)(struct platform_device *);//当设备和驱动分离时执行 //platform_driver的父类,用于描述驱动,设备信息匹配成功后会在这里面填充 struct device_driver driver; //在driver成员里面也可以设置和设备的匹配方式 //用于设置名字表匹配,用于匹配的名字表首地址 const struct platform_device_id *id_table; }; 2.platform_driver父类结构体 struct device_driver struct device_driver { const char *name;//"aaaaa"设置和设备端进行匹配的名字 struct of_device_id *of_match_table;//用于通过设备树的形式匹配设备信息 };
注册驱动信息流程:
1.分配驱动信息对象并且初始化 //probe函数,匹配设备成功执行 int pdrv_probe(struct platform_device *pdev) { return 0; } //remove 设备和驱动分离时执行 int pdrv_remove(struct platform_device *pdev) { return 0; } struct platform_driver pdrv={ .probe=pdrv_probe, .remove=pdrv_remove, .driver={ .name="aaaaa", }, }; 2.注册驱动信息对象 #define platform_driver_register(struct platform_driver *) \ __platform_driver_register(struct platform_driver *, THIS_MODULE) 3.注销驱动信息 void platform_driver_unregister(struct platform_driver *drv)

4. platform 实例

5. 一键注册宏

5.1 概述

在 platform 驱动端内核中有一个宏定义 module_platform_driver 可以用来代替驱动中的入口出口函数以及函数内部关于 platform 驱动信息对象的注册和注销。include/linux/platform_device.h
查看源码
#define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);

5.2 代码

6. 驱动中获取设备信息

6.1 API

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
功能:获取指定设备的一个资源信息
参数:
  • dev: 设备对象指针
  • type: 资源类型 IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_IRQ
  • num: 获取到的资源在同类型资源中的索引号
返回值:成功返回值目标资源的resource首地址,失败返回NULL
2.int platform_get_irq(struct platform_device *dev, unsigned int num)
功能:获取中断类型的资源
参数:
  • dev: 设备对象指针
  • num: 获取到的资源在中断类型资源中的索引号
返回值:成功返回中断号,失败返回错误码

6.2 实例代码

7. platform_match 函数分析

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 */ //使用设备里的driver_override和驱动信息对象里的名字匹配 第一优先级 if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); /* 使用设备树匹配 第二优先级 */ if (of_driver_match_device(dev, drv)) return 1; /* 用于电源管理相关的匹配 第三优先级 */ 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 */ //使用名字匹配,第5优先级 return (strcmp(pdev->name, drv->name) == 0); }

8. 名字表匹配

8.1 概述

使用名字匹配只能让当前的驱动匹配一个设备成功,这不符合驱动的开发思想。如果想让驱动视频多款不同的硬件,可以在驱动信息中构建一张名字表,只要设备的名字在这个表中,我们就可以实现设备和驱动的匹配。

8.2 名字表的类型

#include<linux/mod_devicetable.h> struct platform_device_id { char name[PLATFORM_NAME_SIZE]; //填写名字 kernel_ulong_t driver_data;//当前对象的私有数据 };

8.3 名字表的构建

struct platform_device_id idtable[]={ {"aaaaa",0}, {"bbbbb",1}, {"ccccc",2}, {"ddddd",3}, {}, };
为什么要多一个 {}
因为 platform_match_id 接口源码的设计中没有检查边界:linux/drivers/base/platform.c,当 id 指针指针超出边界的时候,它获取到的是脏数据。

8.4 实例代码

9. 设备树匹配

9.1 概述

在 Linux 内核 3.10 版本后,要求所有的设备信息都存放在设备树里,所以驱动端匹配设备也要通过匹配获取设备信息。

9.2 创建一个测试用的设备树节点

myplatform{ compatible = "hqyj,myplatform"; reg=<0X12345678 0X400>; interrupt-parent=<&gpiof>; interrupts=<9 0>; //9表示引用中断父节点时的索引信息 0表示默认设置 led1=<&gpioe 10 0>;  };

9.3 用于设备树匹配的结构体

struct of_device_id { char name[32];//设备树节点名 char type[32];//设备类型 char compatible[128];//设备树节点内的compatible值 const void *data; };

9.3 构建用于设备树匹配的表

struct of_device_id oftbale[]={ {.compatible="hqyj,myplatform",}, {.compatible="hqyj,myplatform1",}, {}, };

9.4 实例代码