linux的中斷子系統簡介(彙編和hard irq部分)_ARM平臺(S5PV210)
阿新 • • 發佈:2019-02-12
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 ARMARM 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
首先可以檢視系統中有哪些有效的中斷以及相關的資訊.
# 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