前言
参考迅为电子platform平台总线
问题一、platform bus是什么?
a、平台总线(platform bus)是Linux内核中提供的一种虚拟总线,将原本可以使用一个驱动C文件分成两个C文件,一个是平台设备device.c ,一个是平台驱动driver.c。
b、把设备的硬件资源信息放入device.c中,不变的驱动程序放入driver.c中。
c、platform bus 充当“桥梁”就是将这两个文件“链接起来”,负责他们进行匹配、绑定。
问题二、为什么需要platform bus?
a、设备驱动分离。传统代码都是将设备和驱动放入同一个.c文件中,这样就导致代码冗余和可维护性差。这种分离使得device 和driver的职责更加清晰,提高代码可读性和维护性。
b、减少代码的重复性和提高代码的复用性。平台总线使得相同类型的设备可以共享相同的驱动代码。如果一个硬件平台存在多个相同类型的设备,只需要写一个通用代码,然后为每个设备创建相应的device.c文件,将设备特定的代码放入其中。这样就可以减少代码的重复性,提高代码重用性和可维护性。
c、提高可移植性。开发者可以编写适应平台的驱动程序,从而支持特定的外设,使得驱动可以更容易地在不同的硬件平台之间进行移植和重用。
一、注册、卸载platform device
1.1、 platform_device_register 函数
platform_device_register 函数是用于将 platform_device 结构体描述的平台设备注册到内核中。
int platform_device_register(struct platform_device *);
1.2、platform_device_unregister函数
platform_device_unregister 函数用于设备的清理和释放操作。函数用于设备的清理和释放操作。
void platform_device_unregister(struct platform_device *pdev);
1.3、struct platform_device
在上面注册和卸载时都涉及到一个结构体 struct platform_device ,这个结构体是用于描述平台设备的数据结构。它包含了平台设备的各种属性和 信息,用于在内核中表示和管理平台设备。
struct platform_device {
const char *name; // 设备的名称,用于唯一标识设备
int id; // 设备的 ID,可以用于区分同一种设备的不同实例
bool id_auto; // 表示设备的 ID 是否自动生成
struct device dev; // 表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作
u32 num_resources; // 设备资源的数量
struct resource *resource; // 指向设备资源的指针
const struct platform_device_id *id_entry; // 指向设备的 ID 表项的指针,用于匹配设备和驱动
char *driver_override; // 强制设备与指定驱动匹配的驱动名称
/* MFD cell pointer */
struct mfd_cell *mfd_cell; // 指向多功能设备(MFD)单元的指针,用于多功能设备的描述
/* arch specific additions */
struct pdev_archdata archdata; // 用于存储特定于架构的设备数据
};
name:设备的名称,用于唯一标识设备。很关键,主要用于跟platform driver 进行配对(比如 "xxx_gpio"),必须提供一个唯一的名称。
id:设备的 ID,可以用于区分同一种设备的不同实例。一般设置-1。
1.4、struct resource 结构体
用于描述系统重的设备资源,包括内存区域、I/O端口、中断等 。
struct resource {
resource_size_t start; /* 资源的起始地址 */
resource_size_t end; /* 资源的结束地址 */
const char *name; /* 资源的名称 */
unsigned long flags; /* 资源的标志位 */
unsigned long desc; /* 资源的描述信息 */
struct resource *parent; /* 指向父资源的指针 */
struct resource *sibling; /* 指向同级兄弟资源的指针 */
struct resource *child; /* 指向子资源的指针 */
/* 以下宏定义用于保留未使用的字段 */
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
IORESOURCE_IO :表示资源是 I / O 端口资源。IORESOURCE_MEM :表示资源是内存资源。IORESOURCE_REG :表示资源是寄存器偏移量。IORESOURCE_IRQ :表示资源是中断资源。IORESOURCE_DMA :表示资源是 DMA (直接内存访问)资源。
IORESOURCE_PREFETCH :表示资源是无副作用的预取资源。IORESOURCE_READONLY :表示资源是只读的。IORESOURCE_CACHEABLE :表示资源支持缓存。IORESOURCE_RANGELENGTH :表示资源的范围长度。IORESOURCE_SHADOWABLE :表示资源可以被影子资源替代。IORESOURCE_SIZEALIGN :表示资源的大小表示对齐。IORESOURCE_STARTALIGN :表示起始字段是对齐的。IORESOURCE_MEM_64 :表示资源是 64 位内存资源。IORESOURCE_WINDOW :表示资源由桥接器转发。IORESOURCE_MUXED :表示资源是软件复用的。IORESOURCE_SYSRAM :表示资源是系统 RAM (修饰符)。
1.4.4 其他状态和控制标志位:
IORESOURCE_EXCLUSIVE :表示用户空间无法映射此资源。IORESOURCE_DISABLED :表示资源当前被禁用。IORESOURCE_UNSET :表示尚未分配地址给资源。IORESOURCE_AUTO :表示地址由系统自动分配。IORESOURCE_BUSY :表示驱动程序将此资源标记为繁忙。
例如:
构造资源变量
#define MEM_START_ADDR 0xFDD60000
#define MEM_END_ADDR 0xFDD60004
#define IRQ_NUMBER 101
static struct resource my_resources[] = {
{
.start = MEM_START_ADDR, // 内存资源起始地址
.end = MEM_END_ADDR, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},{
.start = IRQ_NUMBER, // 中断资源号
.end = IRQ_NUMBER, // 中断资源号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
构造platform_device变量
static struct platform_device my_platform_device = {
.name = "my_platform_device", // 设备名称
.id = -1, // 设备 ID
.num_resources = ARRAY_SIZE(my_resources), // 资源数量
.resource = my_resources, // 资源数组
#if 0
.dev={
.release = my_platform_device_release, // 释放资源的回调函数
},
#else //或者这样写
.dev.release = my_platform_device_release, // 释放资源的回调函数
#endif
};
示例代码platform_device.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#define MEM_START_ADDR 0xFDD60000
#define MEM_END_ADDR 0xFDD60004
#define IRQ_NUMBER 101
static struct resource my_resources[] = {
{
.start = MEM_START_ADDR, // 内存资源起始地址
.end = MEM_END_ADDR, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
{
.start = IRQ_NUMBER, // 中断资源号
.end = IRQ_NUMBER, // 中断资源号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void my_platform_device_release(struct device *dev)
{
// 释放资源的回调函数
}
static struct platform_device my_platform_device = {
.name = "my_platform_device", // 设备名称
.id = -1, // 设备ID
.num_resources = ARRAY_SIZE(my_resources), // 资源数量
.resource = my_resources, // 资源数组
.dev.release = my_platform_device_release, // 释放资源的回调函数
};
static int __init my_platform_device_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device); // 注册平台设备
if (ret) {
printk(KERN_ERR "Failed to register platform device\n");
return ret;
}
printk(KERN_INFO "Platform device registered\n");
return 0;
}
static void __exit my_platform_device_exit(void)
{
platform_device_unregister(&my_platform_device); // 注销平台设备
printk(KERN_INFO "Platform device unregistered\n");
}
module_init(my_platform_device_init);
module_exit(my_platform_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ll");
二、注册、卸载platform driver
在前面实验中已经讲解了如何注册一个平台设备,后面还需要相应的平台驱动跟他匹配。
2.1、platform_driver_register 函数
platform_driver_register 函数用于将一个平台驱动程序注册到内核中。通过注册平台驱动程
int platform_driver_register(struct platform_driver *driver);
2.2、platform_driver_unregister 函数
platform_device_unregister 函数用于从内核中注销平台设备。通过调用该函数,可以将指
void platform_driver_unregister(struct platform_driver *drv);
2.3、platform_driver 结构体
与platform_device 注册相似,platform_driver 的注册与卸载中都伴随着一个 platform_driver结构体,这个结构体用于编写平台设备驱动程序的重要数据结构。它提 供了与平台设备驱动相关的函数和数据成员,以便与平台设备进行交互和管理。
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; /* 是否阻止延迟探测 */
};
remove:平台设备的移除函数指针。当平台设备从系统中移除时,该函数将被调用以执行清理和释放资源的操作。
shutdown:平台设备的关闭函数指针。当系统关闭时,该函数将被调用以执行与平台设备相关的关闭操作。
id_table:指向 struct platform_device_id 结构体数组的指针,用于匹配平台设备和驱动程 序之间的关联关系。通过该关联关系,可以确定哪个平台设备与该驱动程序匹配,和.driver.name 起到相同的作用,但是优先级高于.driver.name。(很重要)
prevent_deferred_probe:一个布尔值,用于确定是否阻止延迟探测。如果设置为 true,则 延迟探测将被禁用。(没用过这个成员)
struct device_driver {
const char *name; //名称
struct bus_type *bus; //挂接的总线
struct module *owner; //
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table; //用设备树匹配时,用于匹配设备
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p; //保存相关链表,也保存了kobj
};
struct platform_driver 结构体继承了 struct device_driver 结构体,因此可以 直接访问 struct device_driver 中定义的成员。这个结构体成员太多了,我们一般用的比较多的就是 .name ,.owner,of_match_table 这三个成员变量。
.name :上面有提到过,主要是用做跟platform_driver中的name进行匹配
.owner :看到这玩意就写THIS_MODULE
.of_match_table :一个匹配列表也是用作匹配的,只不过这个是跟设备树(配备树也可以理解为是跟platform_device类似的描述设备信息的,后面再说)进行匹配的。
三、匹配细节
在上面的函数解析中是不是发现platform_driver跟platform_device 有好几种匹配的方式,那么这几种方式都有什么差异,或者几个匹配方式都存在时,Linux内核会优先对哪个进行匹配?
因为还没有讲到设备树,所以这里就在源码中随便找了一段示例代码
在这段代码里就有这三种匹形式,.name ,of_match_table,和id_table,
of_match_table用于从完整设备树项(具有供应商部分中的条目)查找匹配项.
id_table用于从剥离的设备树条目(无供应商部分)中查找匹配项
of_match_table表中的厂商和设备树中条目compatible(包含供应商字符串) 的厂商“atmel”不匹配,则会用id_table去匹配设备树条目compatible (不包含供应商字符串),最后如果都不存在则会去匹配.name中的"at91-reset"信息,其中只要有一个能匹配成功都会进入probe函数。
所以匹配优先级为 of_match_table > id_table -> name。
示例代码platform_driver.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
static int my_platform_driver_probe(struct platform_device *pdev)
{
struct resource *res_mem, *res_irq;
// 方法1:直接访问 platform_device 结构体的资源数组
if (pdev->num_resources >= 2) {
struct resource *res_mem = &pdev->resource[0];
struct resource *res_irq = &pdev->resource[1];
// 使用获取到的硬件资源进行处理
printk("Method 1: Memory Resource: start = 0x%llx, end = 0x%llx\n",
res_mem->start, res_mem->end);
printk("Method 1: IRQ Resource: number = %lld\n", res_irq->start);
}
// 方法2:使用 platform_get_resource() 获取硬件资源
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res_mem) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res_irq) {
dev_err(&pdev->dev, "Failed to get IRQ resource\n");
return -ENODEV;
}
// 使用获取到的硬件资源进行处理
printk("Method 2: Memory Resource: start = 0x%llx, end = 0x%llx\n",
res_mem->start, res_mem->end);
printk("Method 2: IRQ Resource: number = %lld\n", res_irq->start);
return 0;
}
static int my_platform_driver_remove(struct platform_device *pdev)
{
// 设备移除操作
return 0;
}
static struct platform_driver my_platform_driver = {
.driver = {
.name = "my_platform_device", // 与 platform_device.c 中的设备名称匹配
.owner = THIS_MODULE,
},
.probe = my_platform_driver_probe,
.remove = my_platform_driver_remove,
};
static int __init my_platform_driver_init(void)
{
int ret;
ret = platform_driver_register(&my_platform_driver); // 注册平台驱动
if (ret) {
printk("Failed to register platform driver\n");
return ret;
}
printk("Platform driver registered\n");
return 0;
}
static void __exit my_platform_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver); // 注销平台驱动
printk("Platform driver unregistered\n");
}
module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ll");
Makefile
export ARCH=arm64
export CC=/home/ooo/work/project/rk3568/rk356x_linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc
#obj-m += platform_device.o
obj-m += platform_driver.o
APP:=app
KDIR :=/home/ooo/work/project/rk3568/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make
$(CC) $(APP).c -o $(APP)
cp *.ko $(APP) /mnt/hgfs/share/
app:
$(CC) $(APP).c -o $(APP)
cp:
cp *.ko $(APP) /mnt/hgfs/share/
clean:
make -C $(KDIR) M=$(PWD) clean
rm $(APP)
device 与driver .name匹配
运行测试
1.首先加载paltform_device.ko驱动
2、继续加载platform_driver.ko驱动
从上面实验可以看到,已经在platform_driver驱动中拿到platform_device设备中的信息
在拿到设备信息后就可以在probe中进行其他操作了,比如说在里面进行字符设备注册。可以参考字符设备(二)-----驱动模型-优快云博客