Linux驱动开发—平台总线模型详解

文章目录

1.平台总线介绍

Linux 平台总线模型(Platform Bus Model)是一种设备驱动框架,用于处理那些没有标准总线(如 PCI、USB 等)的嵌入式设备。它为这些设备提供了统一的设备驱动模型,简化了设备驱动程序的编写和管理。

1.1平台总线模型的组成部分

平台总线模型主要由以下几个组成部分构成:

  1. 平台设备(Platform Device)
  2. 平台驱动(Platform Driver)
  3. 平台总线(Platform Bus)

平台设备(Platform Device)

平台设备表示硬件设备,它们通常通过设备树(Device Tree)或者板文件(Board File)进行描述。平台设备通常包括设备名称、资源(如 I/O 端口、内存区域、中断号等)以及其他平台数据。

平台驱动(Platform Driver)

平台驱动是与平台设备匹配并管理这些设备的软件模块。平台驱动提供了 proberemove 函数,用于设备的初始化和清理。

平台总线(Platform Bus)

平台总线在内核中自动管理,不需要显式地定义。它用于匹配平台设备和平台驱动。

1.2平台总线模型的优势

平台总线模型(Platform Bus Model)在 Linux 内核中的引入为嵌入式设备和驱动程序的开发带来了多项显著的优势。以下是平台总线模型的一些主要优势:

  1. 抽象和统一的设备管理

平台总线模型为没有标准总线的设备提供了统一的抽象和管理方法。通过统一的接口和机制,开发者可以更容易地管理和控制不同类型的设备,无需考虑底层硬件差异。

  1. 简化驱动开发

通过使用平台总线模型,驱动程序开发者不再需要为每种硬件设备编写特定的初始化和资源管理代码。平台设备和平台驱动的标准化接口使得驱动程序的开发和调试更加简单和一致。

  1. 设备树支持

平台总线模型支持设备树(Device Tree),这是一种硬件描述语言,广泛用于描述嵌入式系统中的硬件配置。设备树使得硬件配置从代码中分离出来,可以通过修改设备树文件而不是驱动代码来适应不同的硬件配置,极大地提高了代码的可维护性和可移植性。

  1. 自动匹配和管理

平台总线模型通过内核自动完成平台设备和平台驱动的匹配和管理。这意味着驱动程序不需要显式地查找和初始化设备,内核会自动调用合适的 proberemove 函数来管理设备的生命周期。

  1. 资源管理

平台总线模型提供了简化的资源管理机制。平台设备可以通过设备树或板文件描述其所需的资源(如 I/O 端口、内存区域、中断号等),驱动程序可以通过标准接口获取和使用这些资源,避免了手动管理资源的复杂性和潜在错误。

  1. 模块化和可移植性

通过将硬件特定的配置与驱动代码分离,平台总线模型提高了驱动程序的模块化和可移植性。驱动程序可以更容易地在不同的硬件平台之间移植,只需调整设备树或板文件中的硬件配置即可。

  1. 代码重用

由于平台总线模型提供了标准化的接口和机制,不同驱动程序之间可以共享通用的代码和逻辑。这种代码重用不仅减少了开发时间和成本,还提高了代码的稳定性和可靠性。

8. 内核维护

平台总线模型的标准化和统一管理机制使得内核代码更易于维护和升级。通过减少硬件特定的代码和逻辑,内核开发者可以更专注于改进和优化内核的通用部分,提高内核的整体性能和稳定性。

2.使用平台总线模型开发驱动

2.1注册platform设备

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/atomic.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>

// 描述硬件资源 结构体数组
static struct resource my_device_resources[] = {
    {
        .start = 0x12340000,
        .end = 0x123400FF,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 5,
        .end = 5,
        .flags = IORESOURCE_IRQ,
    }};
void my_device_release(struct device *dev)
{
    printk("This is my device release");
}

static struct platform_device my_platform_device = {
    .name = "my_platform_device",
    .id = -1,
    .num_resources = ARRAY_SIZE(my_device_resources),
    .resource = my_device_resources,
    .dev = {
        .release = my_device_release
    },
};


static int __init platform_device_init(void)
{
    platform_device_register(&my_platform_device);
    printk("platform_device_init!");
    return 0;
}

static void __exit platform_device_exit(void)
{
    platform_device_unregister(&my_platform_device);
    printk("platform_device_exit!");
}

module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("a simple of platform_device");

其中有两个关键的结构体 platform_deviceresource

struct platform_device 功能描述

platform_device 结构体表示一个平台设备,它包含了设备的名称、资源、设备数据以及其他属性。这个结构体在平台总线模型中非常重要,用于描述那些没有标准总线(如 PCI、USB 等)支持的嵌入式设备。

复制代码
struct platform_device {
    const char *name;                     // 设备名称
    int id;                               // 设备ID,通常用于区分同名设备
    struct device dev;                    // 嵌入的设备结构体
    u32 num_resources;                    // 资源数量
    struct resource *resource;            // 指向资源数组的指针
    const struct platform_device_id *id_entry; // 设备ID表
    char *driver_override;                // 用于覆盖默认的驱动程序
};

主要字段解释

  • name: 设备的名称,用于匹配设备和驱动程序。
  • id: 设备ID,用于区分具有相同名称的多个设备。通常设置为 -1。
  • dev : 嵌入的 struct device 结构体,表示通用设备结构,包含设备的通用属性和方法。
  • num_resources: 资源数量,表示设备所使用的资源数量。
  • resource: 指向资源数组的指针,资源数组包含了设备所使用的各种资源(如内存、I/O 端口、中断等)。
  • id_entry: 指向设备ID表的指针,用于在驱动程序中匹配特定的设备。
  • driver_override: 用于指定一个特定的驱动程序覆盖默认的驱动程序。

一般只需要关注name,id, dev,以及使用的资源描述

struct resource 结构体描述

resource 结构体描述了设备使用的硬件资源,例如内存地址范围、中断号等。每个设备可以有多个资源,这些资源通过 platform_device 结构体中的 resource 字段进行管理。

复制代码
struct resource {
    resource_size_t start; // 资源的起始地址
    resource_size_t end;   // 资源的结束地址
    const char *name;      // 资源的名称
    unsigned long flags;   // 资源的类型和属性
    struct resource *parent, *sibling, *child; // 资源树结构中的节点关系
};

主要字段解释

  • start: 资源的起始地址或起始值,例如内存映射地址的开始位置。
  • end: 资源的结束地址或结束值,例如内存映射地址的结束位置。
  • name: 资源的名称,用于识别资源。
  • flags : 资源的类型和属性,通过标志位表示。例如,IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源。
  • parent: 指向父资源的指针,用于构建资源层次结构。
  • sibling: 指向兄弟资源的指针,用于构建资源层次结构。
  • child: 指向子资源的指针,用于构建资源层次结构。

编译加载之后,就会在 /sys/bus/platform/devices/ 下注册新的设备

2.2注册platform驱动

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
static const struct platform_device_id driver_id_table[] = {
    { .name = "my_platform_device" },
    { } // 结尾必须有一个空的元素
};
static int my_platform_driver_probe(struct platform_device *dev )
{
    printk("my_platform_driver_probe");
    // 通过 probe 函数 拿到硬件资源描述, probe函数将传递 platform_device结构体函数
    struct resource *res;
    int irq;
    //获取内存资源
    res = platform_get_resource(dev,IORESOURCE_MEM,0);
    printk("IORESOURCE_MEM start addr is %x ",res->start);
    //获取中断资源
    res = platform_get_resource(dev,IORESOURCE_IRQ,0);
    printk("IRQ number is %d ",res->start);
    //获取完资源进行下一步的操作
    return 0;
}
static int my_platform_driver_remove(struct platform_device *dev )
{
    printk("my_platform_driver_remove");
    return 0;
}

static struct platform_driver my_platform_driver = {
    .probe = my_platform_driver_probe,
    .remove = my_platform_driver_remove,
    .driver = {
        .name = "my_platform_device", // 平台设备名一致
        .owner = THIS_MODULE,
    },
    .id_table = driver_id_table, // id_table 的优先级更高
};
static int __init platform_driver_init(void)
{
    int ret = platform_driver_register(&my_platform_driver);
    if (ret)
        printk(KERN_ALERT "Failed to register platform driver\n");
    else
        printk( "platform_driver_init!\n");
    return ret;
}
static void __exit platform_driver_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
    printk(KERN_ALERT "platform_driver_exit!\n");
}
module_init(platform_driver_init); 
module_exit(platform_driver_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("a simple of platform_driver");

我们只需要注册驱动即可,重点为platform_driver结构体

platform_driver结构体为 Linux 内核中用于描述和管理平台驱动程序的一个重要结构体。它定义了驱动程序的主要回调函数和一些元数据,这些信息允许内核在设备插入和移除时正确地调用驱动程序的相关函数。

结构体定义:

复制代码
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:

  • 类型: int (*probe)(struct platform_device *);
  • 功能: 当匹配的设备被注册时,内核调用此函数来初始化设备。通常在此函数中进行设备的硬件资源获取和初始化。
  • 返回值: 返回0表示成功,负值表示失败。

remove:

  • 类型: int (*remove)(struct platform_device *);
  • 功能: 当设备被移除时,内核调用此函数来清理和释放设备资源。
  • 返回值: 返回0表示成功,负值表示失败。

shutdown:

  • 类型: void (*shutdown)(struct platform_device *);
  • 功能: 当系统关闭或重启时,内核调用此函数来关闭设备。通常用于执行设备的关机操作。

suspend:

  • 类型: int (*suspend)(struct platform_device *, pm_message_t state);
  • 功能: 当设备进入挂起(suspend)状态时,内核调用此函数来保存设备的状态。
  • 返回值: 返回0表示成功,负值表示失败。

resume:

  • 类型: int (*resume)(struct platform_device *);
  • 功能: 当设备从挂起状态恢复时,内核调用此函数来恢复设备的状态。
  • 返回值: 返回0表示成功,负值表示失败。

driver:

  • 类型: struct device_driver
  • 功能: 包含通用的驱动程序信息,如驱动程序的名字 、模块所有者等。platform_driver 通过嵌入 device_driver 结构体继承了大部分通用的驱动程序接口。
  • 关键字段:
    • name: 驱动程序的名字,应该与 platform_device 的名字匹配。
    • owner: 指向该驱动程序模块的指针,通常设置为 THIS_MODULE

id_table:

  • 类型: const struct platform_device_id *
  • 功能: **指向设备 ID 表的指针,用于设备和驱动程序之间的匹配。**优先使用id_table进行名称匹配,如果匹配不上,将会进行device_driver 中的名字匹配

prevent_deferred_probe:

  • 类型: bool
  • 功能: 控制是否防止延迟探测。默认值是 false。

注意:必须要实现probe 函数,当平台设备和平台驱动匹配成功,就会调用probe函数,通常在此完成一些资源的初始化和调用。

例如:

复制代码
static int my_platform_driver_probe(struct platform_device *dev )
{
    printk("my_platform_driver_probe");
    // 通过 probe 函数 拿到硬件资源描述, probe函数将传递 platform_device结构体函数
    struct resource *res;
    int irq;
    //获取内存资源
    res = platform_get_resource(dev,IORESOURCE_MEM,0);
    printk("IORESOURCE_MEM start addr is %x ",res->start);
    //获取中断资源
    res = platform_get_resource(dev,IORESOURCE_IRQ,0);
    printk("IRQ number is %d ",res->start);
    //获取完资源进行下一步的操作
    return 0;
}

2.3效果演示

无论先加载平台设备模块还是平台驱动模块,就会调用probe函数,具体效果如下

相关推荐
绵绵细雨中的乡音1 小时前
网络基础知识
linux·网络
Peter·Pan爱编程2 小时前
Docker在Linux中安装与使用教程
linux·docker·eureka
kunge20132 小时前
Ubuntu22.04 安装virtualbox7.1
linux·virtualbox
清溪5492 小时前
DVWA中级
linux
Sadsvit3 小时前
源码编译安装LAMP架构并部署WordPress(CentOS 7)
linux·运维·服务器·架构·centos
xiaok3 小时前
为什么 lsof 显示多个 nginx 都在 “使用 443”?
linux
苦学编程的谢4 小时前
Linux
linux·运维·服务器
G_H_S_3_4 小时前
【网络运维】Linux 文本处理利器:sed 命令
linux·运维·网络·操作文本
Linux运维技术栈4 小时前
多系统 Node.js 环境自动化部署脚本:从 Ubuntu 到 CentOS,再到版本自由定制
linux·ubuntu·centos·node.js·自动化
拾心214 小时前
【运维进阶】Linux 正则表达式
linux·运维·正则表达式