linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)
2011年9月份时候做的笔记, 当时阅读中断子系统的代码后做的一个PPT, 内核版本不记得了, 硬件平台是samsung 的S5PV210.
这部分主要是针对汇编和hard irq的部分, 在hard irq处理后的softirq的处理, 以及下半部的处理(tasklet/workqueue)都没有涉及.
Agenda
ARM CPU CORE 中只有两根中断引脚, 分别是IRQ和FIQ.
?Important structs
struct irq_desc <strong>irq_desc</strong>[NR_IRQS] __cacheline_aligned_in_smp = { [0 ... NR_IRQS-1] = { .status = IRQ_DISABLED, .chip = &no_irq_chip, .handle_irq = handle_bad_irq, .depth = 1, .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), } }; //NR_IRQS = 393 in R70
struct irq_desc { <strong> unsigned int irq;</strong> struct timer_rand_state *timer_rand_state; unsigned int *kstat_irqs; #ifdef CONFIG_INTR_REMAP struct irq_2_iommu *irq_2_iommu; #endif <strong> irq_flow_handler_t handle_irq; //high level irq-events handle struct irq_chip *chip;</strong> struct msi_desc *msi_desc; void *handler_data; void *chip_data; <strong> struct irqaction *action; /* IRQ action list */</strong> unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; raw_spinlock_t lock; #ifdef CONFIG_SMP cpumask_var_t affinity; const struct cpumask *affinity_hint; unsigned int node; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif const char *name; } ____cacheline_internodealigned_in_smp;
struct <strong>irq_chip</strong> { const char *name; unsigned int (*startup)(unsigned int irq); void (*shutdown)(unsigned int irq); void (*enable)(unsigned int irq); void (*disable)(unsigned int irq); void (*ack)(unsigned int irq); void (*mask)(unsigned int irq); void (*mask_ack)(unsigned int irq); void (*unmask)(unsigned int irq); void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); int (*set_affinity)(unsigned int irq, const struct cpumask *dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); int (*set_wake)(unsigned int irq, unsigned int on); void (*bus_lock)(unsigned int irq); void (*bus_sync_unlock)(unsigned int irq); /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
struct <strong>irqaction</strong> { <strong> irq_handler_t handler; // handler assigned by request_irq</strong> unsigned long flags; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; };之间的关系图:
?External interrupt resources in S5PV210
首先可以查看系统中有哪些有效的中断以及相关的信息.
# cat /proc/interrupts //只显示有相应的action的IRQ的信息 CPU0 IRQ_NR count desc->chip->name action->name 16: 43 s3c-uart s5pv210-uart 18: 59 s3c-uart s5pv210-uart 33: 1 s5p_vic_eint mmc1 36: 0 s5p_vic_eint a700_ts 37: 1 s5p_vic_eint aic3254 headset irq 38: 0 s5p_vic_eint keypad 39: 0 s5p_vic_eint keypad 40: 0 s5p_vic_eint keypad 41: 0 s5p_vic_eint keypad 42: 0 s5p_vic_eint keypad 43: 0 s5p_vic_eint keypad 45: 1 s5p_vic_eint hpd 46: 1 s5p_vic_eint USB wak up 50: 0 VIC s3c-pl330.0 51: 0 VIC s3c-pl330.1 52: 0 VIC s3c-pl330.2 58: 0 VIC System timer 59: 0 VIC s3c2410-wdt 61: 14772 VIC rtc-tick 78: 220 VIC s3c2440-i2c.0 83: 27985 VIC s3c2440-i2c.2 88: 1 VIC s3c-udc 90: 52662 VIC mmc0 92: 268 VIC mmc1 93: 0 VIC s3c-csis 97: 2582 VIC s3cfb, s3cfb 102: 0 VIC s3c-fimc1 103: 0 VIC s3c-fimc2 105: 0 VIC s3c-g2d 106: 747 VIC pvrsrvkm 107: 0 VIC s5p-tvout 108: 0 VIC s5p-tvout 109: 0 VIC s3c2440-i2c.1 110: 0 VIC s3c-mfc 111: 0 VIC s5p-tvout 130: 13 VIC mmc2 170: 0 s5p-eint Bq27520_INT Err: 0
start_kernel setup_arch early_trap_init early_irq_init //没做什么事 init_IRQ s5pv210_init_irq s5p_init_irq vic_init(irq_nr直接是从32开始的, 前面的目前看起来至少留给了timer和UART) s3c_init_vic_timer_irq s3c_init_uart_irqs其中的early_trap_init, 基本的思路就是, 对于有MMU的系统, 异常向量的虚拟地址被映射到0xFFFF0000, 所以, 真正的7个异常向量(__vectors_start~__vectors_end)是被拷贝到这个0xFFFF0000开始的地方了. 接着, 异常处理代码块(__stubs_start~__stubs_end)被拷贝到0xFFFF0200处.
void __init early_trap_init(void) { unsigned long vectors = CONFIG_VECTORS_BASE; //0xFFFF0000 extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; extern char __kuser_helper_start[], __kuser_helper_end[]; int kuser_sz = __kuser_helper_end - __kuser_helper_start; /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */ <strong> memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);</strong> /* * Copy signal return handlers into the vector page, and * set sigreturn to be a pointer to these. */ memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, sizeof(sigreturn_codes)); memcpy((void *)KERN_RESTART_CODE, syscall_restart_code, sizeof(syscall_restart_code)); flush_icache_range(vectors, vectors + PAGE_SIZE); modify_domain(DOMAIN_USER, DOMAIN_CLIENT); }
接下来, 中断发生后, 首先我们看到的是前面注册的那些vectors. 异常向量表的写法主要使用跳转指令B来进行. 因为异常向量表和异常处理代码块之间没有超过B指令要求的2^24=32MB, 仅仅相差0x200. 但是因为vertor_swi不是在这个文件中定义的, 所以, 只能用LDR指令来进行处理了.
__vectors_start: ARM( swi SYS_ERROR0 ) THUMB( svc #0 ) THUMB( nop ) W(b) vector_und + stubs_offset W(ldr) pc, .LCvswi + stubs_offset W(b) vector_pabt + stubs_offset // prefetch abort W(b) vector_dabt + stubs_offset // data abort W(b) vector_addrexcptn + stubs_offset // W(b) vector_irq + stubs_offset //IRQ入口 W(b) vector_fiq + stubs_offset //FIQ .globl __vectors_end __vectors_end:
@@@ 中断处理程序的 stub vector_irq: @ 调整 LR_irq sub lr, lr, #4 @ 保存 R0, LR_irq(中断之前的 PC, 断点), SPSR_irq(中断之前的 CPSR) 到 irq模式的栈中 stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr @ SPSR 设置为 SVC模式 mrs r0, cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 @ 根据中断前的模式跳转到相应的处理程序 @ lr是中断刚开始时的 SPSR,即被中断代码的 CPSR,其低 4位表示中断之前的模式 and lr, lr, #0x0f mov r0, sp ldr lr, [pc, lr, lsl #2] @ 跳转到相应模式的处理程序,模式变为 SVC(SPSR 拷贝到 CPSR ) movs pc, lr @ 跳转表,必须紧跟 ldr lr,[pc,lr,lsl #2]和 movs pc,lr 两条指令(ARM 流水线机制??) .long __irq_usr @ 0 (USR) .long __irq_invalid @ 1 (FIQ) .long __irq_invalid @ 2 (IRQ) .long __irq_svc @ 3 (SVC) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 (ABT) .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b (UND) .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f (SYS)
user mode的处理
@@@ USR模式中断入口 __irq_usr: @ 在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构 sub sp, sp, #S_FRAME_SIZE stmib sp, {r1 - r12} ldmia r0, {r1 - r3} add r0, sp, #S_PC @ here for interlock avoidance mov r4, #-1 @ "" "" "" "" str r1, [sp] @ save the "real" r0 copied @ from the exception stack @ We are now ready to fill in the remaining blanks on the stack: @ r2 - lr_<exception>, already fixed up for correct return/restart @ r3 - spsr_<exception> @ r4 - orig_r0 (see pt_regs definition in ptrace.h) @ Also, separately save sp_usr and lr_usr stmia r0, {r2 - r4} stmdb r0, {sp, lr}^ @ Clear FP to mark the first stack frame zero_fp @ 把被中断任务的 preempt_count 增加 1 get_thread_info tsk #ifdef CONFIG_PREEMPT ldr r8, [tsk, #TI_PREEMPT] @ get preempt count add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] #endif <div><div>?@ 循环调用 asm_do_IRQ() </div><div>?1: get_irqnr_and_base r0, r6, r5, lr </div><div>? movne r1, sp </div><div>?@ routine called with r0 = irq number, r1 = struct pt_regs * </div><div>? adrne lr, 1b </div><div>? <span style="color:red;"><strong>bne asm_do_IRQ</strong></span> </div><div>? </div><div>?#ifdef CONFIG_PREEMPT </div><div>? ldr r0, [tsk, #TI_PREEMPT] </div><div>? str r8, [tsk, #TI_PREEMPT] </div><div>? teq r0, r7 </div><div>? strne r0, [r0, -r0] </div><div>?#endif </div><div>?@ 返回到 user 模式 </div><div>? mov why, #0 </div><div>?<strong><span style="color:#FF0000;"> b ret_to_user</span></strong> </div></div>
svc mode的处理
@@@ SVC模式中断入口 __irq_svc: @ 在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构 sub sp, sp, #S_FRAME_SIZE tst sp, #4 bicne sp, sp, #4 stmib sp, {r1 - r12} ldmia r0, {r1 - r3} add r5, sp, #S_SP @ here for interlock avoidance mov r4, #-1 @ "" "" "" "" add r0, sp, #S_FRAME_SIZE @ "" "" "" "" addne r0, r0, #4 str r1, [sp] @ save the "real" r0 copied from the exception stack mov r1, lr @ We are now ready to fill in the remaining blanks on the stack: @ r0 - sp_svc @ r1 - lr_svc @ r2 - lr_<exception>, already fixed up for correct return/restart @ r3 - spsr_<exception> @ r4 - orig_r0 (see pt_regs definition in ptrace.h) stmia r5, {r0 - r4} @ 把被中断任务的 preempt_count 增加 1 #ifdef CONFIG_PREEMPT get_thread_info tsk ldr r8, [tsk, #TI_PREEMPT] @ get preempt count add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] #endif @ 循环调用 asm-do_IRQ() 1: get_irqnr_and_base r0, r6, r5, lr movne r1, sp @ routine called with r0 = irq number, r1 = struct pt_regs * adrne lr, 1b <span style="color:#FF0000;"><strong> bne asm_do_IRQ </strong></span> @ 如果需要调度,调用 svc_preempt进行内核抢占 #ifdef CONFIG_PREEMPT ldr r0, [tsk, #TI_FLAGS] @ get flags tst r0, #_TIF_NEED_RESCHED blne svc_preempt preempt_return: ldr r0, [tsk, #TI_PREEMPT] @ read preempt value str r8, [tsk, #TI_PREEMPT] @ restore preempt count teq r0, r7 strne r0, [r0, -r0] @ bug() #endif @ 返回到内核空间 ldr r0, [sp, #S_PSR] @ irqs are already disabled msr spsr_cxsf, r0 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
可以看到, 都调用了 asm_do_IRQ
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); <strong> irq_enter();</strong> /* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */ if (unlikely(irq >= NR_IRQS)) { if (printk_ratelimit()) printk(KERN_WARNING "Bad IRQ%u\n", irq); ack_bad_irq(irq); } else { <span style="color:#FF0000;"><strong> generic_handle_irq(irq);</strong></span> } /* AT91 specific workaround */ irq_finish(irq); <strong> irq_exit();</strong> set_irq_regs(old_regs); }
static inline void generic_handle_irq(unsigned int irq) { generic_handle_irq_desc(irq, irq_to_desc(irq)); } static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc) { #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ <strong> desc->handle_irq(irq, desc); // high level handle, take handle_level_irq for example</strong> #else if (likely(desc->handle_irq)) desc->handle_irq(irq, desc); else __do_IRQ(irq); #endif }
然后进入 handle_level_irq
Void handle_level_irq(unsigned int irq, struct irq_desc *desc) { struct irqaction *action; irqreturn_t action_ret; raw_spin_lock(&desc->lock); mask_ack_irq(desc, irq); if (unlikely(desc->status & IRQ_INPROGRESS)) goto out_unlock; desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); kstat_incr_irqs_this_cpu(irq, desc); action = desc->action; if (unlikely(!action || (desc->status & IRQ_DISABLED))) goto out_unlock; desc->status |= IRQ_INPROGRESS; raw_spin_unlock(&desc->lock); <strong> action_ret = handle_IRQ_event(irq, action);</strong> if (!noirqdebug) note_interrupt(irq, desc, action_ret); raw_spin_lock(&desc->lock); desc->status &= ~IRQ_INPROGRESS; if (!(desc->status & (IRQ_DISABLED | IRQ_ONESHOT))) unmask_irq(desc, irq); out_unlock: raw_spin_unlock(&desc->lock); }然后是handle_IRQ_event, 这边调用了request_irq时候注册的那个handle.
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { irqreturn_t ret, retval = IRQ_NONE; unsigned int status = 0; do { trace_irq_handler_entry(irq, action); <strong> ret = action->handler(irq, action->dev_id); // handle registered by request_irq</strong> trace_irq_handler_exit(irq, action, ret); switch (ret) { case IRQ_WAKE_THREAD: … /* Fall through to add to randomness */ case IRQ_HANDLED: status |= action->flags; break; default: break; } retval |= ret; action = action->next; } while (action); if (status & IRQF_SAMPLE_RANDOM) add_interrupt_randomness(irq); local_irq_disable(); return retval; }以图表示的话, 就是:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) 参数: irq---中断通道号,取值范围为 0~NR_IRQS – 1 handler---中断处理程序,原型为 irq_return_t isr_func(int irq, void *dev_id) irq_flags---标志位 dev_name---名称,将会显示在/proc/interrupts中 dev_id---区分共享同一个中断通道的不同的处理程序 void free_irq(unsigned int irq, void *dev_id) 参数: irq---中断通道号,取值范围为 0~NR_IRQS – 1 dev_id---区分共享同一个中断通道的不同的处理程序时才需要用到.
int set_irq_chip(unsigned int irq, struct irq_chip *chip) 设置 chip int set_irq_chip_data(unsigned int irq, void *data) 设置 chip_data int set_irq_handle(unsigned int irq, irq_flow_handler_t handle) 设置 handle_irq int set_irq_data(unsigned int irq, void *data) 设置 handler_data int set_irq_type(unsigned int irq, unsigned int type) 设置指定通道的触发类型
Ryan: PPT完成于2011.9.15, blog完成于2015.1.4
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。