Linux中断子系统分析之(一):整体框架
Linux中断子系统分析之(二):通用的中断处理
Linux中断子系统分析之(三):irq domain
Linux中断子系统分析之(四):驱动程序申请中断
中断子系统中有一个重要的设计机制,那就是Top-half和Bottom-half,将紧急的工作放置在Top-half中来处理,而将耗时的工作放置在Bottom-half中来处理。
如果中断不分上下半部处理,那么意味着只有等上一个中断完成处理后才会打开中断,下一个中断才能得到响应。当某个中断处理处理时间较长时,很有可能就会造成其他中断丢失而无法响应,影响系统性能。
中断分成上下半部处理可以提高中断的响应能力,在上半部处理完成后便将中断打开,这样就可以响应其他中断了,等到中断退出的时候再进行下半部的处理。为了进一步提高的实时性,linux内核还提供了中断线程化的机制。
熟悉设备驱动的应该都清楚,经常会在驱动程序中调用request_irq接口或者request_threaded_irq接口来注册设备的中断处理函数。
在讲具体的注册流程前,先看一下主要的中断标志位:
//中断触发类型
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发
#define IRQF_TRIGGER_LOW 0x00000008 //低电平
#define IRQF_ONESHOT 0x00002000 //一次性触发的中断,不能嵌套
/* 为了确保:
1. 在硬件中断处理完成后才能打开中断,中断上半部肯定是这样的,除非在上半部开中断
2. 在中断线程化中保持关闭状态,直到该中断源上的所有thread_fn函数都执行完
驱动程序可以设置IRQF_ONESHOT标志
*/
#define IRQF_SHARED 0x00000080 //多个设备共享一个中断号,需要外设硬件支持
#define IRQF_PERCPU 0x00000400 //属于特定CPU的中断
#define IRQF_NOBALANCING 0x00000800 //禁止在CPU之间进行中断均衡处理
#define IRQF_NO_THREAD 0x00010000 //禁止中断线程化
request_irq是老旧的接口了,这里就分析request_threaded_irq这个函数:
/* irq,软件中断号
handler,primary handler,处理一些紧急的事情,上半部
flags,一些标志位
thread_fn,threaded interrupt handler,系统会创建线程执行该函数
devname,设备的名字
dev_id,对于共享中断,需要传入dev_id,标识自己
*/
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;
//对于共享中断,要传入dev_id
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
//通过软件中断号获取irq_desc
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
......
//handler、thread_fn不能同时为空
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
//分配一个irqaction
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
//根据传入的参数初始化irqaction
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
......
chip_bus_lock(desc);
//设置
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
......
return retval;
}
传入request_threaded_irq的primary handler和threaded handler参数有下面四种组合:
__setup_irq:
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;
cpumask_var_t mask;
......
new->irq = irq;
if (!(new->flags & IRQF_TRIGGER_MASK))
new->flags |= irqd_get_trigger_type(&desc->irq_data);
/* nested类型的中断,parent IRQ的threaded handler通过handle_nested_irq函数
调用该中断的threaded interrupt handler */
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) { //nested类型的中断只需提供thread_fn
ret = -EINVAL;
goto out_mput;
}
new->handler = irq_nested_primary_handler;
} else {
if (irq_settings_can_thread(desc)) {
ret = irq_setup_forced_threading(new); //强制中断线程化
if (ret)
goto out_mput;
}
}
//创建线程
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}
......
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
//old不为NULL,表示为共享中断,则每一个irqaction的触发方式要相同,相同的类型中断
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
((old->flags ^ new->flags) & IRQF_ONESHOT))
goto mismatch;
/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
do {
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1; //shared为1,标志为共享中断
}
......
if (!shared) { //非共享中断
......
/* 设置中断触发类型 */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc,
new->flags & IRQF_TRIGGER_MASK);
......
}
......
//启动中断
if (irq_settings_can_autoenable(desc))
irq_startup(desc, true);
else
/* Undo nested disables: */
desc->depth = 1;
......
} else if (new->flags & IRQF_TRIGGER_MASK) {
......
}
*old_ptr = new; //把new的irqaction挂在irq_desc的action链表
......
if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
desc->istate &= ~IRQS_SPURIOUS_DISABLED;
__enable_irq(desc); //使能中断
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
//唤醒中断线程
if (new->thread)
wake_up_process(new->thread);
if (new->secondary)
wake_up_process(new->secondary->thread);
......
}
强制中断线程化:
static int irq_setup_forced_threading(struct irqaction *new)
{
//如果打开了CONFIG_IRQ_FORCED_THREADING配置项,force_irqthreads总是为1
if (!force_irqthreads)
return 0;
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
return 0; //IRQF_NO_THREAD即不想中断线程化
new->flags |= IRQF_ONESHOT;
/* 如果调用request_threaded_irq都传入了primary handler和threaded handler参数
则primary handler和threaded handler都会在进程上下文里执行
*/
if (new->handler != irq_default_primary_handler && new->thread_fn) {
new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!new->secondary)
return -ENOMEM;
new->secondary->handler = irq_forced_secondary_handler;
new->secondary->thread_fn = new->thread_fn;
new->secondary->dev_id = new->dev_id;
new->secondary->irq = new->irq;
new->secondary->name = new->name;
}
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
return 0;
}
创建中断线程:
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2,
};
//创建线程,执行函数为irq_thread
if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
param.sched_priority -= 1;
}
......
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
get_task_struct(t);
new->thread = t;
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
来张图:
下面讲讲中断线程的唤醒流程,举个例子,如下场景:
某irq的irq_desc里的handle_irq设置为handle_simple_irq,用于简易流控处理(当然具体设置成什么函数,取决于中断控制器驱动程序,在映射阶段设置的),调用request_threaded_irq注册该irq的中断服务例程时,未设置primary handler,设置了threaded handler(xxx_threaded_handler),那么创建的irqaction里的primary handler会设置为irq_default_primary_handler,并会创建一个线程,执行函数为irq_thead。
看看irq_default_primary_handler:
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
该irq到来时,会调用根据irq找到对应的irq_desc执行里面的handle_irq,即handle_simple_irq:
执行到__handle_irq_event_percpu函数:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
//遍历irq_desc的action链表
for_each_action_of_desc(desc, action) {
irqreturn_t res;
......
//执行irqaction里的handler,即irq_default_primary_handler,该函数直接返回IRQ_WAKE_THREAD
res = action->handler(irq, action->dev_id);
......
switch (res) {
case IRQ_WAKE_THREAD:
......
__irq_wake_thread(desc, action); //handler函数返回IRQ_WAKE_THREAD,则唤醒进程
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
__irq_wake_thread:
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
......
//设置thread_flags的IRQTF_RUNTHREAD位
if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
return;
desc->threads_oneshot |= action->thread_mask;
atomic_inc(&desc->threads_active);
//唤醒中断线程
wake_up_process(action->thread);
}
中断线程的执行函数为irq_thread:
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);
if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;
......
//等待中断来临
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;
......
action_ret = handler_fn(desc, action);
if (action_ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);
if (action_ret == IRQ_WAKE_THREAD)
irq_wake_secondary(desc, action);
wake_threads_waitq(desc);
}
......
}
等待中断来临:
static int irq_wait_for_interrupt(struct irqaction *action)
{
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
//如果thread_flags的IRQTF_RUNTHREAD位置1表示中断来了
if (test_and_clear_bit(IRQTF_RUNTHREAD,
&action->thread_flags)) {
__set_current_state(TASK_RUNNING);
return 0;
}
//中断没来则进程睡眠
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return -1;
}
中断来临执行irq_forced_thread_fn或irq_thread_fn函数,取决于是否开启强制中断线程化,以irq_thread_fn为例:
static irqreturn_t irq_thread_fn(struct irq_desc *desc,
struct irqaction *action)
{
irqreturn_t ret;
//执行irqaction里的thread_fn函数,即xxx_threaded_handler
ret = action->thread_fn(action->irq, action->dev_id);
......
return ret;
}