找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

671

积分

0

好友

91

主题
发表于 前天 06:33 | 查看: 8| 回复: 0

在掌握了中断、内存、定时器等独立概念后,如何将这些知识点系统性地组织起来,编写出结构清晰的驱动代码?这就要引入 Linux 驱动开发中最经典的设计范式:总线 (Bus) - 设备 (Device) - 驱动 (Driver) 模型。在嵌入式及 SoC 开发领域,Platform 总线是最常被使用的一种抽象。

Platform 模型的核心思想在于解耦。它将驱动代码清晰地划分为两部分:

  • Device (硬件描述):描述板载设备的硬件资源,例如地址、中断号等。在现代 Linux 内核中,这一角色通常由设备树 (Device Tree) 承担。
  • Driver (软件逻辑):定义如何操作设备,其逻辑不依赖于硬件的具体物理地址,这些地址会在运行时动态获取。
  • Platform Bus (总线):充当“红娘”,负责将匹配的 Device 与 Driver 关联起来。

我们可以用一个简单的比喻来理解:

  • Device 在设备树中声明:“我叫 learn,my-led,我有寄存器和中断两个资源!”
  • Driver 向内核注册时说:“我能驱动所有名叫 learn,my-led 的设备。”
  • Bus 发现双方的“暗号”(即 compatible 属性字符串)一致,于是触发驱动的 probe 函数。这个 probe 函数是驱动真正的入口(类似于普通 C 程序的 main 函数),只有匹配成功时才会被执行。

下面通过一个完整的示例,展示 Platform 模型的代码实现。

一、硬件描述:设备树

硬件信息通常在 .dts.dtsi 文件中描述,这是一个设备树节点示例:

/* 此节点描述了一个硬件设备及其资源 */
my_demo_device {
    /* 匹配“暗号”:必须与驱动中的定义完全一致 */
    compatible = "learn,platform-demo";
    /* 资源1:寄存器物理地址 (基地址 0x10005000,长度 0x100) */
    reg = <0x10005000 0x100>;
    /* 资源2:中断号 (连接到 gpio1 控制器的第 5 号引脚) */
    interrupt-parent = <&gpio1>;
    interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
    status = "okay";
};

理解设备树是深入嵌入式Linux开发的基石,更多关于系统配置和内核的知识可以在 网络/系统 板块找到。

二、驱动逻辑:Platform 驱动

以下是驱动源码 platform_demo_drv.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h> // 核心头文件
#include <linux/mod_devicetable.h> // 存放 of_device_id
#include <linux/io.h>
#include <linux/interrupt.h>

/* 驱动私有数据结构 */
struct my_data {
    void __iomem *base_addr; // 映射后的虚拟地址
    int irq;                 // 虚拟中断号
};

/*
 * ============================================================
 * 2. PROBE 函数:当硬件和驱动匹配成功时,内核自动调用
 * ============================================================
 */
static int my_probe(struct platform_device *pdev)
{
    struct my_data *data;
    struct resource *res;
    int ret;

    dev_info(&pdev->dev, "【匹配成功】Probe 函数被调用!\n");

    /* 1. 分配私有数据内存 (使用 devm 系列函数自动管理) */
    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    /* 将私有数据指针保存到 platform_device 中,便于后续取用 */
    platform_set_drvdata(pdev, data);

    /*
     * 2. 获取并映射内存资源 (MMIO)
     * 对应设备树中的 reg = <...>;
     */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第一个内存资源
    // devm_ioremap_resource 自动完成 request_mem_region 和 ioremap
    data->base_addr = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(data->base_addr)) {
        return PTR_ERR(data->base_addr);
    }
    dev_info(&pdev->dev, "映射物理地址 %pr 到虚拟地址 %p\n", res, data->base_addr);

    /*
     * 3. 获取中断资源
     * 对应设备树中的 interrupts = <...>;
     */
    data->irq = platform_get_irq(pdev, 0); // 获取第一个中断号
    if (data->irq < 0) {
        return data->irq;
    }

    /*
     * 4. 申请中断
     * 注意:此处中断处理函数为 NULL 仅用于演示,实际开发中必须提供
     */
    ret = devm_request_irq(&pdev->dev, data->irq,
                           NULL, // 此处应为中断处理函数指针
                           0, "my_platform_irq", data);
    dev_info(&pdev->dev, "驱动初始化完成!\n");
    return 0;
}

/*
 * ============================================================
 * 3. REMOVE 函数:当驱动卸载或设备被移除时调用
 * ============================================================
 */
static int my_remove(struct platform_device *pdev)
{
    // struct my_data *data = platform_get_drvdata(pdev);
    dev_info(&pdev->dev, "驱动已卸载,Bye!\n");
    // 注意:由于使用了 devm_ 系列函数,内存和中断资源会自动释放
    // 若未使用,则需在此手动 iounmap 和 free_irq
    return 0;
}

/*
 * ============================================================
 * 1. 匹配表 (Match Table)
 * 这是驱动与设备树“接头”的凭证,内核据此进行匹配
 * ============================================================
 */
static const struct of_device_id my_match_table[] = {
    { .compatible = "learn,platform-demo" }, // 必须与设备树中的 compatible 属性一致
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_match_table);

/*
 * 4. 定义驱动结构体
 */
static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove, // 注意:在新版本内核中,remove 可能返回 void
    .driver = {
        .name = "my_demo_drv",
        .of_match_table = my_match_table, // 关联匹配表
    },
};

/*
 * 5. 模块初始化和退出宏
 * module_platform_driver 宏自动生成 module_init 和 module_exit,
 * 并在内部调用 platform_driver_register/Unregister
 */
module_platform_driver(my_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("CXY");

三、关键代码解析

  1. module_platform_driver(my_driver): 这是一个非常实用的宏。它自动生成了 module_initmodule_exit 函数,并在其中分别调用了 platform_driver_register(&my_driver)platform_driver_unregister(&my_driver)。对于大多数标准的 Platform 驱动,直接使用此宏可以简化代码。

  2. platform_get_resource: 此函数是驱动获取硬件资源的桥梁。它能够从 platform_device 中解析出设备树里定义的物理地址、中断等资源,使驱动无需硬编码这些值,极大地提高了代码的通用性和可移植性。

  3. devm_ (Device Managed) 系列函数: 这是现代 Linux 驱动开发的“最佳实践”之一。devm_kzallocdevm_request_irqdevm_ioremap_resource 等函数都带有自动资源管理功能。当驱动的 probe 函数执行失败,或 remove 函数被调用时,通过这些函数申请的资源会被内核自动释放。这能有效避免常见的内存泄漏、资源未释放等问题,显著提升驱动代码的健壮性。掌握这类资源管理技巧是高效 Linux 系统开发的重要部分,相关内容可在 运维/DevOps 专题中进一步学习。

通过以上剖析,可以看到 Platform 模型通过清晰的层级划分和自动匹配机制,使得 Linux 驱动开发更加模块化和规范化,尤其适合资源多样的嵌入式场景。




上一篇:H5动画实战:淘宝直播双11互动玩法中Spine与Phaser的技术实现与工程保障
下一篇:EasyPen安全扫描实战:Python+跨平台GUI,赋能企业资产与漏洞管理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-11 02:06 , Processed in 0.080273 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表