- 申请中断函数。该函数会引起线程的睡眠,调用之后,会使能中断。(但未触发)
int request_irq( unsigned int irq, //要申请的中断号
irq_handler_t handler, //中断处理函数
unsigned long flags, //中断触发标志(include/linux/interrupt.h),
const char *name, //中断名字
void *dev) // flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数
返回值 : 0中断申请成功 <0 申请失败
- 释放中断
void free_irq( unsigned int irq, //中断号
void *dev) //
- 中断处理函数
irq_handler_t
原型, 第二个参数[in]是一个指向 void 的指针,也就是个通用指针,需要与request_irq
申请中断函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构
irqreturn_t (*irq_handler_t) (int, void *) //第一个[in]是 要中断处理函数相应的中断号
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
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) //禁止中断,并且将中断状态保存在 flags 中
local_irq_restore(flags) //恢复中断,将中断到 flags 状态
-
上半部的使用建议
- 要处理的内容不希望被其他中断打断,那么可以放到上半部
- 要处理的任务对时间敏感,可以放到上半部
- 要处理的任务与硬件有关,可以放到上半部
-
软中断
- 结构体定义 include/linux/interrupt.h
struct softirq_action { void (*action)(struct softirq_action *) };
- 使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,
软中断必须在编译的时候静态注册
void open_softirq( int nr, // nr:要开启的软中断 void (*action)(struct softirq_action *)) // action:软中断对应的处理函数
nr的枚举选项
enum { HI_SOFTIRQ=0, /* 高优先级软中断 */ TIMER_SOFTIRQ, /* 定时器软中断 */ NET_TX_SOFTIRQ, /* 网络数据发送软中断 */ NET_RX_SOFTIRQ, /* 网络数据接收软中断 */ BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* tasklet 软中断 */ SCHED_SOFTIRQ, /* 调度软中断 */ HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */ RCU_SOFTIRQ, /* RCU 软中断 */ NR_SOFTIRQS };
-
tasklet(更建议)
- 数据结构
struct tasklet_struct { struct tasklet_struct *next; /* 下一个 tasklet */ unsigned long state; /* tasklet 状态 */ atomic_t count; /* 计数器,记录对 tasklet 的引用数 */ void (*func)(unsigned long); // tasklet 执行的 回调函数 unsigned long data; /* 函数 func 的参数 */ };
使用的方法:
-
自行定义一个
tasklet
变量 -
使用
tasklet_init()
函数初始化tasklet
变量void tasklet_init( struct tasklet_struct *t, //要初始化的tasklet void (*func)(unsigned long), //回调函数 unsigned long data); // func 的传参
-
也可以使用宏
DECLARE_TASKLET(name, func, data)
来完成tasklet的定义和初始化,该宏在 include/linux/interrupt.h -
开启tasklet的调度
void tasklet_schedule(struct tasklet_struct *t)
逻辑流程:
/* 定义 taselet */ struct tasklet_struct testtasklet; /* tasklet 处理函数 */ void testtasklet_func(unsigned long data) { /* tasklet 具体处理内容 */ } /* 中断处理函数*(即中断上半部) */ irqreturn_t test_handler(int irq, void *dev_id) { ...... /* !!!!!!!!!!!!!!!!!!调度 tasklet */ tasklet_schedule(&testtasklet); ...... } /* 驱动入口函数 */ static int __init xxxx_init(void) { ...... /* 初始化 tasklet */ tasklet_init(&testtasklet, testtasklet_func, data); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... }
-
工作队列 工作队列是在进程上下文执行,将要退后的工作交给一个内核线程去执行。因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
- 表示一个工作的结构体
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; /* 工作队列处理函数 */ };
- 表示一个工作队列
struct workqueue_struct { };
- 内核使用工作者线程(worker thread)来处理工作队列中的各个工作, Linux 内核使用worker 结构体表示工作者线程
实际应用中,我们只需定义工作(work_struct),关于工作队列和工作者线程,基本上不用管。创建工作比较简单,使用
struct worker { };
#define INIT_WORK(_work, _func)
定义 ,_work 表示要初始化的工作, _func 是工作对应的处理函数。#define DECLARE_WORK(n, f)
一次性完成工作的创建和初始化,n 表示定义的工作(work_struct), f 表示工作对应的处理函数。- 开启调度才能运行
bool schedule_work(struct work_struct *work)
- 逻辑流程
/* 定义工作(work) */ struct work_struct testwork; void testwork_func_t(struct work_struct *work); { /* work 具体处理内容 */ } /* 中断处理函数 */ irqreturn_t test_handler(int irq, void *dev_id) { ...... /* 调度 work */ schedule_work(&testwork); ...... } /* 驱动入口函数 */ static int __init xxxx_init(void) { ...... /* 初始化 work */ INIT_WORK(&testwork, testwork_func_t); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... }
- 表示一个工作的结构体
- 与中断有关的设备树属性信息
- #interrupt-cells, 指定中断源的信息 cells 个数
- interrupt-controller, 空节点,表示当前节点为中断控制器。
- interrupts, 指定中断号,触发方式等
- interrupt-parent, 指定父中断,也就是中断控制器。
- API
- 获取中断号
unsigned int irq_of_parse_and_map( struct device_node *dev, // 设备节点 int index); // 索引号,interrupts属性含多条中断信息,通过index指定获取信息 返回中断号
- 如果是gpio的话,可以使用 gpio_to_irq 函数获取 gpio 对应的中断号
int gpio_to_irq(unsigned int gpio) //gpio 获取GPIO对应的编号 返回值:GPIO对应的中断号
- 获取中断号
- 按键触发中断
- 按键 对应原理图 GPIO1_IO18
- 在设备树 .dts 文件中,添加该引脚的宏 imx6ull-pinfunc-snvs.h ==> #define MX6UL_PAD_KEY__GPIO1_IO18 0x008C 0x0318 0x0000 0x5 0x0
- free_irq时发生错误:Trying to free already-free IRQ
上两个函数,在使用的时候,最后一个参数必须保持一致,问题解决
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) void free_irq(unsigned int irq, void *dev)