当前位置: 首页 > news >正文

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 输出状态,以控制 LEDgpio_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 号并初始化 LEDgpiono = of_get_named_gpio(pdev->dev.of_node, "led1", 0); // 从设备树获取 LED GPIOif (gpiono < 0) { // 检查 GPIO 号是否有效ret = gpiono; // 记录错误码goto err1; // 跳转到错误处理}ret = gpio_request(gpiono, NULL); // 请求 GPIOif (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); // 关闭 LEDgpio_free(gpiono); // 释放 GPIOreturn 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"); // 声明模型许可证

http://www.mrgr.cn/news/30209.html

相关文章:

  • 深圳华为展厅:30寸OLED透明屏中控桌引领科技新风尚
  • Java多线程详解⑦(全程干货!!!)内存可见性 || volatile || JMM || wait notify notifyAll
  • 丹摩征文活动|Llama3.1:从安装到熟练使用的全方位教程
  • QT信号和槽与自定义的信号和槽
  • 使用docker方式进行Oracle数据库的物理迁移(helowin/oracle_11g)
  • 深度学习之卷积问题
  • Linux系统查看硬件配置教程
  • Android U 多任务启动分屏——Launcher流程(下分屏 更新中)
  • 电力电容器、电子电容器的区别
  • shell脚本中的if语句、shell脚本中的if条件语句介绍和使用案例(非常全面)
  • DDoS对策是什么?详细解说DDoS攻击难以防御的理由和对策方法
  • 高等数学 3.3 泰勒公式
  • SpringCloud微服务实现服务降级的最佳实践
  • 数据结构-排序(冒泡,选择,插入,希尔,快排,归并,堆排)
  • 人工智能时代,程序员如何保持核心竞争力?
  • 制作一个rabbitmq-sdk
  • 组态软件之万维组态介绍(web组态、html组态、vue2/vue3组态)
  • 文献分享: SIGMOD-24论文集概览
  • 【Python】从基础到进阶(九):探索Python中的迭代器与生成器
  • 【数据结构初阶】栈接口实现及经典OJ题超详解
  • 【QT】基于HTTP协议的网络应用程序
  • 计算机组成原理——进制与编码
  • 24最新Stable Diffusion之Lora模型训练详细教程
  • 嵌入式八股文(C语言篇)
  • css 横向盒子 不换行 超过出现横向滚动条
  • 【九盾安防】叉车安全解决方案——叉车限速器改善仓库和人身安全