嵌入式Linux驱动开发笔记:pinctrl/gpio子系统、按键驱动、中断与等待队列

发布时间:2026/10/15 3:56:17
嵌入式Linux驱动开发笔记:pinctrl/gpio子系统、按键驱动、中断与等待队列
1. 引言本文整理自嵌入式Linux驱动开发课程第15~18章涵盖 pinctrl/gpio 子系统、按键驱动、中断处理以及等待队列阻塞IO的核心知识点适合驱动开发入门与复习。2. pinctrl子系统和gpio子系统驱动2.1 Linux驱动分层思想Linux驱动设计三大思想面向对象万物皆对象抽象到具体的过程。设备与驱动分离设备树dtb存放硬件资源驱动drv.c通过设备树获取硬件信息实现驱动可移植。驱动分层思想Linux内核为相似事物定义基类具体事物继承基类。如GPIO子系统、I2C子系统、SPI子系统等。分层示意A类设备核心层 / | \ A设备实例1 A设备实例2 A设备实例3GPIO与I2C的分层管理pinctrl子系统管理引脚复用IOMUX和电气特性配置。GPIO子系统管理GPIO的输入/输出、高低电平控制。若引脚配置为GPIO功能则由GPIO子系统管理若配置为I2C功能则由I2C子系统管理。2.2 pinctrl子系统两个概念Pin controller devices对应IOMUX复用引脚包含引脚配置。Pinctrl client devices使用pinctrl系统的设备。设备树语法示例/* 节点标签节点分组可被引用 */ pinctrl_led: ledgrp { fsl,pins MX6UL_PAD_GPIO1_IO03_GPIO1_IO03 0x10B0 /* LED0电气特性配置 */ ; }; gpio_leds { #address-cells 1; #size-cells 1; compatible pute-gpio-leds; pinctrl-names default; // 引脚状态名称列表 pinctrl-0 pinctrl_led; // 引用pinctrl_led节点 led-gpio gpio1 3 GPIO_ACTIVE_LOW; // 自定义属性低电平有效 status okay; };关键点fsl,pins属性写法由FSL规定宏定义在arch/arm/boot/dts/imx6ul-pinfunc.h。pinctrl-names定义引脚状态名称default、sleep、idle等。pinctrl-0引用具体的pinctrl配置节点。2.3 gpio子系统设备树中引用GPIOputeled { #address-cells 1; #size-cells 1; compatible pute,pute_led; pinctrl-names default; pinctrl-0 pinctrl_led; gpio-led gpio1 3 GPIO_ACTIVE_LOW; // 引用gpio1 pin3低电平有效 status okay; };GPIO子系统常用函数函数名功能of_get_named_gpio从设备树节点中解析并获取指定GPIO硬件编号gpio_request申请GPIO资源gpio_free释放GPIO资源devm_gpio_request申请GPIO资源生命周期与device绑定自动释放gpio_direction_output设置为输出模式gpio_direction_input设置为输入模式gpio_get_value读取输入引脚电平gpio_set_value设置输出引脚电平gpio_to_irq通过GPIO编号获得中断号pinctrl与gpio的关系pinctrl负责引脚复用 电气属性GPIO子系统负责输入 输出 高低电平控制先pinctrl配置成GPIO再用GPIO子系统控制设备树只告诉用哪个脚驱动才真正控制GPIO2.4 代码调整步骤删除原来of_iomap的 pdata、pdir。使用of_get_named_gpio获得GPIO编号。使用gpio_request申请GPIO。使用gpio_direction_output/gpio_direction_input设置输入输出。输出时设置默认电平gpio_set_value输入时通过gpio_get_value获得引脚状态。最后gpio_free释放GPIO。3. 按键驱动非阻塞IO3.1 设备树配置putekey { #address-cells 1; #size-cells 1; compatible pute-key; status okay; pinctrl-names default; pinctrl-0 pinctrl_putekey; key-gpio gpio1 18 GPIO_ACTIVE_LOW; interrupt-parent gpio1; interrupts 18 IRQ_TYPE_EDGE_RISING; };pinctrl节点pinctrl_putekey: keynode { fsl,pins MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 ; };注意需要把设备树中原来的key节点屏蔽掉否则GPIO会申请失败。3.2 按键驱动实现方式轮询非阻塞IO方式在read中不断查询GPIO状态。阻塞中断唤醒方式等待队列 中断推荐。3.3 代码调整删除原来of_iomap的 pdata、pdir。使用of_get_named_gpio获得GPIO编号。使用gpio_request申请GPIO。使用gpio_direction_output/gpio_direction_input设置输入输出。输出时设置默认电平输入时通过gpio_get_value获得引脚状态。最后gpio_free释放GPIO。4. 中断4.1 中断概念中断是指CPU在执行程序过程中出现某些突发事件急待处理CPU暂停当前程序转去处理突发事件处理完毕后再返回原程序被中断的位置继续执行。内部中断来源于CPU内部。外部中断来源于CPU外部。查看中断信息cat/proc/interrupts4.2 设备树适配putekey { #address-cells 1; #size-cells 1; compatible pute-key; status okay; pinctrl-names default; pinctrl-0 pinctrl_key; key-gpio gpio1 18 GPIO_ACTIVE_LOW; /* KEY0 */ interrupt-parent gpio1; interrupts 18 IRQ_TYPE_EDGE_RISING; };gpio1控制器在dtsi中的定义gpio1: gpio0209c000 { compatible fsl,imx6ul-gpio, fsl,imx35-gpio; reg 0x0209c000 0x4000; interrupts GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH, GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH; gpio-controller; #gpio-cells 2; interrupt-controller; #interrupt-cells 2; };触发方式IRQ_TYPE_EDGE_RISING上升沿触发IRQ_TYPE_EDGE_FALLING下降沿触发IRQ_TYPE_EDGE_BOTH双边沿触发4.3 中断相关函数函数名功能of_irq_get/gpio_to_irq从设备树或GPIO获得中断号request_irq申请中断注册中断处理函数free_irq释放中断enable_irq/disable_irq使能/禁用中断中断处理函数返回值enumirqreturn{IRQ_NONE,// 不是本驱动要处理的中断IRQ_HANDLED,// 中断正常处理IRQ_WAKE_THREAD,// 唤醒threaded irq线程};4.4 操作步骤适配设备树加入interrupt-parent和interrupts属性。通过of_irq_get/gpio_to_irq获得中断号。调用request_irq()申请中断填入中断处理函数。编写中断处理函数注意返回值。修改app验证中断是否触发。改进为阻塞方式读取中断 等待队列。查看中断cat/proc/interrupts# 46: 0 gpio-mxc 18 Edge key_interrupt4.5 中断上下半部上半部Top Half也称为中断顶半部用于完成尽量少的比较紧急的功能。通常被设置为不可被新中断打断。只能做清中断、读寄存器、唤醒等待队列、调度底半部。下半部Bottom Half用来完成中断事件绝大多数任务可以被新的中断打断。相对不是非常紧急且比较耗时。实现方式机制运行环境能否休眠调度时机并行性用途顶半部中断上下文否立即执行独占CPU紧急硬件操作软中断中断上下文否中断返回前SMP多核并行网络、块设备、定时器tasklet中断上下文否中断返回前同类型不能并行驱动最常用轻量底半部workqueue进程上下文能内核线程调度完全并行能休眠、耗时操作tasklet使用DECLARE_TASKLET(my_tasklet,tasklet_func,data);tasklet_schedule(my_tasklet);5. 等待队列阻塞IO5.1 等待队列概念等待队列是Linux内核中用于实现阻塞IO的重要机制。当进程需要等待某个条件满足时可以将其放入等待队列中休眠条件满足后再唤醒。5.2 等待队列相关函数函数/宏功能init_waitqueue_head初始化等待队列头wait_event_interruptible可中断地等待条件满足wake_up_interruptible唤醒等待队列中的进程DECLARE_WAITQUEUE声明一个等待队列项5.3 阻塞IO实现流程在驱动中定义等待队列头。在read函数中调用wait_event_interruptible等待按键事件。在中断处理函数上半部中唤醒等待队列。条件满足后read函数返回按键数据。6. 总结pinctrl子系统负责引脚复用和电气属性配置GPIO子系统负责GPIO的输入输出控制按键驱动可通过轮询或中断方式实现中断处理分为上半部和下半部下半部可用tasklet或workqueue实现等待队列实现阻塞IO配合中断实现高效的按键读取。