一:Linux中断简介
Linux内核提供了完善的中断框架,无需配置寄存器,使能IRQ等,只需要申请中断,然后注册中断处理函数即可,每个中断都有一个中断号,通过中断号即可区分不同的中断,在Linux内核中使用一个int变量表示中断号。
二:中断API
1、申请中断request_irq
int request_irq(unsigned int irq, //中断号
irq_handler_t handler, //中断处理函数
unsigned long flags, //中断标志
const char *name, //中断名字
void *dev) //一般设置为设备结构体,传递给中断处理函数irq_handler_t的第二个参数
中断标志:
2、释放中断free_irq
void free_irq(unsigned int irq, void *dev_id)
3、中断处理函数
irqreturn_t (*irq_handler_t)(int, void *);
4、中断使能与禁止
(1) disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理函数已经全部退出。 disable_irq_nosync函数调用以后立即返回,不会等待当前中断处理程序执行完毕。
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)
(2)全局中断使能与禁止
local_irq_enable();
local_irq_disable();
local_irq_save(flags)
local_irq_restore(flags)
5、获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
该函数可以从设备树文件“interupts”属性中提取到对应的设备号。
dev:设备节点
index:索引号,指定要获取的信息。
6、获取GPIO对应的中断号
int gpio_to_irq(unsigned int gpio)
gpio:要获取的gpio编号。
返回值为gpio对应的中断号。
三:中断上半部与下半部
1、中断处理过程分为两部:上半部与下半部
上半部:中断处理函数,处理过程快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提取出来交给下半部去执行,这样中断处理函数就会快进快出。
2、中断设计思路:
(1)如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
(2)如果要处理的任务对时间敏感,可以放到上半部。
(3)如果要处理的任务与硬件有关,可以放到上半部。
(4)其他任务优先考虑下半部。
3、下半部机制
(1)软中断
Linux内核使用结构体softirq_action表示软中断:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
在Linux内核kernel/softirq.c文件中定义了10个软中断:
struct softirq_action softirq_vec[NR_SOFTIRQS]
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
要使用软中断,必须先对对应的中断处理函数进行注册,因为所有的CPU都可以访问软中断服务函数,虽然不同CPU的中断触发机制和控制机制不同,但是访问的都是softirq_vec中定义的action函数。
软中断注册函数:
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
软中断注册之后需要进行触发:
void raise_softirq(unsigned int nr)
软中断必须在编译时静态注册,Linux内核使用softirq_init函数对软中断进行了初始化:
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
(2)tasklet
taskled是利用软中断实现的另一种下半部机制。
tasklet结构体定义:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
初始化tasklet_init:
static inline void tasklet_init(struct tasklet_struct *tasklet,
void (*func)(unsigned long),
unsigned long data)
宏定义:name表示tastlet,func表示处理函数,data表示func函数的参数
DECLARE_TASKLET(name, func, data)
在上半部,也就是中断处理函数中调动tasklet_schedule函数就能使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);
......
}
(3)工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果推后的工作可以睡眠就可以选择工作队列,否则只能选择软中断或tasklet。
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内核使用工作者线程来处理工作队列中的各个工作,Linux内核使用worker结构体表示工作者线程:
struct worker {
/* on idle list while idle, on busy hash table while busy */
union {
struct list_head entry; /* L: while idle */
struct hlist_node hentry; /* L: while busy */
};
struct work_struct *current_work; /* L: work being processed */
work_func_t current_func; /* L: current_work's fn */
struct pool_workqueue *current_pwq; /* L: current_work's pwq */
bool desc_valid; /* ->desc is valid */
struct list_head scheduled; /* L: scheduled works */
/* 64 bytes boundary on 64bit, 32 on 32bit */
struct task_struct *task; /* I: worker task */
struct worker_pool *pool; /* I: the associated pool */
/* L: for rescuers */
struct list_head node; /* A: anchored at pool->workers */
/* A: runs through worker->node */
unsigned long last_active; /* L: last active timestamp */
unsigned int flags; /* X: flags */
int id; /* I: worker id */
/*
* Opaque string set with work_set_desc(). Printed out with task
* dump for debugging - WARN, BUG, panic or sysrq.
*/
char desc[WORKER_DESC_LEN];
/* used only by rescuers to point to the target workqueue */
struct workqueue_struct *rescue_wq; /* I: the workqueue to rescue */
};
实际开发中只需要定义work_struct即可,然后使用INIT_WORK宏来初始化:work表示初始化的工作,func表示工作对应的处理函数
#define INIT_WORK(_work, _func)
也可以使用DECLARE_WORK宏一次性完成工作的创建和初始化:n表示工作work_struct,f表示处理函数
#define DECLARE_WORK(n, f)
和tasklet一样,工作也需要调度才能运行,工作的调度函数为schedule_work:
bool schedule_work(struct work_struct *work)
示例:
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
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);
......
}
四:设备树文件中断配置
Linux内核如果使用设备树那么就需要对设备树文件中的中断信息进行设置,Linux内核通过读取设备树文件中的中断属性信息来配置中断。imx6ull的中断控制节点为intc,根据compatible属性可以在内核中找到相关的驱动文件。
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
1、属性“#interrupt-cells”表示该中断控制器下设备的cells大小。对于ARM处理器的GIC来说一共3个cells:
(1)中断类型,0表示SPI中断,1表示PPI中断。
(2)中断号,对于SPI中断来说中断号的范围为0~987,对于PPI中断来说中断号的范围为0~15
(3)标志,bit[3:0]表示中断触发类型,1表示上升沿触发,2表示下降沿触发,4表示高电平触发,8表示低电平触发。bit[15:8]为PPI中断的CPU掩码。
2、属性“interrupt-controller”节点为空,表示当前节点是中断控制器。
示例:gpio1作为中断控制器
gpio1: gpio@0209c000 {
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>;
};
具体设备:GPIO1_IO18
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "my-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
status = "okay";
};
3、属性“interrupts”指定中断号和触发方式等,具体信息数量由“#interrupt-cells”指定,当为2时,针对示例“interrupts = <18 IRQ_TYPE_EDGE_BOTH”:0表示GPIO5_IO00,IRQ_TYPE_EDGE_BOTH表示边沿触发。
4、属性“interrupt-parent”指定父中断,也就是中断控制器。