在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
我们在申请中断时究竟是在做什么?共享中断是如何实现的?在本篇中你都可以找到答案。
3 中断注册和注销
3.1 中断注册
前文中断初始化过程已经给irq_chip和handle_irq赋过值了。本节主要的目的就是给irqaction赋值。
涉及到的最重要的接口就是 request_irq ,该函数主要完成了下述几个重点工作。
- 注册指定中断号对应的后处理函数(基于链表技术实现中断号共享功能)。
- 调用setup_irq实现中断方式设置。
- 调用setup_irq实现中断使能。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
struct irqaction *action;
int retval;
...
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
if (irq >= NR_IRQS)
return -EINVAL;
if (irq_desc[irq].status & IRQ_NOREQUEST)
return -EINVAL;
if (!handler)
return -EINVAL;
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action)
return -ENOMEM;
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
select_smp_affinity(irq);
...
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
}
request其实并没有做太多实质性的工作,大部分工作都是委托setup_irq来做的。
- 从中我们可以看到内核的封装思想。外部函数尽量设计的简单,暴露尽量少的细节,如果细节过多就再封装一个内部函数来实现。
- 我们还可以学到的一点是,当结构体入参比较复杂时,可以再创建一个入参为结构体成员的外部函数给外部调用者使用。
接下来,我们一起分析下setup_irq函数。
int setup_irq(unsigned int irq, struct irqaction *new)
{
struct irq_desc *desc = irq_desc + irq;
struct irqaction *old, **p;
const char *old_name = NULL;
unsigned long flags;
int shared = 0;
if (irq >= NR_IRQS)
return -EINVAL;
if (desc->chip == &no_irq_chip)
return -ENOSYS;
...
spin_lock_irqsave(&desc->lock, flags);
p = &desc->action;
old = *p;
if (old) {
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
...
do {
p = &old->next;
old = *p;
} while (old);
shared = 1;
}
*p = new;
if (new->flags & IRQF_NOBALANCING)
desc->status |= IRQ_NO_BALANCING;
if (!shared) {
irq_chip_set_defaults(desc->chip);
...
if (new->flags & IRQF_TRIGGER_MASK) {
if (desc->chip && desc->chip->set_type)
desc->chip->set_type(irq,
new->flags & IRQF_TRIGGER_MASK);
else
printk(KERN_WARNING "No IRQF_TRIGGER set_type "
"function for IRQ %d (%s)\n", irq,
desc->chip ? desc->chip->name :
"unknown");
} else
compat_irq_chip_set_default_handler(desc);
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);
if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = 0;
desc->status &= ~IRQ_DISABLED;
if (desc->chip->startup)
desc->chip->startup(irq);
else
desc->chip->enable(irq);
} else
desc->depth = 1;
}
desc->irq_count = 0;
desc->irqs_unhandled = 0;
spin_unlock_irqrestore(&desc->lock, flags);
new->irq = irq;
register_irq_proc(irq);
new->dir = NULL;
register_handler_proc(irq, new);
return 0;
mismatch:
...
spin_unlock_irqrestore(&desc->lock, flags);
return -EBUSY;
}
该函数如果需要你自己来实现,我相信大体的流程都是没问题的,其中有几个技巧和需要注意的问题值得我们学习:
- 操作desc[irq]时一定要加锁,否则可能产生竞态。
- 在操作链表时使用了struct irqaction **p二级指针,具体的好处可以参看《C和指针》中的链表操作相关章节。
- 虽然在C语言中不推荐使用goto语句,但在大量类似的错误处理场景中使用goto语句是最好的选择。
3.2 中断注销
中断注销是中断注册的逆动作。流程比较简单,主要就是将irqaction链表中的指定节点移除,如果是最后一个节点的话会附带着disable掉指定irq。
注销接口函数是free_irq(),该函数通过dev_id来识别到底卸载链表中的哪个节点。
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc;
struct irqaction **p;
unsigned long flags;
irqreturn_t (*handler)(int, void *) = NULL;
WARN_ON(in_interrupt());
if (irq >= NR_IRQS)
return;
desc = irq_desc + irq;
spin_lock_irqsave(&desc->lock, flags);
p = &desc->action;
for (;;) {
struct irqaction *action = *p;
if (action) {
struct irqaction **pp = p;
p = &action->next;
if (action->dev_id != dev_id)
continue;
*pp = action->next;
...
if (!desc->action) {
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown)
desc->chip->shutdown(irq);
else
desc->chip->disable(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
unregister_handler_proc(irq, action);
synchronize_irq(irq);
...
kfree(action);
return;
}
printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
spin_unlock_irqrestore(&desc->lock, flags);
return;
}
...
}
中断处理流程下回分解。。。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
|