平台总线-platform

前言

参考迅为电子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。

        struct device dev:表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作。
        必须为该参数提供一个有效的 struct device 对象,该结构体的 release 方法必须要实现,否则 在编译的时候会报错。卸载platform_devices 时释放。
        u32 num_resources:设备资源的数量。如果设备具有资源(如内存区域、中断等),则需
要提供资源的数量。
        struct resource *resource:指向设备资源的指针。

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);
};
其中最重要的是前四个参数,每个参数的具体介绍如下所示:
        (1 resource_size_t start :资源的起始地址。它表示资源的起始位置或者起始寄存器的地
址。
        (2 resource_size_t end :资源的结束地址。它表示资源的结束位置或者结束寄存器的地
址。
        (3 const char *name :资源的名称。它是一个字符串,用于标识和描述资源。
        (4 unsigned long flags :资源的标志位。它包含了一些特定的标志,用于表示资源的属
性或者特征。例如,可以用标志位来指示资源的可用性、共享性、缓存属性等。 flags 参数的
具体取值和含义可以根据系统和驱动的需求进行定义和解释,但通常情况下,它用于表示资源
的属性、特征或配置选项。下面是一些常见的标志位及其可能的含义
1.4.2、 资源类型相关标志位
IORESOURCE_IO :表示资源是 I / O 端口资源。
IORESOURCE_MEM :表示资源是内存资源。
IORESOURCE_REG :表示资源是寄存器偏移量。
IORESOURCE_IRQ :表示资源是中断资源。
IORESOURCE_DMA :表示资源是 DMA (直接内存访问)资源。
1.4.3、 资源属性和特征相关标志位:
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; /* 是否阻止延迟探测 */
};
        probe:平台设备的探测函数指针。如果platform_device 与platform_driver匹配成功时就会进入到该回调函数中,该 函数将被调用以初始化和配置设备。 很重要

        remove:平台设备的移除函数指针。当平台设备从系统中移除时,该函数将被调用以执行清理和释放资源的操作。

        shutdown:平台设备的关闭函数指针。当系统关闭时,该函数将被调用以执行与平台设备相关的关闭操作。 

        suspend:平台设备的挂起函数指针。当系统进入挂起状态时,该函数将被调用以执行与 平台设备相关的挂起操作。
        resume:平台设备的恢复函数指针。当系统从挂起状态恢复时,该函数将被调用以执行与 平台设备相关的恢复操作。
        driver:包含了与设备驱动程序相关的通用数据,它是 struct device_driver 类型的实例。其
中包括驱动程序的名称、总线类型、模块拥有者、属性组数组指针等信息,该结构体的name参数,与platform_device中的.name参数相同才能匹配成功,从而进入probe函数(很重要

        id_table:指向 struct platform_device_id 结构体数组的指针,用于匹配平台设备和驱动程 序之间的关联关系。通过该关联关系,可以确定哪个平台设备与该驱动程序匹配,和.driver.name 起到相同的作用,但是优先级高于.driver.name。(很重要

        prevent_deferred_probe:一个布尔值,用于确定是否阻止延迟探测。如果设置为 true,则 延迟探测将被禁用。(没用过这个成员)

        上面说到的两个非常重要的结构体,需要做下说明 struct device_driver
        2.4、 struct device_driver 结构体
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中进行其他操作了,比如说在里面进行字符设备注册。可以参考字符设备(二)-----驱动模型-CSDN博客

平台总线platform bus)模型是指一种计算机系统中用于连接各种硬件和设备的通信总线。这种总线模型通常用于多个处理器、存储器、输入/输出设备等之间的通信和数据传输。平台总线模型的设计有助于简化系统结构,提高系统的可扩展性和可靠性。 平台总线模型通常由一组标准接口和协议组成,以确保各种硬件和设备之间的互操作性。这些接口和协议包括物理层接口、数据传输协议、控制信号、时序和电气规范等。通过这些标准化的接口和协议,不同厂商生产的硬件和设备可以在同一个系统中无缝地协同工作。 在平台总线模型中,通常会存在主设备和从设备的概念。主设备负责发起和控制数据传输,而从设备则被动地响应主设备的请求并提供服务。这种分工使得系统中的各种硬件和设备可以协调工作,大大提高了系统的整体效率和性能。 在实际应用中,平台总线模型广泛用于各种计算机系统中,如个人电脑、服务器、嵌入式系统等。它为不同类型的硬件和设备之间提供了一个统一的通信桥梁,简化了系统的设计和维护,同时也提高了系统的可靠性和稳定性。 总之,平台总线模型是一种重要的计算机系统设计模型,它为各种硬件和设备之间的通信和数据传输提供了统一的标准接口和协议,有助于提高系统的可扩展性、互操作性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值