首页 > 代码库 > Linux 中断处理

Linux 中断处理

1: 通过调用 request_irq api来注册指定中断号上的 irq_handler,flags可选是否是共享或者其他
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

2:requst_irq 进一步调用 request_threaded_irq
desc = irq_to_desc(irq); // 获取该irq号对应的 irq_desc结构,并check相关的字段,注意到desc结构和irq号是一一对应的(采用基数树结构实现irq_desc_tree)
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 创建一个irqaction对象
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 = __setup_irq(irq, desc, action); // 调用__setup_irq 安装action

3:__setup_irq函数负责安装一个irq,应该是代码的核心所在
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*/
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)

Step1: 如果触发类型没有指定,那么使用默认的配置
Step2:检查中断是否被嵌套到其他的中断线程中
Step3:如果该中断提供了一个线程函数并且中断不被其他线程嵌套,那么就为该中断创建一个线程
/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*/
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;
}
}
Step4:如果该中断号被设置为共享,那么必须满足下面的条件
(1)同一个IRQ上被注册的其他irq_handler都设置了共享,即IRQF_SHARED
(2)中断触发类型必须相同(level, edge, polarity)
(3)是否是ONESHOT类型也必须保持一致
(4)all handlers must agree on per-cpuness

/* add new interrupt at end of irq queue */
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
Step5:
Step6:设置和更新/proc/interrupts的显示
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);

4:内核提供了配置开关,可以开启强制irq线程
注意:部分中断是不能被线程化的,例如IPIs中断等,这些中断被标志为:IRQF_NO_THREAD,All interrupts marked IRQF_TIMER or IRQF_PER_CPU are automatically excluded from threading
#ifdef CONFIG_IRQ_FORCE_THREADING
__readmostly bool fore_threads


5:早期的中断和异常处理
https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html
arch/x86/kernel/head64.c,中断向量共256个(前32个为异常,后面的为中断编号:32~255)
#define NUM_EXCEPTION_VECTORS 32
在64位模式下我们IDT(中断描述符表)初始化

set_intr_gate(n, addr)
set_system_gate(n, addr)
set_system_intr_gate(n, addr)
set_trap_gate(n, addr)
set_task_gate(n, addr)


#ifdef CONFIG_X86_64
struct desc_ptr idt_descr __ro_after_init = {
.size = NR_VECTORS * 16 - 1,
.address = (unsigned long) idt_table,
};

 

asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data)
{

for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)
set_intr_gate(i, early_idt_handler_array[i]); // 设置中断门,填充到idt_table里
load_idt((const struct desc_ptr *)&idt_descr);
}

static inline void _set_gate(int gate, unsigned type, void *addr,
unsigned dpl, unsigned ist, unsigned seg)
{
gate_desc s;

pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
/*
* does not need to be atomic because it is only done once at
* setup time
*/
write_idt_entry(idt_table, gate, &s);
write_trace_idt_entry(gate, &s);
}

static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
memcpy(&idt[entry], gate, sizeof(*gate));
}


如果是由中断门进入中断处理程序的,CPU会清除IF标志位,这样当当前中断处理程序执行时,CPU不会对其他的中断进行处理;
只有当当前的中断处理程序返回时,CPU才在iret指令执行时重新设置IF标志位。

默认情况下,内核栈(线程栈大小为)2个Page大小(32位体系结构上8KB,64位体系结构上16KB)
中断上下文的栈空间大小在禁用KASAN时大小是2个Page大小(和线程栈类似)
#define IRQ_STACK_ORDER (2 + KASAN_STACK_ORDER)
#define IRQ_STACK_SIZE (PAGE_SIZE << IRQ_STACK_ORDER)

#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

PerCPU的中断栈
#ifdef CONFIG_X86_64
per_cpu(irq_stack_ptr, cpu) =
per_cpu(irq_stack_union.irq_stack, cpu) +
IRQ_STACK_SIZE;
#endif


arch/x86/include/asm/irq.h:44:extern __visible unsigned int do_IRQ(struct pt_regs *regs);
arch/x86/kernel/irq_64.c:59: WARN_ONCE(1, "do_IRQ(): %s has overflown the kernel stack (cur:%Lx,sp:%lx,irq stk top-bottom:%Lx-%Lx,exception stk top-bottom:%Lx-%Lx)\n",
arch/x86/kernel/irq.c:208: * do_IRQ handles all normal device IRQ‘s (the special
arch/x86/kernel/irq.c:212:__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
arch/x86/entry/entry_64.S:518: interrupt do_IRQ
arch/x86/entry/entry_32.S:655: call do_IRQ


do_IRQ函数是所有普通设备中断的入口
/*
* do_IRQ handles all normal device IRQ‘s (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc * desc;
/* high bit used in ret_from_ code */
unsigned vector = ~regs->orig_ax;

/*
* NB: Unlike exception entries, IRQ entries do not reliably
* handle context tracking in the low-level entry code. This is
* because syscall entries execute briefly with IRQs on before
* updating context tracking state, so we can take an IRQ from
* kernel mode with CONTEXT_USER. The low-level entry code only
* updates the context if we came from user mode, so we won‘t
* switch to CONTEXT_KERNEL. We‘ll fix that once the syscall
* code is cleaned up enough that we can cleanly defer enabling
* IRQs.
*/

entering_irq();

/* entering_irq() tells RCU that we‘re not quiescent. Check it. */
RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

desc = __this_cpu_read(vector_irq[vector]);

if (!handle_irq(desc, regs)) {
ack_APIC_irq();

if (desc != VECTOR_RETRIGGERED) {
pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
__func__, smp_processor_id(),
vector);
} else {
__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
}
}

exiting_irq();

set_irq_regs(old_regs);
return 1;
}

调用流程 do_IRQ --> handle_irq --> generic_handle_irq_desc --> desc->handle_irq(desc);

作业:
(1)irq_stack_union的含义是什么?
(2)desc数据结构是合适创建的?
答:
256个中断向量对应的irq_desc不是一次性初始化完的。
其中一条路径是:start_kernel --> early_irq_init --> alloc_descs --> irq_insert_desc
另外一条路径是: arch_setup_msi_irq --> irq_alloc_hwirq --> irq_alloc_hwirqs --> __irq_alloc_descs --> alloc_descs

 

Linux 中断处理