ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

驱动学习第二周

驱动学习第二周 一Linux字符设备驱动框架字符设备驱动以字节流形式访问设备文件/dev/ 下设备对应的设备文件网络设备驱动通过写入数据读取数据的方式来操作网卡等网络设备块设备驱动以固定数据量块来进行读写操作的设备。#include linux/init.h #include linux/module.h MODULE_LICENSE(GPL v2);//开源协议 //file_operations操作函数数个 //file_operations结构体填充 //probe函数提供设备注册 //remove函数提供设备注销 //platform_driver结构体填充 // 加载驱动进入初始化 static int led_init(void) { //驱动注册 return 0; } // 驱动卸载推出函数 static void led_exit(void) { //驱动注销 return; } module_init(led_init); // 向内核告知加载驱动时的入口 module_exit(led_exit); // 向内核告知推出驱动时运行该函数Linux将设备抽象为文件进行操作将对驱动的访问控制抽象为对文件的readwriteopenclose。这些操作对应的函数指针填充于结构体file_operations注册驱动时通过存入结构体cdev传递给内核。为了方便系统管理驱动设备驱动运行时需要向内核注册并提交相关信息。二应用程序访问底层驱动的过程系统加载驱动时根据驱动声明的入口函数启动驱动通常情况下用于注册驱动程序需提交注入名字拥有者以及用于设备探测移除匹配的相关信息。完成所有注册相关操作并帮助驱动成功找到设备后VFS在/dev/下创建设备文件以及对应的inode结构体。1 VFS虚拟文件系统根据文件名找到 inode以及存放设备号(dev_t)内核拿设备号查cdev_map查到驱动注册的 struct cdev。2将 cdev-ops (file_operations) 赋值给 struct file-f_op调用file_operations中存放的open函数等待驱动初始化。3内核分配struct file记录打开模式和偏移 file-f_op 已经指向 file_operations后续 read/write/ioctl 直接通过 file-f_op 调用不再经过 cdev_map 查找将 fd 绑定到 struct file 最后返回给用户态。后续 read()/write()/ioctl()通过 file-f_op 直接调用驱动函数。cdev_init() 初始化cdev结构体alloc_chrdev_region() 注册设备号cdev_add() 将cdev和设备号绑定在cdev_map上class_create() 创建设备类/sys/classdevice_create() 通过struct class发送uevent事件通知mdev创建设备三Linux platform子系统框架3.1概述与基本结构为了方便代码的移植原来的驱动被分为总线bus设备device驱动driver。Platform 设备驱动专门用于处理**不可自动发现non-discoverable** 的设备即 SoC 内部直接集成或硬连线到主板上的外设如 UART、SPI控制器、GPIO控制器、I2C控制器等。通过维护驱动链表和设备链表来存放设备信息和驱动信息。通过将硬件资源寄存器地址、中断号、时钟、GPIO写入设备树Board File来减小Linux核心文件的大小。通过probe/remove 回调来进行硬件初始化与业务逻辑的注册。Linux Device Model (统一设备模型)├── struct bus_type → platform_bus_type (Platform 总线类型)├── struct device → platform_device (Platform 设备实例)├── struct device_driver → platform_driver (Platform 驱动实例)└── struct kobject/kset → sysfs 文件系统暴露接口static int my_probe(struct platform_device *pdev) { // 1. 获取硬件资源 struct resource *res platform_get_resource(pdev, IORESOURCE_MEM, 0); int irq platform_get_irq(pdev, 0); // 2. 初始化硬件 void __iomem *base devm_ioremap_resource(pdev-dev, res); // 3. 注册字符设备向上层提供接口 // ... return 0; } static const struct of_device_id my_of_match[] { { .compatible vendor,my-device }, { } }; static struct platform_driver my_driver { .probe my_probe, .remove my_remove, .driver { .name my-device, .of_match_table my_of_match, }, }; module_platform_driver(my_driver);3.2驱动编写1确认设备类型。字符寄存器块2确认设备和描述设备所需资源。3确认设备总线匹配移除函数规划驱动持有者名字匹配列表。4确认设备注册需要的信息和驱动注册需要的信息。struct platform_device *pdev和struct platform_driver *pdriver5确认设备匹配优先级匹配表中专用设备匹配表中通用设备设备名与驱动名。6匹配好后VFS会调用注册驱动注册信息中的probe函数并提供注入信息的platform_device指针。此函数需要以该结构体指针获取的信息请求相应设备资源并对对应设备进行注册。7设备离线后相应资源应该释放。VFS调用的remove函数需写入清除或释放相关资源的程序。1. of_match_table设备树 compatible 匹配 ← 最高优先级 2. id_table平台设备 ID 表匹配 3. driver.name device.name名称匹配 ← 最低优先级3.3设备树随着设备越来越多描述设备资源需要的资源越来越多让Linux内核存储这些会使内核文件变得大而杂乱所以要求所有设备信息进入设备树存储以保证内核干净和可移植性。.dts 文件 → dtb → 内核启动时解析 → 自动创建 platform_devicecompatible:驱动通过of_match_table中的compatible与设备树匹配reg 寄存器的起始地址 地址的长度statusdisable不注册okay注册设备名-gpios gpio控制器的标签名 管脚编号 标志interrupt-parent 中断控制器的标签名interrupts 1个或多个uint32的数字描述中断的信息 ,...;// #interrupt-cells四Linux 中断子系统框架cpu资源是有限的不可能要求cpu时刻关注输入设备的状态。为了让cpu有更多时间处理主任务片上新增加一个中断控制器GIC通知cpu设备发来信息。GIC通注册信息管理中断相关资源包括是否使能屏蔽中断号对应的设备结构体中断类型普通IRQ快速FIQ中断优先级。通过以上信息和接收到的信号返回中断号设备结构体。SPI共享外设中断由 Distributor 路由到指定 CPUUARGPIO。PPI私有外设中断每个 CPU 核一份TimerSGI软件触发用于核间通信(IPI) 核间通知4.1注册中断释放int ret request_irq(irq_number, handler_func, flags, name, dev_id);// 常用 flags// IRQF_SHARED - 共享中断线// IRQF_TRIGGER_RISING - 上升沿触发// IRQF_TRIGGER_FALLING - 下降沿触发// 释放中断free_irq(irq_number, dev_id);4.2共享中断中断控制存在共线共号问题为了判断是谁触发的中断信号soc通常会停供相应寄存器或类似机制来辅助判断。4.3中断上下半由于中断处理必须及时处理但又不能耽搁cpu太久需要对中断处理做切割将信息快速保存的流程或快速硬件操作的指令等打包为中断上半部分让中断回调直接拉起执行。解析校验拆分包处理多段采样等则放在下半。如果一个任务时间十分敏感将其放在上半部如果一个任务和硬件有关将其放在上半部如果一个任务要保证不被其他中断打断将其放在上半部其他所有任务考虑放在下半部4.4 tasklet下半部实现1Linux除了由硬件触发的中断以外还存在软中断soft IRQ通过软件实现中断效果的方式可以让中断不依赖外部中断控制器的情况发生。tasklet则是Linux官方封装的软中断使用接口。tasklet_init()注册tasklet回调函数tasklet_schedule()调用软中断一个tasklet被tasklet_schedule调用过它的state状态就被设置为TASKLET_STATE_SCHED,接下来等待被执行。tasklet被执行完后它的状态才会被清除。如果重复调用一个tasklet,它如果没有被执行过是不会重复执行的。4.5workqueue下半实现2工作队列(workqueue)是另外一种将工作推后执行的形式它和我们前面讨论的所有其他形式都不相同。工作队列可以把工作推后交由一个内核线程去执行一这个下半部分总是会在进程上下文执行但由于是内核线程其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。通常在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则1如果推后执行的任务需要睡眠那么只能选择工作队列2如果推后执行的任务需要延时指定的时间再触发那么使用工作队列因为其可以利用timer延时3 如果推后执行的任务需要在一个tick之内处理则使用软中断或tasklet因为其可以抢占普通进程和内核线程4如果推后执行的任务对延迟的时间没有任何要求则使用工作队列此时通常为无关紧要的任务。另外如果你需要用一个可以重新调度的实体来执行你的下半部处理你应该使用工作队列。它是惟一能在进程上下文运行的下半部实现的机制也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时在你需要执行阻塞式的I/O操作时它都会非常有用。实际上工作队列的本质就是将工作交给内核线程处理因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高而工作队列实现了内核线程的封装不易出错所以我们也推荐使用工作队列。INIT_WORK初始化work_struct结构体并绑定工作队列回调函数schedule_work将工作项放入系统默认工作队列的链表中并唤醒工作线程执行cancel_work_sync取消已提交但未执行的工作项并同步等待正在执行的工作项完成五Linux Input子系统框架5.1概述Input 子系统用于管理各类输入设备如按键、触摸屏、鼠标等负责对外部事件的感知与上报。它将硬件层产生的原始输入事件经过设备驱动层和核心层处理后通过统一的接口,当 input_register_device() 执行时内核会自动创建如 /dev/input/eventX提供给用户空间应用程序使用。linux内核注册了多个handler驱动模块并通过input_handler_list链表维护Linux内核中维护了一个名为input_handler_list的链表每个input_handler都会被注册到这个链表 上而链表是通过类型为list_head的成员node串联起来的。input_register_device() 时 遍历 input_handler_list 每个 handler 调用自己的 match() 函数 匹配成功 → 调用 handler-connect() 例如 evdev handler 匹配所有设备 → 创建 /dev/input/eventX kbd handler 只匹配键盘 → 创建 /dev/input/ttyX5.2数据结构示例// 输入设备结构体 struct input_dev { const char *name; // 设备名称 const char *phys; // 物理路径 struct input_id id; // 设备ID (vendor, product, version) unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 支持的事件类型 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 支持的按键 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 支持的相对坐标 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 支持的绝对坐标 // ... 其他字段 };5.3 Input 驱动编写流程// 1. 分配 input_dev struct input_dev *input input_allocate_device(); // 2. 设置设备信息 input-name my-keypad; input-id.bustype BUS_HOST; input-id.vendor 0x0001; input-id.product 0x0001; // 3. 设置支持的事件类型 input-evbit[0] BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); input-keybit[BIT_WORD(KEY_A)] BIT_MASK(KEY_A); // 4. 注册设备 input_register_device(input); // 5. 在中断或定时器中上报事件 input_report_key(input, KEY_A, 1); // 按下 input_sync(input); // 同步 input_report_key(input, KEY_A, 0); // 释放 input_sync(input);EV_KEY 0x01 按键事件EV_REL 0x02 相对坐标鼠标移动EV_ABS 0x03 绝对坐标触摸屏EV_SYN 0x00 同步事件EV_LED 0x11 LED事件
返回列表