Linux设备驱动模型 流程和原理

Linux驱动设备模型
Linux字符设备就是一种设备(device),在Linux中作为文件保存,并且其中的各种操作方法集合还可以自定义
除了字符设备之外还有块设备和网络设备
现在的设备中大部分都很复杂,不只是写一下简单的打开、关闭、读写操作就可以的
所以引入了->驱动设备模型<-
设备驱动模型分为三个部分:设备(device)、驱动(driver)和链接两者之间的总线(bus)
将设备、驱动程序和总线(如PCI、USB)抽象为统一的对象,并建立了设备和驱动程序之间的绑定机制。
以下是三个主要的数据结构:
struct device:表示一个设备对象
struct device_driver:表示一个驱动程序对象
struct bus_type:表示一种总线类型(如PCI、USB)
其中,总线是Linux内核负责(实例为platform_bus_type),我们只需要关心设备和驱动

默认称设备为platform_device
      驱动为Platform_driver
他们的结构体是:

#include <linux/module.h>
#include <linux/platform_device.h>//用到这两个头文件

/* 驱动程序结构体 */
struct platform_driver {
    int (*probe)(struct platform_device *pdev);
    /* probe函数指针,当设备与驱动匹配时调用,用于初始化设备 */

    int (*remove)(struct platform_device *pdev);
    /* remove函数指针,当设备从系统中移除时调用,用于释放资源 */

    void (*shutdown)(struct platform_device *pdev);
    /* shutdown函数指针,在系统关机或重启时调用,通常用于执行设备的安全关闭操作 */

    int (*suspend)(struct platform_device *pdev, pm_message_t state);
    /* suspend函数指针,在设备进入低功耗状态时调用 */

    int (*resume)(struct platform_device *pdev);
    /* resume函数指针,在设备从低功耗状态恢复时调用 */

    struct device_driver driver;
    /* device_driver结构体,包含了驱动程序的基本信息和操作 */
};
Platform Device 结构体
platform_device结构体用于描述一个平台设备。它的定义通常在内核头文件linux/platform_device.h中。

#include <linux/module.h>
#include <linux/platform_device.h>//用到这两个头文件

/* 设备结构体 */
struct platform_device {
    const char *name;
    /* 设备名称,用于标识设备 */

    int id;
    /* 设备ID,用于区分同类型的多个设备 */

    struct device dev;
    /* device结构体,包含设备的基本信息和操作 */

    u32 num_resources;
    /* 设备资源的数量 */

    struct resource *resource;
    /* 设备资源数组,描述设备使用的IO端口、内存和中断等资源 */

    const struct platform_device_id *id_entry;
    /* 设备ID表,用于驱动程序匹配设备 */

    char *driver_override;
    /* 可选的驱动程序覆盖名称,用于指定设备使用特定的驱动程序 */
};
      

现在要写驱动,就要把驱动和设备分开写。
bus来匹配二者的时候,有三种匹配方式,1.名称 2.id 3.设备树(dts)

设备驱动和设备一样,在使用之前要向内核注册,代码如下:
static int __init my_driver_init(void) {
    return platform_driver_register(&my_platform_driver);
}

static void __exit my_driver_exit(void) {
    platform_driver_unregister(&my_platform_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

驱动和设备匹配的时候要执行probe函数,配套地,要写一个remove函数来释放资源
probe函数的主要作用是当设备被检测到时(通常在设备被插入或系统启动时),
内核调用该函数来初始化设备并将其绑定到驱动程序上
最后Probe函数再调用驱动自己的Probe函数,完成驱动的初始化
Probe函数的实现过程:
static int my_device_probe(struct platform_device *pdev)
{
    // 1. 检查设备和驱动程序是否匹配
    // 2. 分配资源
    // 3. 初始化设备
    // 4. 注册设备
    // 5. 创建设备文件

    return 0; // 返回0表示成功,负值表示失败
}

直接实例化一个驱动:
static struct platform_driver my_platform_driver = {
    .probe = my_device_probe,
    .remove = my_device_remove,
    .driver = {
        .name = "my_device",
        .owner = THIS_MODULE,
        .of_match_table = my_device_of_match,
    },
};

module_platform_driver(my_platform_driver);

通过驱动设备模型来写device的时候直接写在设备树上,名称、id、资源等,用到设备树是为了有更好的兼容性
以下是一个简单的设备树:(假如这个设备叫xiaolaoshu,id为666666)

/dts-v1/;
/* 设备树版本声明,必须有 */
#include <dt-bindings/input/input.h>
/* 包含了一些设备树绑定的宏定义,这里是为了展示,可以根据需要添加或省略 */

{
    /* 定义根节点 */
    compatible = "my,device-tree";
    /* 兼容性字符串,用于描述这个设备树的类型,驱动程序可以通过它来匹配设备树 */

    aliases {
        /* 定义设备别名,方便引用设备 */
        mouse = &xiaolaoshu;
        /* 将别名“mouse”指向设备节点“xiaolaoshu” */
    };

    xiaolaoshu: mouse@666666 {
        /* 定义设备节点,名字为“xiaolaoshu”,地址为“666666” */
        compatible = "my,xiaolaoshu";
        /* 设备的兼容性字符串,驱动程序可以通过它来匹配设备 */
        reg = <0x666666>;
        /* 设备的寄存器地址,假设为“666666” */
        interrupt-parent = <&intc>;
        /* 指定中断控制器,引用下文定义的“intc”节点 */
        interrupts = <1 2>;
        /* 中断配置,假设中断号为1,类型为2(具体含义根据硬件手册确定) */
        gpio = <&gpio1 10 0>;
        /* GPIO配置,引用下文定义的“gpio1”控制器,使用其第10号引脚 */
        status = "okay";
        /* 设备状态,表示设备可用 */

        /* 定义虚构的资源配置 */
        resource1 {
            resource-name = "power-gpio";
            /* 资源名称为“power-gpio” */
            resource-gpio = <&gpio1 10 0>;
            /* 资源使用的GPIO配置,引用“gpio1”控制器的第10号引脚 */
        };

        resource2 {
            resource-name = "data-gpio";
            /* 资源名称为“data-gpio” */
            resource-gpio = <&gpio1 11 0>;
            /* 资源使用的GPIO配置,引用“gpio1”控制器的第11号引脚 */
        };
    };

    intc: interrupt-controller {
        /* 定义中断控制器节点,名字为“intc” */
        compatible = "my,intc";
        /* 中断控制器的兼容性字符串 */
        #interrupt-cells = <2>;
        /* 定义中断单元数,通常是2,表示中断号和中断类型 
        address-cell和size-cell是为了规定设备节点怎么去写地址,值1代表占32位,值2代表64位*/
        
        interrupt-controller;
        /* 声明这是一个中断控制器 */
    };

    gpio1: gpio@1 {
        /* 定义GPIO控制器节点,名字为“gpio1”,地址为1 */
        compatible = "my,gpio";
        /* GPIO控制器的兼容性字符串 */
        #gpio-cells = <2>;
        /* 定义GPIO单元数,通常是2,表示GPIO号和标志位 */
        gpio-controller;
        /* 声明这是一个GPIO控制器 */
    };
};

关于设备树:一般是由硬件厂商写好的,我们只需要看懂它,添加或修改一部分即可


 

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值