1. 驱动申请中断API
1. 1 和中断相关的linux实时性分析以及中断线程化的背景介绍
1、linux内核的实时性
在Linux kernel中,一个外设的中断处理被分成top half和bottom half,top half进行最关键,最基本的处理,而比较耗时的操作被放到bottom half(softirq、tasklet)中延迟执行。虽然bottom half被延迟执行,但始终都是先于进程执行的。为何不让这些耗时的bottom half和普通进程公平竞争呢?因此,linux kernel借鉴了RTOS的某些特性,对那些耗时的驱动interrupt handler进行线程化处理,在内核的抢占点上,让线程(无论是内核线程还是用户空间创建的线程,还是驱动的interrupt thread)在一个舞台上竞争CPU。
中断上下部,不能让内核保证关中断的时间和抢占时间,也就是响应时间不确定。内核使用线程化比上下部更能保证响应时间,但是对于需要快速处理的中断支持不好
1.2 request_threaded_irq的接口规格
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)
1、输入参数描述
irq:要注册handler的那个IRQ number。
handler:primary handler。需要注意的是primary handler和threaded interrupt handler不能同时为空,否则会出错
thread_fn:threaded interrupt handler。如果该参数不是NULL,那么系统会创建一个kernel thread,调用的function就是thread_fn
irqflags:
flag定义 | 描述 |
---|---|
IRQF_TRIGGER_XXX | 描述该interrupt line触发类型的flag |
IRQF_SHARED | 这是flag用来描述一个interrupt line是否允许在多个设备中共享 |
IRQF_PERCPU | 特定属于一个CPU的 |
IRQF_NOBALANCING | 对于那些可以在多个CPU之间共享的中断,具体送达哪一个processor是有策略的,我们可以在多个CPU之间进行平衡。如果你不想让你的中断参与到irq balancing的过程中那么就设定这个flag |
IRQF_ONESHOT | one shot本身的意思的只有一次的,结合到中断这个场景,则表示中断是一次性触发的,不能嵌套 |
IRQF_NO_SUSPEND | 在系统suspend的时候,不用disable这个中断,如果disable,可能会导致系统不能正常的resume |
IRQF_FORCE_RESUME | 在系统resume的过程中,强制必须进行enable的动作,即便是设定了IRQF_NO_SUSPEND这个flag |
IRQF_NO_THREAD | 有些low level的interrupt是不能线程化的(例如系统timer的中断),这个flag就是起这个作用的 |
1.3 request_threaded_irq代码分析
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;
if (((irqflags & IRQF_SHARED) && !dev_id) || ---------------------------------------(1)
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
desc = irq_to_desc(irq); ------------------------------------(2)
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) || --------------------------(3)
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) { ----------------------------------(4)
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}
retval = __setup_irq(irq, desc, action); -----------------------(5)
if (retval) {
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}
return retval;
}
(1)对于那些需要共享的中断,在request irq的时候需要给出dev id,否则会出错退出。为何对于IRQF_SHARED的中断必须要给出dev id呢?实际上,在共享的情况下,一个IRQ number对应若干个irqaction,当操作irqaction的时候,仅仅给出IRQ number就不是非常的足够了,这时候,需要一个ID表示具体的irqaction,这里就是dev_id的作用了。我们举一个例子:
void free_irq(unsigned int irq, void *dev_id)
当释放一个IRQ资源的时候,不但要给出IRQ number,还要给出device ID。只有这样,才能精准的把要释放的那个irqaction 从irq action list上移除
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
do {
irqreturn_t res;
res = action->handler(irq, action->dev_id);
……
action = action->next;
} while (action);
……
}
linux interrupt framework虽然支持中断共享,但是它并不会协助解决识别问题,它只会遍历该IRQ number上注册的irqaction的callback函数,这样,虽然只是一个外设产生的中断,linux kernel还是把所有共享的那些中断handler都逐个调用执行。为了让系统的performance不受影响,irqaction的callback函数必须在函数的最开始进行判断,是否是自己的硬件设备产生了中断(读取硬件的寄存器),如果不是,尽快的退出。
需要注意的是,这里dev_id并不能在中断触发的时候用来标识需要调用哪一个irqaction的callback函数,通过上面的代码也可以看出,dev_id有些类似一个参数传递的过程,可以把具体driver的一些硬件信息,组合成一个structure,在触发中断的时候可以把这个structure传递给中断处理函数。
(2)通过IRQ number获取对应的中断描述符
(3)并非系统中所有的IRQ number都可以request,有些中断描述符被标记为IRQ_NOREQUEST,标识该IRQ number不能被其他的驱动request。
(4)传入request_threaded_irq的primary handler NULL、threaded handler设定的情况
这种情况下,系统会帮忙设定一个default的primary handler:irq_default_primary_handler,协助唤醒threaded handler线程
(5)这部分的代码很简单,分配struct irqaction,赋值,调用__setup_irq进行实际的注册过程。
2、注册irqaction
(1)nested IRQ的处理代码
static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
……
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
new->handler = irq_nested_primary_handler;
} else {
……
}
……
}
(2)forced irq threading处理
forced irq threading其实就是将系统中所有可以被线程化的中断handler全部线程化,即便你在request irq的时候,设定的是primary handler,而不是threaded handler。当然那些不能被线程化的中断(标注了IRQF_NO_THREAD的中断,例如系统timer)还是排除在外的。irq_settings_can_thread函数就是判断一个中断是否可以被线程化,如果可以的话,则调用irq_setup_forced_threading在set irq的时候强制进行线程化。具体代码如下:
static void irq_setup_forced_threading(struct irqaction *new)
{
if (!force_irqthreads)-------------------------------(a)
return;
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))-------(b)
return;
new->flags |= IRQF_ONESHOT; -------------------------(d)
if (!new->thread_fn) {-------------------------------(c)
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
}
}
(a)系统中有一个强制线程化的选项:CONFIG_IRQ_FORCED_THREADING,如果没有打开该选项,force_irqthreads总是0,因此irq_setup_forced_threading也就没有什么作用,直接return了。如果打开了CONFIG_IRQ_FORCED_THREADING,说明系统支持强制线程化,但是具体是否对所有的中断进行强制线程化处理还是要看命令行参数threadirqs。如果kernel启动的时候没有传入该参数,那么同样的,irq_setup_forced_threading也就没有什么作用,直接return了。只有bootloader向内核传入threadirqs这个命令行参数,内核才真正在启动过程中,进行各个中断的强制线程化的操作。
(b)看到IRQF_NO_THREAD选项你可能会奇怪,前面irq_settings_can_thread函数不是检查过了吗?为何还要重复检查?其实一个中断是否可以进行线程化可以从两个层面看:一个是从底层看,也就是从中断描述符、从实际的中断硬件拓扑等方面看。另外一个是从中断子系统的用户层面看,也就是各个外设在注册自己的handler的时候是否想进行线程化处理。所有的IRQF_XXX都是从用户层面看的flag,因此如果用户通过IRQF_NO_THREAD这个flag告知kernel,该interrupt不能被线程化,那么强制线程化的机制还是尊重用户的选择的。
PER CPU的中断都是一些较为特殊的中断,不是一般意义上的外设中断,因此对PER CPU的中断不强制进行线程化。IRQF_ONESHOT选项说明该中断已经被线程化了(而且是特殊的one shot类型的),因此也是直接返回了。
(c)强制线程化只对那些没有设定thread_fn的中断进行处理,这种中断将全部的处理放在了primary interrupt handler中(当然,如果中断处理比较耗时,那么也可能会采用bottom half的机制),由于primary interrupt handler是全程关闭CPU中断的,因此可能对系统的实时性造成影响,因此考虑将其强制线程化。struct irqaction中的thread_flags是和线程相关的flag,我们给它打上IRQTF_FORCED_THREAD的标签,表明该threaded handler是被强制threaded的。new->thread_fn = new->handler这段代码表示将原来primary handler中的内容全部放到threaded handler中处理,新的primary handler被设定为default handler。
(d)强制线程化是一个和实时性相关的选项
(3)创建interrupt线程。代码如下:
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,
};
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;
}
if (IS_ERR(t))
return PTR_ERR(t);
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m); -----------设定这个中断线程的调度策略和调度优先级
get_task_struct(t); --------------用get_task_struct可以为这个threaded handler的task struct增加一次reference count,
这样,即便是该thread异常退出也可以保证它的task struct不会被释放掉
new->thread = t;
set_bit(IRQTF_AFFINITY, &new->thread_flags); --------------设定IRQTF_AFFINITY的标志,在threaded handler中会检查该标志并进行IRQ affinity的设定。
return 0;
}
(4)共享中断的检查
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
unsigned int oldtype;
if (irqd_trigger_type_was_set(&desc->irq_data)) {
oldtype = irqd_get_trigger_type(&desc->irq_data);
} else {
oldtype = new->flags & IRQF_TRIGGER_MASK;
irqd_set_trigger_type(&desc->irq_data, oldtype);
}
if (!((old->flags & new->flags) & IRQF_SHARED) || ------------------(a)
(oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
((old->flags ^ new->flags) & IRQF_ONESHOT))
goto mismatch;
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
do { ------------------(b)
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}
(a)old指向注册之前的action list,如果不是NULL,那么说明需要共享interrupt line。但是如果要共享,需要每一个irqaction都同意共享(IRQF_SHARED),每一个irqaction的触发方式相同(都是level trigger或者都是edge trigger),相同的oneshot类型的中断(都是one shot或者都不是),per cpu类型的相同中断(都是per cpu的中断或者都不是)。
(b)将该irqaction挂入队列的尾部。
(5)thread mask的设定
if (new->flags & IRQF_ONESHOT) {
if (thread_mask == ~0UL) {
ret = -EBUSY;
goto out_unlock;
}
new->thread_mask = 1UL << ffz(thread_mask);
} else if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
irq);
ret = -EINVAL;
goto out_unlock;
}