platform

platform

在Linux内核中,platform驱动模型是一种用于管理和组织与系统总线(如PCI、USB等)无关的硬件设备的架构。

这些设备通常直接连接到处理器的总线或集成在芯片组中。

platform驱动模型提供了一种通用的方式来管理这些设备,使得内核能够灵活地支持各种硬件平台。

device、bus、driver模型

在Linux操作系统中,设备、总线和驱动模型(Device, Bus, Driver Model)

是一种用于管理和组织硬件设备的架构。

这种模型将硬件设备、总线和驱动程序抽象为独立的实体,并通过它们之间的交互来实现硬件的管理和控制。

1. 设备(Device)

设备是硬件实体,如网卡、硬盘、USB设备等。每个设备都有唯一的标识符和属性,Linux内核通过这些信息来识别和管理设备。

关键点:
设备标识:每个设备都有一个唯一的标识符,通常是设备ID和厂商ID。

设备属性:设备可能具有多种属性,如设备类型、版本号、支持的功能等。

设备资源:设备可能需要分配资源,如内存、中断、I/O端口等。

2. 总线(Bus)

总线是连接设备和处理器的通信路径。常见的总线类型包括PCI、USB、I2C、SPI等。总线负责在设备和处理器之间传递数据和控制信号。

关键点:
总线类型:不同类型的总线有不同的协议和规范。

总线拓扑:总线可以有不同的拓扑结构,如树形、星形等。

总线驱动:总线需要相应的驱动程序来管理和控制总线上的设备。

3. 驱动(Driver)

驱动程序是Linux内核中用于控制和管理硬件设备的软件模块。驱动程序负责初始化设备、配置设备、处理设备的中断和数据传输等。

关键点:
设备探测(Probe):驱动程序通过探测函数(probe)来识别和初始化设备。

设备移除(Remove):当设备被移除时,驱动程序通过移除函数(remove)来释放资源并清理设备。

设备操作:驱动程序提供接口,允许应用程序通过系统调用与设备进行交互。

Linux设备模型的核心组件

Linux设备模型主要由以下几个核心组件组成:

kobject:内核对象,用于表示设备、驱动、总线等实体。

kset:内核对象集合,用于组织和管理一组kobject。

device:表示硬件设备,包含设备的属性和资源。

bus:表示总线,负责管理连接在其上的设备和驱动。

driver:表示设备驱动,负责控制和管理设备。

设备、总线和驱动模型的交互

设备、总线和驱动模型通过以下方式进行交互:

设备注册:设备通过总线注册到系统中,总线驱动负责管理这些设备。

驱动注册:驱动程序通过总线注册到系统中,总线驱动负责将设备与驱动匹配。

设备与驱动匹配:当设备与驱动匹配成功后,总线驱动会调用驱动的探测函数(probe)来初始化设备。

设备操作:应用程序通过系统调用与设备进行交互,驱动程序负责处理这些请求并操作设备。

设备移除:当设备被移除时,总线驱动会调用驱动的移除函数(remove)来释放资源并清理设备。

平台设备(Platform Device)

平台设备是那些与系统总线无关的硬件设备,如GPIO控制器、温度传感器、时钟控制器等。
这些设备通常集成在主板上或直接连接到处理器的总线。

关键点:

设备标识:平台设备通常通过设备树(Device Tree)或ACPI(高级配置和电源接口)来描述。

设备资源:平台设备可能需要分配资源,如内存、中断、I/O端口等。

平台驱动(Platform Driver)

平台驱动是用于控制和管理平台设备的软件模块。平台驱动负责初始化设备、配置设备、处理设备的中断和数据传输等。

关键点:

设备探测(Probe):驱动程序通过探测函数(probe)来识别和初始化设备。

设备移除(Remove):当设备被移除时,驱动程序通过移除函数(remove)来释放资源并清理设备。

设备操作:驱动程序提供接口,允许应用程序通过系统调用与设备进行交互。

平台总线(Platform Bus)

平台总线是虚拟总线,用于连接平台设备和平台驱动。

平台总线负责管理这些设备和驱动,并确保它们能够正确地匹配和交互。

关键点:

总线注册:平台总线在内核启动时自动注册。

设备与驱动匹配:平台总线负责将平台设备与平台驱动匹配。

平台驱动模型的交互

平台驱动模型通过以下方式进行交互:

设备注册:平台设备通过设备树或ACPI注册到系统中,平台总线负责管理这些设备。

驱动注册:平台驱动通过platform_driver_register()函数注册到系统中,平台总线负责将设备与驱动匹配。

设备与驱动匹配:当设备与驱动匹配成功后,平台总线会调用驱动的探测函数(probe)来初始化设备。

设备操作:应用程序通过系统调用与设备进行交互,驱动程序负责处理这些请求并操作设备。

设备移除:当设备被移除时,平台总线会调用驱动的移除函数(remove)来释放资源并清理设备。

示例

/dts-v1/;                             // 指定设备树的版本,表示这是一个设备树源文件

/ {                                    // 根节点,所有其他节点都将是这个根节点的子节点
    model = "My LED Controller";      // 描述设备树的模型名称,通常用于标识设备
    compatible = "my,led-controller"; // 兼容性字符串,表示该设备树和某些驱动程序或设备的兼容关系
                    //这个字段通常用于描述整个设备树的兼容性,可以表示整个系统或设备族。例如,它标识了该设备树适用于哪些硬件平台或架构。
                    // 这样,即使在将来有多个设备节点,这个根节点的 compatible 也是可以用于全局设备树兼容性检查的。
    led_controller: led_controller@12345678 { // 定义一个名为led_controller的节点,地址为0x12345678
        //compatible 这个字段用于具体描述该设备节点(如 led_controller)的兼容性,告诉内核这个特定的设备与哪个驱动程序兼容。
        // 这使得内核能够根据这个设备的描述找到相应的驱动,进行初始化和相应控制。
        compatible = "my,led-controller"; // 该节点的兼容性字符串,通常与驱动程序相对应
        reg = <0x12345678 0x1000>;  // 描述设备的寄存器地址和大小,这里地址为0x12345678,大小为0x1000字节
        interrupts = <0 75 0>;      // 定义中断信息,第一个值表示中断标志,第二个值是中断号,第三个值是触发方式(0表示边沿触发)
    };
};

#include <linux/init.h>               // 包含模块初始化和卸载的宏
#include <linux/module.h>            // 包含基本的模块定义和注册功能
#include <linux/platform_device.h>   // 包含平台设备的相关定义
#include <linux/of.h>                // 包含设备树的相关定义
#include <linux/of_device.h>         // 包含设备树设备的相关操作头文件
#include <linux/io.h>                // 包含内存映射I/O的相关函数
#include <linux/interrupt.h>         // 包含中断处理的相关函数

// 平台驱动的 probe 函数
static int my_led_probe(struct platform_device *pdev)
{
    struct resource *res;           // 定义指向资源结构的指针
    void __iomem *base;             // 定义指向映射内存的指针
    int irq;                        // 定义中断号

    pr_info("LED platform driver probed\n"); // 打印驱动探测成功的信息

    // 获取内存资源
    // pdev 是一个指向 platform_device 结构体的指针,该结构体在设备匹配时由内核传递给驱动的 probe 函数。
    // 它包含与设备相关的所有信息,包括从设备树解析出的资源。
    // 当驱动通过 platform_driver_register 注册后,内核会查找装备了与 my_led_of_match 中定义的兼容性字符串相匹配的设备树节点,
    // 然后为该设备创建一个 platform_device 实例,把从设备树中获取的信息(如资源)填充到 pdev 中。
    // 这样,后续的资源获取函数就可以基于 pdev 访问设备树提供的信息。
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) { // 如果资源获取失败
        pr_err("Failed to get memory resource\n"); // 打印错误信息
        return -ENODEV; // 返回设备不存在错误
    }

    // 映射内存
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base)) { // 如果映射失败
        pr_err("Failed to map memory\n"); // 打印错误信息
        return PTR_ERR(base); // 返回错误码
    }

    // 获取中断资源
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) { // 如果获取中断号失败
        pr_err("Failed to get IRQ resource\n"); // 打印错误信息
        return irq; // 返回中断号错误
    }

    // 请求中断
    // devm_request_irq 函数是一个包装器,它会调用 request_irq 函数,并在驱动移除时释放相应的中断。
    // 该函数会把中断号 irq、中断处理函数 my_led_irq_handler、中断触发模式(0表示边沿触发)、中断描述符和中断处理函数参数 dev_id 传递给 request_irq 函数。
    // 该函数返回 0 表示成功,负值表示失败。
    if (devm_request_irq(&pdev->dev, irq, my_led_irq_handler, 0, pdev->name, pdev)) {
        pr_err("Failed to request IRQ\n"); // 如果请求中断失败
        return -EBUSY; // 返回资源繁忙错误
    }

    pr_info("LED platform driver initialized\n"); // 打印驱动初始化成功的信息
    return 0; // 返回成功
}

// 平台驱动的移除函数
static int my_led_remove(struct platform_device *pdev)
{
    pr_info("LED platform driver removed\n"); // 打印驱动移除的信息
    return 0; // 返回成功
}

// 定义中断处理函数
static irqreturn_t my_led_irq_handler(int irq, void *dev_id)
{
    pr_info("LED IRQ handler called\n"); // 打印中断处理函数被调用的信息
    return IRQ_HANDLED; // 返回中断已处理
}

// 设备树匹配表
static const struct of_device_id my_led_of_match[] = {
    { .compatible = "my,led-controller", }, // 匹配设备树中的兼容字符串
    { /* sentinel */ } // 结束标记
};
MODULE_DEVICE_TABLE(of, my_led_of_match); // 声明设备表

// 平台驱动结构体定义
static struct platform_driver my_led_driver = {
    .driver = {
        .name = "my_led_controller", // 驱动名称
        .of_match_table = my_led_of_match, // 设备树匹配表
    },
    .probe = my_led_probe, // probe 函数
    .remove = my_led_remove, // remove 函数
};

// 驱动初始化函数
static int __init my_led_driver_init(void)
{
    int ret;

    // 注册平台驱动
    ret = platform_driver_register(&my_led_driver);
    if (ret) { // 如果注册失败
        pr_err("Failed to register LED platform driver\n"); // 打印错误信息
        return ret; // 返回错误码
    }

    pr_info("LED platform driver registered\n"); // 打印驱动注册成功的信息
    return 0; // 返回成功
}

// 驱动卸载函数
static void __exit my_led_driver_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&my_led_driver);
    pr_info("LED platform driver unregistered\n"); // 打印驱动注销的信息
}

module_init(my_led_driver_init); // 指定模块初始化函数
module_exit(my_led_driver_exit); // 指定模块卸载函数

MODULE_LICENSE("GPL"); // 定义许可证
MODULE_AUTHOR("Your Name"); // 定义作者信息
MODULE_DESCRIPTION("A simple LED platform driver"); // 定义模块描述

示例2


/dts-v1/;
/ {
    model = "My Key LED Controller";
    compatible = "my,key-led-controller";

    key_led {
        compatible = "my,led-controller";
        interrupt-parent = <&gpiof>;
        interrupts = <9 0>;
        led1 = <&gpioe 10 0>;
    };
};
示例代码执行流程
模块加载:

调用pdrv_init函数,注册平台驱动。

调用pdrv_probe函数,初始化设备。

解析设备树节点,获取GPIO和中断资源。

请求GPIO和中断,初始化字符设备驱动。

创建设备节点。

按键按下:

触发中断,调用key_led_handle函数。

改变LED状态,唤醒等待队列。

读取LED状态:

用户空间读取设备节点/dev/key_led。

如果LED状态已改变,返回新状态;否则阻塞等待。

模块卸载:

调用pdrv_exit函数,注销平台驱动。

调用pdrv_remove函数,释放资源,删除设备节点。

代码执行效果
内核日志:显示模块加载、按键中断、模块卸载等信息。

LED状态:按键按下时,LED状态取反。

字符设备:用户空间可以通过读取设备节点获取LED状态。
通过 GPIO 控制 LED 灯的开关。
使用中断处理响应外部事件(例如按钮按下),切换 LED 状态。
提供字符设备接口,可通过 /dev/key_led 进行用户空间的读写操作。

#include <linux/device.h>            // 包含设备结构体的定义
#include <linux/fs.h>                // 包含文件系统相关的定义
#include <linux/init.h>              // 包含模块初始化和卸载的宏定义
#include <linux/interrupt.h>         // 中断处理相关的定义
#include <linux/mod_devicetable.h>   // 设备表处理的定义
#include <linux/module.h>            // 模块基本定义和注册
#include <linux/of.h>                // 包含设备树相关的定义
#include <linux/of_gpio.h>           // 包含 GPIO 相关的设备树处理函数
#include <linux/of_irq.h>            // 包含中断处理的设备树相关操作
#include <linux/platform_device.h>   // 包含平台设备相关的定义

#define CNAME "key_led"              // 定义字符设备名称

// 全局变量
struct device_node* node;          // 设备树节点指针
int gpiono, irqno;                 // GPIO 引脚号和中断号
int major;                         // 字符设备主设备号
struct class* cls;                // 设备类结构体指针
struct device* dev;               // 设备结构体指针
int status = 0;                   // LED 状态,0 表示关闭,1 表示打开
wait_queue_head_t wq_head;        // 等待队列头
int condition = 0;                // 数据是否就绪的条件标志

// 中断处理函数
irqreturn_t key_led_handle(int irq, void* dev)
{
    // 切换 LED 状态
    status = !status;
    // 设置 GPIO 输出状态,以控制 LED
    gpio_set_value(gpiono, status);
    // 唤醒等待队列
    condition = 1;                  // 标记数据已准备好
    wake_up_interruptible(&wq_head); // 唤醒等待该队列的进程
    return IRQ_HANDLED;            // 表明中断已被处理
}

// 字符设备驱动 - 打开函数
int key_led_open(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 打印打开函数的调试信息
    return 0; // 返回0表示成功
}

// 字符设备驱动 - 读函数
ssize_t key_led_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    if (file->f_flags & O_NONBLOCK) { // 如果是非阻塞模式
        return -EINVAL; // 返回无效参数错误
    } else {
        // 阻塞模式,等待条件满足
        ret = wait_event_interruptible(wq_head, condition);
        if (ret) {
            pr_err("wait_event_interruptible error\n");
            return ret; // 返回错误码
        }
    }

    // 将状态拷贝到用户空间
    if (size > sizeof(status))
        size = sizeof(status); // 防止拷贝超出状态大小
    ret = copy_to_user(ubuf, &status, size);
    if (ret) {
        pr_err("copy_to_user error\n");
        return -EIO; // 返回输入输出错误
    }
    condition = 0; // 清除条件
    return size; // 返回拷贝的大小
}

// 字符设备驱动 - 关闭函数
int key_led_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 打印关闭函数的调试信息
    return 0; // 返回0表示成功
}

// 字符设备驱动 - 文件操作结构体
struct file_operations fops = {
    .open = key_led_open, // 打开函数
    .read = key_led_read, // 读函数
    .release = key_led_close, // 关闭函数
};

// 平台驱动 - 探测函数
int pdrv_probe(struct platform_device* pdev)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 打印探测函数的调试信息

    // 解析 GPIO 号并初始化 LED
    gpiono = of_get_named_gpio(pdev->dev.of_node, "led1", 0); // 从设备树获取 LED GPIO
    if (gpiono < 0) { // 检查 GPIO 号是否有效
        ret = gpiono; // 记录错误码
        goto err1; // 跳转到错误处理
    }
    ret = gpio_request(gpiono, NULL); // 请求 GPIO
    if (ret) {
        pr_err("gpio_request error\n");
        goto err1; // 跳转到错误处理
    }
    ret = gpio_direction_output(gpiono, 0); // 设置 GPIO 为输出并初始为低
    if (ret) {
        pr_err("gpio_direction_output error\n");
        goto err2; // 跳转到错误处理
    }

    // 解析中断号并注册中断处理
    irqno = platform_get_irq(pdev, 0); // 获取中断线号
    if (irqno < 0) {
        pr_err("irq_of_parse_and_map error\n");
        goto err2; // 跳转到错误处理
    }
    ret = request_irq(irqno, key_led_handle, IRQF_TRIGGER_FALLING, CNAME, NULL);
    if (ret) {
        pr_err("request_irq error\n");
        goto err2; // 跳转到错误处理
    }

    // 注册字符设备
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        pr_err("register_chrdev error\n");
        goto err3; // 跳转到错误处理
    }

    // 创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        pr_err("class_create error\n");
        ret = PTR_ERR(cls);
        goto err4; // 跳转到错误处理
    }
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
    if (IS_ERR(dev)) {
        pr_err("device_create error\n");
        ret = PTR_ERR(dev);
        goto err5; // 跳转到错误处理
    }

    init_waitqueue_head(&wq_head); // 初始化等待队列头
    return 0; // 返回成功

err5:
    class_destroy(cls); // 销毁类
err4:
    unregister_chrdev(major, CNAME); // 注销字符设备
err3:
    free_irq(irqno, NULL); // 释放中断请求
err2:
    gpio_free(gpiono); // 释放 GPIO
err1:
    return ret; // 返回错误码
}

// 平台驱动 - 移除函数
int pdrv_remove(struct platform_device* pdev)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); // 打印移除函数的调试信息
    device_destroy(cls, MKDEV(major, 0)); // 销毁设备节点
    class_destroy(cls); // 销毁设备类
    unregister_chrdev(major, CNAME); // 注销字符设备
    free_irq(irqno, NULL); // 释放中断请求
    gpio_set_value(gpiono, 0); // 关闭 LED
    gpio_free(gpiono); // 释放 GPIO

    return 0; // 返回成功
}

// 设备树匹配表
struct of_device_id oftable[] = {
    {
        .compatible = "my,led-controller", // 与驱动匹配的设备树字符串
    },
    { /* 表示结束 */ }
};

// 平台驱动结构体
struct platform_driver pdrv = {
    .probe = pdrv_probe, // 探测函数
    .remove = pdrv_remove, // 移除函数
    .driver = {
        .name = "duang", // 驱动程序名称,不能省略
        .of_match_table = oftable, // 设备树匹配表
    }
};

// 模块初始化函数
static int __init pdrv_init(void)
{
    return platform_driver_register(&pdrv); // 注册平台驱动
}

// 模块退出函数
static void __exit pdrv_exit(void)
{
    platform_driver_unregister(&pdrv); // 注销平台驱动
}

module_init(pdrv_init); // 指定模块初始化函数
module_exit(pdrv_exit); // 指定模块卸载函数
MODULE_LICENSE("GPL"); // 声明模型许可证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可能只会写BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值