4.4.6. linux中断处理
4.4.6.1. 注册中断
中断函数注册接口
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)
参数 |
说明 |
irq |
IRQ软中断号 |
hanlder |
中断发生时优先执行handler,如果此参数为NULL且thread_fn不为空,系统执行默认的handler |
thread_fn |
中断线程化的处理程序,如果不为空会创建一个内核线程。handler和thread_fn不能同时为空 |
irqflags |
中断标志位 |
devname |
中断名称 |
dev_id |
传递给中断处理程序的参数 |
request_threaded_irq(irq, handler, thread_fn, irqflags,devname, dev_id)
| //对于共享中断,通常通过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;
|--desc = irq_to_desc(irq);
| //判断是否设置_IRQ_NOREQUEST标志,它是系统预留的
| //_IRQ_PER_CPU_DEVID标志是per cpu中断,需要用request_percpu_irq()函数注册中断
|--if (!irq_settings_can_request(desc) ||
| WARN_ON(irq_settings_is_per_cpu_devid(desc)))
| return -EINVAL;
| //primary handler和thread_fn不能同时为NULL,primary handler为NULL,
| //则采用默认的irq_default_primary_handler
|--if (!handler)
| if (!thread_fn)
| return -EINVAL;
| handler = irq_default_primary_handler;
| //分配并初始化irqaction
|--action = kzalloc(sizeof(struct irqaction), GFP_KERNEL)
| action->handler = handler;//初始化primary handler
| action->thread_fn = thread_fn;//初始化线程中断处理函数
| action->flags = irqflags;
| action->name = devname;
| action->dev_id = dev_id;
| //使能irq chip供电
|--irq_chip_pm_get(&desc->irq_data)
| //注册irqaction到irq_desc
|--__setup_irq(irq, desc, action)
__setup_irq承担了主要的注册工作
__setup_irq
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|--new->irq = irq;
| //设置触发类型,它在irq_create_fwspec_mapping创建软硬中断映射时初始化
|--if (!(new->flags & IRQF_TRIGGER_MASK))
| new->flags |= irqd_get_trigger_type(&desc->irq_data);
|--nested = irq_settings_is_nested_thread(desc);
| //如果支持中断嵌套,thread_fn不能为NULL, primary handler为空默认设为irq_nested_primary_handler
| if (nested)
| new->handler = irq_nested_primary_handler;
| else
| //判断是否支持中断线程化,如果支持则强制中断线程化
| if (irq_settings_can_thread(desc))
| irq_setup_forced_threading(new);
| //>>>>>>对没有嵌套的中断线程处理函数创建内核线程
|--if (new->thread_fn && !nested)
| setup_irq_thread(new, irq, false);
| if (new->secondary)
| setup_irq_thread(new->secondary, irq, true);
| //IRQCHIP_ONESHOT_SAFE表示中断控制器不支持中断嵌套(中断控制器支持ONESHOT)?
|--if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
| new->flags &= ~IRQF_ONESHOT;
| //request irq chip resource
|--if (!desc->action)
| irq_request_resources(desc);
| //>>>>>共享中断的处理
| //old非null,表示irq_desc已经注册了中断,代表共享中断
|--old_ptr = &desc->action;
| old = *old_ptr;
| if (old)
| //NMI中断不支持共享?
| if (desc->istate & IRQS_NMI)
| ret = -EINVAL;
| goto out_unlock;
| //非共享中断、触发类型不一致、IRQF_ONESHOT标志不一致为不匹配情况
| if (!((old->flags & new->flags) & IRQF_SHARED) ||
| (oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
| ((old->flags ^ new->flags) & IRQF_ONESHOT))
| goto mismatch;
| //old_ptr循环移动指向irq_desc的irqaction链表的最后一个元素的next,thread_mask记录共享中断irqaction
| do {
| thread_mask |= old->thread_mask;
| old_ptr = &old->next;
| old = *old_ptr;
| } while (old);
| shared = 1;//表示一个共享中断
|
|--if (new->flags & IRQF_ONESHOT)
| new->thread_mask = 1UL << ffz(thread_mask);
| // primary handler为NULL,又不支持ONESHOT的控制器,对于电平触发的中断会引发中断风暴
| else if (new->handler == irq_default_primary_handler &&
| !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE))
| ret = -EINVAL;
| goto out_unlock;
| //>>>>>>非共享中断的处理
|--if (!shared)
| init_waitqueue_head(&desc->wait_for_threads);
| __irq_set_trigger(desc,new->flags & IRQF_TRIGGER_MASK);
| irq_activate(desc);
| 清/设desc->istate标志,清/设&desc->irq_data标志
| if (irq_settings_can_autoenable(desc))
| irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
| //将新的irqaction连入irq_desc的irqaction链表
|--*old_ptr = new;
| //Check whether we disabled the irq via the spurious handler
| //before. Reenable it and give it another chance.?
|--if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED))
| desc->istate &= ~IRQS_SPURIOUS_DISABLED;
| __enable_irq(desc);
|--irq_setup_timings(desc, new);
|--if (new->thread)
| wake_up_process(new->thread);
|--if (new->secondary)
| wake_up_process(new->secondary->thread);
|--register_irq_proc(irq, desc);
|--register_handler_proc(irq, new);
__setup_irq会处理共享中断和非共享中断,最终会将irqaction链入irq_desc的irqaction链表完成注册。对于中断线程化会创建线程并会将其加入就绪队列。
irq_setup_forced_threading:如果没有设置_IRQ_NOTHREAD标志,则说明可以被中断线程化,则irq_setup_forced_threading执行强制中断线程化,它通过new->thread_fn = new->handler 将主中断处理函数赋值给中断线程函数,后面会通过setup_irq_thread来创建线程专门执行主处理函数,实现了中断处理的线程化
setup_irq_thread:会通过kthread_create创建内核线程,调度策略SCHED_FIFO,优先级50,对于中断线程化的情况,则主处理函数线程会”irq/中断号-中断名”为线程名,原有的线程处理函数线程会以”irq/中断号-s-中断名”; 对于非中断线程化的情况,则只会为线程处理函数线程创建名为”irq/中断号-中断名”的线程。同时会设置中断亲和标志IRQTF_AFFINITY?
对于共享中断的处理:如果irq_desc上已经有irq_action,而本次又要链接新的irq_action,则说明为一个共享中断,共享中断需要检查不匹配的情况,在最后会将old_ptr循环移动指向irq_desc的irqaction链表的最后一个元素的next,old指向*old_ptr,同时thread_mask每个bit代表一个irqaction,记录了所有的共享中断,当desc->thread_active等于0,才能算中断处理完成。
new->handler == irq_default_primary_handler && !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)):primary handler为irq_default_primary_handler ,说明请求时为NULL,有一种情况,如msi中断,中断控制器本身支持ONESHOT,则会设置IRQCHIP_ONESHOT_SAFE,没有设置IRQCHIP_ONESHOT_SAFE,说明中断控制器不支持ONESHOT,但是对于电平触发的中断,如果不设置primary handler,控制器又不支持ONESHOT,就会引发中断风暴,因此此情况下一定要设置primary handler
非共享中断的处理:初始化desc->wait_for_threads等待队列?设置触发类型?清/设desc->istate标志,清/设&desc->irq_data标志
*old_ptr = new:将新的irqaction连入irq_desc的irqaction链表
wake_up_process:唤醒中断线程
4.4.6.2. 中断处理
4.4.6.2.1. gic_handle_irq
<drivers/irqchip/irq-gic.c>
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
|--struct gic_chip_data *gic = &gic_data[0];
|--void __iomem *cpu_base = gic_data_cpu_base(gic);
|--do {
//读取GIC的GICC_IAR寄存器,读取行为本身是对中断的ack
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
//获取发生中断的硬中断号
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (unlikely(irqnr >= 1020))
break;
//写入GICC_EOIR寄存器,通知CPU interface中断处理完成
if (static_branch_likely(&supports_deactivate_key))
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
isb();
//对SGI私有中断的处理
if (irqnr <= 15)
smp_rmb();
this_cpu_write(sgi_intid, irqstat);
handle_domain_irq(gic->domain, irqnr, regs);
} while (1);
generic_handle_irq在__gic_init_bases函数中被设置为默认的中断处理函数,在前面的文章中已经解释过了
读GIC的GICC_IAR寄存器,读取行为本身是对中断的ack,会让中断从pending状态进入到active状态
写入GICC_EOIR寄存器,通知CPU interface中断处理完成,让中断从active状态进入到inactive状态
handle_domain_irq: 对于gic中断控制器会执行gic_of_init初始化,它会创建并注册irq_domain.第一个参数gic->domain就是gic初始化时创建的,它代表了中断控制器。。irqnr为硬中断号,通过硬 中断号可以知道软中断号,然后以软中断号为索引可以获取到irq_desc,进一步获取到irq_data并获取到irqaction进行处理
4.4.6.2.2. handle_domain_irq
handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
|--__handle_domain_irq(domain, hwirq, true, regs);
|--unsigned int irq = hwirq;
|--irq_enter();
| //以硬中断号为索引软中断号返回给irq
|--irq = irq_find_mapping(domain, hwirq);
|--generic_handle_irq(irq);
|--irq_exit();
irq_enter: 显式的告诉linux内核现在进入中断上下文,主要通过增加preempt.count的HARDIRQ域计数值来实现
irq_find_mapping: 以硬中断号为索引找到软中断号,这里irq_domain会维护软硬件中断号的映射关系
generic_handle_irq: 为通用中断处理的主函数
irq_exit:irq_enter对应,退出中断上下文
4.4.6.2.3. generic_handle_irq
int generic_handle_irq(unsigned int irq)
|--struct irq_desc *desc = irq_to_desc(irq);
|--generic_handle_irq_desc(desc);
|--desc->handle_irq(desc)
|--handle_fasteoi_irq(desc)
|--handle_irq_event(desc);
|--__handle_irq_event_percpu(desc, &flags);
|--for_each_action_of_desc(desc, action)
| //标记中断被强制线程化
|--if (irq_settings_can_thread(desc) &&
| !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
| lockdep_hardirq_threaded();
|--res = action->handler(irq, action->dev_id);
|--switch (res)
//primary handler处理完毕,需要唤醒中断线程
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action);
//无中断线程
case IRQ_HANDLED:
*flags |= action->flags;
handle_arch_irq可以理解为中断从底层处理进入顶层处理的入口,而desc->handle_irq为中断的回调。最终会调用action->handler,此处为驱动程序中注册的中断处理函数
action->handler函数如果返回的是IRQ_WAKE_THREAD则唤醒中断线程执行,中断线程在注册中断时创建,如果返回IRQ_HANDLED则表示没有中断线程
注解
request_threaded_irq会将参数hanlder传递给action->handler,如果此函数为空,将采用默认的irq_default_primary_hanlder。