Linux驱动开发中断
Posted XXX_UUU_XXX
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发中断相关的知识,希望对你有一定的参考价值。
中断函数
中断号
中断号用来区分不同的中断,Linux内核使用一个int变量表示中断号。中断号也称为中断线。
中断API函数
request_irq
Linux内核使用中断需要申请,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设备结构体传递给中断处理函数的第二个参数。
- 返回值:0,中断申请成功;负值,中断申请失败,-EBUSY表示中断已经被申请。
标志 | 描述 |
IRQF_SHARED | 多个设备共享一个中断号,共享的所有中断都必须指定IRQF_SHARED标志 |
IRQF_ONESHOT | 中断执行一次就结束 |
IRQF_TRIGGER_NONE | 无触发 |
IRQF_TRIGGER_RISING | 上升沿触发 |
IRQF_TRIGGER_FALLING | 下降沿触发 |
IRQF_TRIGGER_HIGH | 高电平触发 |
IRQF_TRIGGER_LOW | 低电平触发 |
free_irq
中断使用完后释放相应的中断,如果中断不是共享中断,free_irq会删除中断处理函数并禁止中断。
void free_irq(unsigned int irq,
void *dev);
- irq:释放的中断号。
- dev:区分不同的共享中断,共享中断只有在释放最后中断处理函数时才会被禁止。
- 返回值:无。
中断处理函数
第一个参数:中断号。
第二个参数:指向void的指针,与request_irq函数的dev参数保持一直,用于区分共享中断的不同设备。
返回值:irqreturn_t类型,共有三种返回值,一般使用IRQ_HANDLED。
/* 中断处理函数 */
irqreturn_t(*irq_handler_t)(int, void *)
/* 返回值irqreturn_t类型定义 */
enum irqreturn
IRQ_NONE = (0 << 0);
IRQ_HANDLED = (1 << 0);
IRQ_WAKE_THREAD = (1 << 1);
;
typedef enum irqreturn irqreturn_t
中断使能/禁止函数
disable_irq函数需要等到当前正在执行的中断处理函数执行完才返回,需要保证不会产生新的中断,并且所有已经开始执行的中断程序已经全部退出。
/* 中断使能 */
void enable_irq(unsigned int irq);
/* 中断禁止 */
void disable_irq(unsigned int irq);
- irq:中断号。
disable_irq_nosync函数调用后立即返回,不会等待当前中断处理程序执行完毕。
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);
中断上半部和下半部
上半部:中断处理函数,对时间敏感、处理速度较快、不会占用很长时间的操作放在上半部进行。
下半部:中断处理过程中比较耗时的程序放在下半部进行,Linux内核提供多种下半部机制。
Linux内核将中断分为上半部和下半部的主要目的:实现中断处理函数的快进快出。
上半部和下半部举例:上半部将数据拷贝到内存,下半部对数据进行处理。
需要根据实际情况判断哪些代码属于上半部还是下半部,可参考如下情况进行判断:
- 要处理的内容不希望被其他中断打断,放在上半部。
- 要处理的任务对时间敏感,放在上半部。
- 要处理的任务和硬件有关,放在上半部。
- 其余的任务,放在下半部。
中断下半部常用处理方法
软中断(不推荐使用)
Linux内核定义softirq_action结构体表示软中断。
struct softirq_action
void (*action)(struct softirq_action *);
?;
在kernel/softirq.c中定义了10个软中断。softirq_action结构体中的action成员变量是软中断的服务函数。
static struct softirq_action softirq_vec[NR_SOFTRQS];
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
;
注册软中断处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *));
- nr:要开起的软中断,从10个软中断中选择。
- action:软中断对应的处理函数。
- 返回值:无。
触发中断
void raise_softirq(unsigned int nr);
- nr:要开起的软中断,从10个软中断中选择。
- 返回值:无。
软中断必须哎编译的时候静态注册。
tasklet(建议使用)
Linux内核定义tasklet_struct结构体表示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初始化。
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的名字。
- func:tasklet的处理函数。
- data:传递给func的参数。
tasklet也要用到上半部,只是上半部的中断处理函数重点是调度tasklet_schedule使tasklet在合适的时间运行。
void tasklet_schedule(struct tasklet_struct *t);
- t:要调用的tasklet,也是宏DECLARE_TASKLET里面的name。
- 返回值:无。
tasklet例程
// 定义tasklet
struct tasklet_struct test_tasklet;
// tasklet处理函数
void test_tasklet_func(unsigned long data)
// tasklet的具体处理代码
// 中断处理函数
irqreturn_t test_handler(int irq, void *dev_id)
//调度tasklet
tasklet_schedule(&test_tasklet);
// 驱动入口函数
static int __init xxx_init(void)
// 初始化tasklet
tasklet_init(&test_tasklet, test_tasklet_func, data);
// 注册中断处理函数
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
工作队列(需要睡眠的工作中使用)
工作队列在进程上下文执行,将要推后的工作交给一个内核线程去执行,允许睡眠或重新调度。
Linux内核定义work_struct结构体表示一个工作。
struct work_struct
atomic_long_t data;
struct list_head entry;
work_func_t func; // 工作队列处理函数
;
许多工作组成工作队列,定义workqueue_struct结构体表示工作队列。
struct workqueue_struct
struct list_head pwqs;
struct list_head list;
struct mutex mutex;
int work_color;
int flush_color;
atomic_t nr_pwqs_to_flush;
struct wq_flusher *first_flusher;
struct list_head flusher_queue;
struct list_head flusher_overflow;
struct list_head maydays;
struct worker *rescuer;
int nr_drainers;
int saved_max_active;
struct workqueue_attrs *unbound_attrs;
struct pool_workqueue *dfl_pwq;
char name[WQ_NAME_LEN];
struct rcu_head rcu;
unsigned int flags ____cacheline_aligned;
struct pool_workqueue __percpu *cpu_pwqs;
struct pool_workqueue __rcu *numa_pwq_tbl[];
;
Linux内核使用工作者线程worker thread处理工作队列中的各个工作,Linux内核定义worker结构体表示工作者线程。
struct worker
union
struct list_head entry;
struct hlist_node hentry;
;
struct work_struct *current_work;
work_func_t current_func;
struct pool_workqueue *current_pwq;
bool desc_valid;
struct list_head scheduled;
struct task_struct *task;
struct worker_pool *pool;
struct list_head node;
unsigned long last_active;
unsigned int flags;
int id;
char desc[WORKER_DESC_LEN];
struct workqueue_struct *rescue_wq;
;
Linux驱动开发,只需要定义工作work_struct即可。定义work_struct结构体变量,然后INIT_WORK初始化。
#define INIT_WORK(_work, _func)
- _work:要初始化的工作。
- _func:工作处理函数 。
也可以使用宏DECLARE_WORK一次性完成工作的创建和初始化。
#define DECLARE_WORK(n, f)
- n:定义的工作。
- f:工作处理函数。
工作的调度函数schedule_work
bool schedule_work(struct work_struct *work);
- work:要调度的工作。
- 返回值:0,成功;其他值,失败。
工作队列例程
// 定义工作
struct work_struct test_work;
// 工作处理函数
void test_work_func_t(struct work_struct *work)
// 中断处理函数
irqreturn_t test_handler(int irq, void *dev_id)
// 调度工作work
schedule_work(&test_work);
// 驱动入口函数
static int __init xxx_init(void)
// 初始化工作
INIT_WORK(&test_work, test_work_func_t);
// 注册中断处理函数
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
设备树设置中断信息
- interrupt-controller,表示当前节点为中断控制器。
- #interrupt-cells,指定interrupts属性的cells大小。
- interrupts,指定中断号、触发方式。
- interrupt-parent,指定父中断,即中断控制器。
Linux内核通过读取设备树中的中断属性信息配置中断。imx6ull.dtsi文件中intc节点表示如下:
intc: interrupt-controller@00a01000
compatible = "arm, cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
;
#interrupt-cells描述interrupt属性的cells大小,对于ARM处理器的GIC中断控制器,共有3个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节点也可作为中断控制器,imx6ull.dtsi文件中gpio5节点表示如下:
gpio5: gpio@0x0ac000
compatible = "fsl, imx6ul-gpio", "fsl, imx35-gpio";
reg = <0x020ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
;
interrupts描述中断源信息,中断类型都是SPI,触发电平是高电平,查看芯片手册gpio5的中断号一个是74,对应GPIO5_IO00~GPIO5_IO15低16位IO,一个是75,对应GPIO5_IO16~GPIO5_IO31高16位IO。
Linux内核include/linux/irq.h文件中定义了中断号(线)的状态:
/*
* IRQ line status.
*
* Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
*
* IRQ_TYPE_NONE - default, unspecified type
* IRQ_TYPE_EDGE_RISING - rising edge triggered
* IRQ_TYPE_EDGE_FALLING - falling edge triggered
* IRQ_TYPE_EDGE_BOTH - rising and falling edge triggered
* IRQ_TYPE_LEVEL_HIGH - high level triggered
* IRQ_TYPE_LEVEL_LOW - low level triggered
* IRQ_TYPE_LEVEL_MASK - Mask to filter out the level bits
* IRQ_TYPE_SENSE_MASK - Mask for all the above bits
* IRQ_TYPE_DEFAULT - For use by some PICs to ask irq_set_type
* to setup the HW to a sane default (used
* by irqdomain map() callbacks to synchronize
* the HW state and SW flags for a newly
* allocated descriptor).
*
* IRQ_TYPE_PROBE - Special flag for probing in progress
*
* Bits which can be modified via irq_set/clear/modify_status_flags()
* IRQ_LEVEL - Interrupt is level type. Will be also
* updated in the code when the above trigger
* bits are modified via irq_set_irq_type()
* IRQ_PER_CPU - Mark an interrupt PER_CPU. Will protect
* it from affinity setting
* IRQ_NOPROBE - Interrupt cannot be probed by autoprobing
* IRQ_NOREQUEST - Interrupt cannot be requested via
* request_irq()
* IRQ_NOTHREAD - Interrupt cannot be threaded
* IRQ_NOAUTOEN - Interrupt is not automatically enabled in
* request/setup_irq()
* IRQ_NO_BALANCING - Interrupt cannot be balanced (affinity set)
* IRQ_MOVE_PCNTXT - Interrupt can be migrated from process context
* IRQ_NESTED_TRHEAD - Interrupt nests into another thread
* IRQ_PER_CPU_DEVID - Dev_id is a per-cpu variable
* IRQ_IS_POLLED - Always polled by another interrupt. Exclude
* it from the spurious interrupt detection
* mechanism and from core side polling.
*/
enum
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
IRQ_TYPE_SENSE_MASK = 0x0000000f,
IRQ_TYPE_DEFAULT = IRQ_TYPE_SENSE_MASK,
IRQ_TYPE_PROBE = 0x00000010,
IRQ_LEVEL = (1 << 8),
IRQ_PER_CPU = (1 << 9),
IRQ_NOPROBE = (1 << 10),
IRQ_NOREQUEST = (1 << 11),
IRQ_NOAUTOEN = (1 << 12),
IRQ_NO_BALANCING = (1 << 13),
IRQ_MOVE_PCNTXT = (1 << 14),
IRQ_NESTED_THREAD = (1 << 15),
IRQ_NOTHREAD = (1 << 16),
IRQ_PER_CPU_DEVID = (1 << 17),
IRQ_IS_POLLED = (1 << 18),
;
具体开发板中fxls8471磁力计的中断属性信息示例如下:
fxls8471@1e
compatible = "fsl, fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
;
具体开发板的设备树一般只需要添加两行:
- interrupt-parent属性设置gpio5为中断控制器 。
- interrupts设置中断信息,0,表示GPIO5_IO00,8表示低电平触发。
获取中断号
irq_of_parse_and_map
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index);
- dev:设备节点。
- index:interrupts属性可能包含多条中断信息,index指定要获取的信息。
- 返回值:中断号。
gpio_to_irq
获取gpio对应的中断号
inT gpio_to_irq(unsigned int gpio);
- gpio:GPIO编号。
- 返回值:GPIO对应的中断号。
以上是关于Linux驱动开发中断的主要内容,如果未能解决你的问题,请参考以下文章
Linux——Linux驱动之GPIO中断的应用实战(下)(中断下文之工作队列的使用,处理非常复杂且耗时的操作)
Linux驱动实践:中断处理中的工作队列 workqueue 是什么鬼?