1. 程式人生 > >linux的中斷子系統簡介(彙編和hard irq部分)_ARM平臺(S5PV210)

linux的中斷子系統簡介(彙編和hard irq部分)_ARM平臺(S5PV210)

2011年9月份時候做的筆記, 當時閱讀中斷子系統的程式碼後做的一個PPT, 核心版本不記得了, 硬體平臺是samsung 的S5PV210.

這部分主要是針對彙編和hard irq的部分, 在hard irq處理後的softirq的處理, 以及下半部的處理(tasklet/workqueue)都沒有涉及.

Agenda

•Interrupts in ARM •Important structs •External interrupt resources in S5PV210 •Code flow •Kernel API •Interrupts in ARM
ARM CPU CORE 中只有兩根中斷引腳, 分別是IRQ和FIQ.
•IRQ –Why chip can handle so many IRQS?      ===>  VIC •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
irq_desc is a global struct describing all the interrupt lines in the system.

struct irq_desc {
<span style="color:#FF0000;">	unsigned int		irq;</span>
	struct timer_rand_state 	*timer_rand_state;
	unsigned int            		*kstat_irqs;
#ifdef CONFIG_INTR_REMAP
	struct irq_2_iommu      	*irq_2_iommu;
#endif
<span style="color:#FF0000;">	irq_flow_handler_t		handle_irq;	//high level irq-events handle
	struct irq_chip		*chip;</span>
	struct msi_desc		*msi_desc;
	void		*handler_data;
	void		*chip_data;
<span style="color:#FF0000;">	struct irqaction		*action;	/* IRQ action list */</span>
	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> {
<span style="color:#FF0000;">	irq_handler_t handler;  // handler assigned by request_irq</span>
	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


•Code flow
首先可以檢視系統中有哪些有效的中斷以及相關的資訊.
# 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

首先是初始化的過程, IRQ init sequence:
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.
	 */
<span style="color:#FF0000;">	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);</span>

	/*
	 * 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

•@  迴圈呼叫 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 
• bne <span style="color:#FF0000;">asm_do_IRQ</span>
• 
•#ifdef CONFIG_PREEMPT 
•  ldr  r0, [tsk, #TI_PREEMPT] 
•  str  r8, [tsk, #TI_PREEMPT] 
• teq r0, r7 
• strne  r0, [r0, -r0] 
•#endif 
•@  返回到 user 模式   
• mov why, #0 
• <span style="color:#FF0000;">b ret_to_user</span>

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 
 bne asm_do_IRQ 
 
@  如果需要排程,呼叫 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);

	<span style="color:#FF0000;">irq_enter();</span>

	/*
	 * 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;">generic_handle_irq(irq);</span>
	}

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	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
	<span style="color:#FF0000;">desc->handle_irq(irq, desc);  // high level handle, take handle_level_irq for example</span>
#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);

	action_ret = <span style="color:#FF0000;">handle_IRQ_event(irq, action);</span>
	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);
		ret = <span style="color:#FF0000;">action->handler(irq, action->dev_id);	 //  handle registered by request_irq</span>
		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;
}
以圖表示的話, 就是:


•Kernel API
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