1.Linux 字符设备驱动框架及应用程序访问底层驱动过程1.1Linux 字符设备驱动框架1.2应用程序访问底层驱动过程1.应用层打开设备文件时操作系统首先通过设备文件找到对应的inode结构体再根据inode结构体中记录的设备号到字符设备映射表中查找。2.若字符设备映射表中未找到对应设备号直接报错返回文件找不到若找到则可通过设备号和cdev结构体的关联找到对应的cdev结构体进而获取到其中的file_operation操作接口可操作底层设备。3.打开成功后系统会创建file结构体将cdev结构体相关信息填充到file结构体中后续操作可直接通过file结构体调用底层接口无需重复查找提高操作效率。补充机制说明inode这个结构体第一次打开这个设备时对应的i_cdev是没有值的发现对应的指针为空它就会通过设备号去查找去找对应的底下的cdev,找到之后会把对应的cdev的地址放到i_cdev中第二次open打开这个设备文件的时候对应的i_cdev已经记录了就可以通过i_cdev找到file这个结构体为啥也保存了file_operations的地址。因为我们对应打开文件之后一般都是读写那对于的read和write第一个参数都是文件描述符你通过文件描述就是找到的是file结构体从而找到file_operations不然的话你需要重新找一边大大提高了对应的效率。这些结构体都在VFS层虚拟文件系统VFS是一个抽象层其向上提供了统一的文件访问接口而向下则兼容了各种不同的文件系统。2.Linux Platform子系统框架总线、设备、驱动框架核心思想将设备信息从驱动中分离出来我们需要在操作系统中添加设备和驱动两部分设备中包含的是设备的信息和资源驱动中包含操作设备的函数接口。实际上是操作系统中的两个链表一个驱动链表一个设备链表设备和驱动都会注册到总线上当注册设备的时候会去寻找同名的驱动注册驱动时会去寻找同名的设备相互查找根据总线来写驱动的思路1、根据自己的设备来确定总线的类型platform bus / i2C bus /usb bus/....2、根据总线的类型来确定设备在总线上如何描述struct platform_device{设备名;设备的资源通用的设备描述(struct device : platform_data-记录设备私有的信息)id_entry:当设备和驱动的id_table中某一个成员匹配上的时候这个成员就会记录id_table中匹配上的成员地址};struct resource{资源的开始资源的结束资源的类型:IO资源(寄存器地址) 中断资源(中断号) DMA资源(通道)}3、根据总线的类型来确定驱动在总线上如何描述struct platform_driver{probe函数 :设备和驱动匹配的时候操作系统自动调用remove函数:设备和驱动分离的时候操作系统自动调用通用的驱动描述(struct device_driver : 这里面可以记录驱动的名字)id_table : 当前驱动支持平台设备(记录支持的平台设备名字)},};·4、根据总线的类型来确定总线上如何注册设备int platform_device_register(struct platform_device *pdev);5、根据总线的类型来确定总线上如何注册驱动int platform_driver_register(struct platform_driver *pdriver)6、根据总线的类型来确定驱动和设备的匹配原则如果驱动提供了id_table,那就拿设备的名字和id_table中记录的名字匹配如果驱动没有提供id_table,那就拿设备的名字和驱动的名字进行匹配7、一旦设备和驱动匹配后操作系统会调用驱动提供的probe函数在这个函数中一般会做两件事1获取匹配的硬件资源2注册字符设备设备和驱动匹配成功后会出现驱动中有设备设备中有驱动也就是你中有我我中有你驱动中可以包含多个设备设备中只能有一个驱动。3.设备树设备树是一个单独描述设备信息的文件设备树的存在让设备信息不再以代码的形式存在于Linux内核源码中提高了内核的可移植性设备树源文件格式为.dts文件类似于.c文件编译后生成.dtb文件类似于.o文件头文件为.dtsi文件类似于.h文件设备树采用树形结构最外层是根节点节点下可包含属性信息和子节点每个节点代表一个设备属性用于描述设备的具体信息属性分为两类标准属性有固定属性名称如compatible、reg所有平台通用需按规则编写和自定义属性为单个设备单独定义可自定义名称和值。首先要想写设备树先要把板子相关的设备树文件copy出来将其放到一个文件夹中(从exynos4412-fs4412.dts往前找看他包含了哪个头文件vsp 打开它然后以此类推将所有相关的设备树文件都找到了将这些文件放到一个文件夹里)然后根据你需要写的节点看你的节点会包含什么属性在这个文件夹里去搜索例如gpio控制器节点一定会包含“gpio-controller”这个属性使用命令grep gpio-controller * -nR打开后看别人是怎么写的再Documentation/devicetree/bindings里找gpio控置器的说明输入命令grep #xxxx如gpiointerrupt-cells*-nR | grep samsung厂家名这样我们就可以通过查询资料来编写我们自己的设备树节点。4.Linux 中断子系统框架中断控制器GIC )负责对每个中断源进行编号、使能、屏蔽和优先级仲裁并根据配置将中断请求分类为普通中断IRQ或快速中断FIQ最终分发给ARM 核心ARM Core。ARM 核接收到中断信号后会立即保存当前程序的执行上下文现场然后跳转到异常处理向量表执行对应的中断服务例程ISR处理完后再恢复现场并返回被打断的程序继续执行。5.Linux 上半部和下半部机制及特点中断上半部和下半部是将中断处理函数中需要做的事情分成两部分在不同的函数中完成。中断处理函数中完成的事情 是上半部(屏蔽外面的中断)。而另外一个函数中完成的事情是下半部(不屏蔽外面的中断)。上半部由中断产生,就执行中断处理函数(上半部)下半部在合适的时间点执行(一般是在上半部结束的时候开始触发下半部)当硬件中断触发时CPU 会暂停当前正在执行的代码跳转到中断处理函数。在这个阶段系统处于一种非常特殊的状态当前 CPU 上的中断被屏蔽至少同优先级和更低优先级的中断被屏蔽内核不可抢占不能进行进程调度不能睡眠没有进程上下文无法切换这意味着上半部每多执行 1 微秒整个系统就僵死 1 微秒——其他中断无法响应其他进程无法调度。紧急的事情耗费时间不多我们放在上半部耗时的时间长或需要休眠我们放在下半部下半部的实现机制1软中断2taklet基于软中断实现1.在注册中断之前初始化tasklet2.在中断处理函数return之前调用中断下半部的触发函数3.在中断下半部函数中执行不紧急的事件3workqueue1.在注册中断之前初始化workqueue2.在中断处理函数return之前调用中断下半部的触发函数3.在中断下半部函数中执行不紧急的事件4.2进程上下文和中断上下文当内核代表某个进程执行时就说内核处于进程上下文。当 CPU 响应硬件中断如网卡收到数据包、磁盘 IO 完成、定时器到期时内核会跳转到对应的中断处理程序ISR, Interrupt Service Routine执行。此时内核处于中断上下文。中断上下文不能休眠进程上下文可以睡眠6.Linux Input子系统框架input子系统属于输入类子系统作用是感知外部事件输入向上层应用提供标准化的输入接口统一各类输入设备的访问方式降低上层应用开发和驱动开发的难度。1.核心层input core是Linux系统内核中已经预先实现好的核心部分负责input设备和input事件处理程序的匹配工作。2.事件处理层input handler对各类输入外设进行分类不同种类的外设对应不同的事件处理程序常见的默认事件处理程序包括通用事件接口evdev鼠标事件接口游戏手柄摇杆接口joydevLED状态管理接口。3.设备驱动层是开发input子系统驱动时需要编写的部分对应具体的外设硬件需要结合对应外设的基础驱动子系统如按键需结合GPIO子系统、触摸屏需结合I2C子系统、USB键盘/鼠标需结合USB子系统、部分键盘需结合SPI子系统共同实现。核心链表与结构体1.内核中维护两条链表input_handler_list链接所有已注册的事件处理程序每个节点为input_handler结构体包含事件处理名称、回调函数等成员、input_dev_list链接所有已注册的输入外设每个节点为input_dev结构体描述外设的能力属性。2.当新注册一个input_dev时核心层会遍历input_handler_list查找能匹配的事件处理程序当新注册一个input_handler时核心层会遍历input_dev_list查找能匹配的外设设备。3.二者匹配成功后会调用input_handler的connect回调函数生成input_handle结构体该结构体同时关联匹配的input_dev和input_handler将两者通过链表连接起来完成配对。自己编写的按键输入子系统1原理图2设备树节点3编写驱动代码并在板子上测试分配内存并初始化input设备设置本设备支持按键事件具体按键是key2这样就设置他的功能我们handler也记录了自己的功能这样我们dev与handler就会匹配。注册input我们自己写的驱动代码里没有字符设备注册函数为啥在上层应用依旧可以调用orenread...呢那是因为在我们connect函数中设置了。cdev_init时evdev 把一组函数指针注册进去即struct file_operations当你在应用层调用 open(/dev/input/event0, O_RDONLY) 时系统调用会进入内核内核根据这个文件对应的主设备号、次设备号找到对应的 cdev进而找到 evdev_fops最终调用 evdev_open()硬件驱动调用 input_register_device()│▼Input Core 收到设备开始匹配 handler│▼evdev_handler 匹配成功调用 evdev_connect()│▼evdev_connect() 做5件事1. 分配次设备号确定 event几2. 分配内存保存设备状态3. 绑定设备和处理器建立桥梁4. 注册字符设备让内核知道怎么操作它5. 创建设备节点让 udev 生成 /dev/input/eventX│▼用户空间可以 open/read /dev/input/eventX 了一句话概括驱动告诉内核我有个设备 → 内核自动给它配对 evdev 处理器 → evdev 创建字符设备和设备节点 → 用户空间可以通过 /dev/input/eventX 读取输入事件。