网站栏目页模板,安徽教育云网站建设,公司门户网站什么意思,网站怎么申请支付宝Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 中断作为驱动开发中很重要的一个概念#xff0c;在实际的项目实践中经常用到。本节的主要内容包括中断简介、硬件原理分析、驱动程序开发及运行测试。其中驱动程…Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 中断作为驱动开发中很重要的一个概念在实际的项目实践中经常用到。本节的主要内容包括中断简介、硬件原理分析、驱动程序开发及运行测试。其中驱动程序的开发是本节的重点内容。 本节内容较多分两次更新。 本笔记的思维导图如下 一、Linux中断简介
1.中断API函数 中断号很好理解不赘述了。 request_irq函数申请中断不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数会激活(使能)中断所以不需要我们手动去使能中断 request_irq 函数原型 如下
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
irq要申请中断的中断号。 handler中断处理函数当中断发生以后就会执行此中断处理函数。 flags中断标志 name中断名字设置以后可以在/proc/interrupts 文件中看到对应的中断名字。 dev 如果将 flags 设置为 IRQF_SHARED 的话 dev 用来区分不同的中断一般情况下将 dev 设置为设备结构体 dev 会传递给中断处理函数 irq_handler_t 的第二个参数。 返回值 0 中断申请成功其他负值 中断申请失败如果返回-EBUSY 的话表示中断已经 被申请了。free_irq 函数中断释放
如果中断不是共享的那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq 函数原型如下所示
void free_irq(unsigned int irq,
void *dev)
irq 要释放的中断。 dev如果中断设置为共享(IRQF_SHARED)的话此参数用来区分具体的中断。共享中断 只有在释放最后中断处理函数的时候才会被禁止掉。 返回值无。
中断处理函数
中断处理函数格式如下所示
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针也就 是个通用指针需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备 dev 也可以指向设备数据结构。中断处理函数的返回值为 irqreturn_t 类型 irqreturn_t 类型定义 如下所示
10 enum irqreturn {
11 IRQ_NONE (0 0),
12 IRQ_HANDLED (1 0),
13 IRQ_WAKE_THREAD (1 1),
14 };
15
16 typedef enum irqreturn irqreturn_t;
一般中断服务函数返回值使用如下形式
return IRQ_RETVAL(IRQ_HANDLED)
中断使能与禁止
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
需要保证不会产生新的中断并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下可以使用另外一个中断禁止函数
void disable_irq_nosync(unsigned int irq)
关闭当前处理器的整个中断系统
local_irq_enable()
local_irq_disable()
要考虑到别的任务的感受此时就要用到下面两个函数
local_irq_save(flags)
local_irq_restore(flags)
这两个函数是一对 local_irq_save 函数用于禁止中断并且将中断状态保存在 flags 中。 local_irq_restore 用于恢复中断将中断到 flags 状态。 2.上半部和下半部
上半部上半部就是中断处理函数那些处理过程比较快不会占用很长时间的处理就可 以放在上半部完成。 下半部如果中断处理过程比较耗时那么就将这些比较耗时的代码提出来交给下半部 去执行这样中断处理函数就会快进快出。 需要放到下半部的 1不希望被打断 2)时间敏感 3)硬件相关 Linux 内核使用结构体 softirq_action 表示软中断内容如下
433 struct softirq_action
434 {
435 void (*action)(struct softirq_action *);
436 };
softirq_action 结构体中的 action 成员变量就是软中断的服务函数 要使用软中断必须先使用 open_softirq 函数注册对应的软中断处理函数 open_softirq 函数原型如下
void open_softirq(int nr, void (*action)(struct softirq_action *))
nr要开启的软中断在示例代码 51.1.2.3 中选择一个。 action软中断对应的处理函数。 返回值 没有返回值。 注册好软中断以后需要通过 raise_softirq 函数触发 raise_softirq 函数原型如下
void raise_softirq(unsigned int nr)
nr要触发的软中断 无返回值 Linux 内核使用 softirq_init 函数初始化软中断。
634 void __init softirq_init(void)
635 {
636 int cpu;
637
638 for_each_possible_cpu(cpu) {
639 per_cpu(tasklet_vec, cpu).tail
640 per_cpu(tasklet_vec, cpu).head;
641 per_cpu(tasklet_hi_vec, cpu).tail
642 per_cpu(tasklet_hi_vec, cpu).head;
643 }
644
645 open_softirq(TASKLET_SOFTIRQ, tasklet_action);
646 open_softirq(HI_SOFTIRQ, tasklet_hi_action);
647 }
softirq_init 函数默认会打开 TASKLET_SOFTIRQ 和HI_SOFTIRQ。
tasklet 是利用软中断来实现的另外一种下半部机制在软中断和 tasklet 之间建议大家使 用 tasklet。 Linux 内核使用结构体
484 struct tasklet_struct
485 {
486 struct tasklet_struct *next; /* 下一个 tasklet */
487 unsigned long state; /* tasklet 状态 */
488 atomic_t count; /* 计数器记录对 tasklet 的引用数 */
489 void (*func)(unsigned long); /* tasklet 执行的函数 */
490 unsigned long data; /* 函数 func 的参数 */
491 };
tasklet_init 函数初始化 tasklettaskled_init 函数原型如下
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
t要初始化的 tasklet func tasklet 的处理函数。 data 要传递给 func 函数的参数 返回值 没有返回值。 使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化
DECLARE_TASKLET(name, func, data) 其中 name 为要定义的 tasklet 名字这个名字就是一个 tasklet_struct 类型的时候变量 func就是 tasklet 的处理函数 data 是传递给 func 函数的参数。 在上半部也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运 行 tasklet_schedule 函数原型如下
void tasklet_schedule(struct tasklet_struct *t)
t要调度的 tasklet也就是 DECLARE_TASKLET 宏里面的 name。 返回值 没有返回值。
工作队列是另外一种下半部执行方式 要推后的工作可以睡眠那么就可以选择工作队列否则的话就只能选择软 中断或 tasklet。 work_struct 结构体表示一个工作
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
工作队列使用 workqueue_struct 结构体表示。
worker 结构体表示工作者线程。
每个 worker 都有一个工作队列。 只需要定义工作(work_struct)即可。
直接定义一个 work_struct 结构体 变量即可然后使用 INIT_WORK 宏来初始化工作
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作 _func 是工作对应的处理函数。 DECLARE_WORK 宏一次性完成工作的创建和初始化
#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct) f 表示工作对应的处理函数。 工作的调度函数为 schedule_work函数原型如下所示
bool schedule_work(struct work_struct *work)
work 要调度的工作。 返回值 0 成功其他值 失败。
3.设备树中断信息节点
intc 节点就是I.MX6ULL 的中断控制器节点。
对于 ARM 处理的 GIC 来说一共有 3 个 cells这三个 cells 的含义如下 第一个 cells中断类型 0 表示 SPI 中断 1 表示 PPI 中断。 第二个 cells中断号对于 SPI 中断来说中断号的范围为 0~987对于 PPI 中断来说中断 号的范围为 0~15。 第三个 cells标志 bit[3:0]表示中断触发类型为 1 的时候表示上升沿触发为 2 的时候 表示下降沿触发为 4 的时候表示高电平触发为 8 的时候表示低电平触发。 bit[15:8]为 PPI 中 断的 CPU 掩码。 interrupt-controller 节点为空表示当前节点是中断控制器。 gpio 节点也可以作为中断控制器。 简单总结一下与中断有关的设备树属性信息 ①、 #interrupt-cells指定中断源的信息 cells 个数。 ②、 interrupt-controller表示当前节点为中断控制器。 ③、 interrupts指定中断号触发方式等。 ④、 interrupt-parent指定父中断也就是中断控制器。
4.获取中断号
irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
dev 设备节点。
index索引号 interrupts 属性可能包含多条中断信息通过 index 指定要获取的信息。 返回值中断号。 如果使用 GPIO 的话可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号函数原型如 下
int gpio_to_irq(unsigned int gpio)
gpio 要获取的 GPIO 编号。 返回值 GPIO 对应的中断号。
二、硬件原理图 按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的 KEY0接了一个 10K 的上拉电阻因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平当 KEY0按下以后 UART1_CTS 就是低电平。 以下内容将在后续的笔记中整理更新
三、驱动开发
1.修改设备树文件
2.按键中断驱动程序编写
3.编写测试APP
四、运行测试
1.编译驱动程序和测试APP
2.运行测试
五、总结 本文为参考正点原子开发板配套教程整理而得仅用于学习交流使用不得用于商业用途。