转载于http://blog.csdn.net/dinuliang/article/details/5826542
原文转自http://www.lslnet.com/linux/f/docs1/i06/big5136941.htm
本文試圖由entry-armv.s入手,簡要分析arm cpu的中斷主體部分.cpu 假設為 sa1100
本文假設您已經瞭解i386體系的相應代碼.
本文中任何地方都可能發生錯誤,希望得到您的指正和完善.謝謝.
linux/arch/arm/kernel/entry-armv.s:
__stubs_end:
.equ __real_stubs_start, .LCvectors + 0x200
.LCvectors: swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)
ENTRY(__trap_init)
stmfd sp!, {r4 - r6, lr}
adr r1, .LCvectors @ set up the vectors
mov r0, #0
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 - r6, pc})
在系統啟動初期,負責主要init工作的start_kernel()中調用trap_init(),這是__trap_init()一個包裝.
__trap_init()首先將7個'異常向量'從LCvectors處copy到viraddr 0x0處,這是與arm cpu硬件的約定.而後,再把__stubs_start到__stubs_end之間的代碼也就是具體的異常處理程序copy到viraddr 0x200處.
上述代碼執行或被copy後就不再有用,所以被安排在.text.init段(.section ".text.init",#alloc,#execinstr),該段的內存在初始化的末期(start_kernel()->kernel_thread(init...)->free_initmem())被釋放.
或許值得說明一下在這個cpu中跳躍指令b的機器碼用的是偏移量,而且由於偏移量只佔24bit,所以不能大於正負32M,而ldr pc的寫法則沒有這個限制(注意SWI的異常向量),所以LCvectors處代碼的寫法是正確的.
在linux/arch/arm/kernel/entry-armo.s中有類似的代碼:
.Ljump_addresses:
swi SYS_ERROR0
.word vector_undefinstr - 12
.word vector_swi - 16
.word vector_prefetch - 20
.word vector_data - 24
.word vector_addrexcptn - 28
.word vector_IRQ - 32
.word _unexp_fiq - 36
b . + 8
/*
* initialise the trap system
*/
ENTRY(__trap_init)
stmfd sp!, {r4 - r7, lr}
adr r1, .Ljump_addresses
ldmia r1, {r1 - r7, ip, lr}
orr r2, lr, r2, lsr #2
orr r3, lr, r3, lsr #2
orr r4, lr, r4, lsr #2
orr r5, lr, r5, lsr #2
orr r6, lr, r6, lsr #2
orr r7, lr, r7, lsr #2
orr ip, lr, ip, lsr #2
mov r0, #0
stmia r0, {r1 - r7, ip}
ldmfd sp!, {r4 - r7, pc}^
可以看出這裡的跳轉用的是絕對地址,或許這個cpu 的尋址範圍在32M內,這裡entry-armo.s中的O大概是指old吧.
在linux系統中類似的直接'玩弄'機器碼的地方並不少見,尤其以啟動和關機代碼為甚.
{
這裡有一個小問題:
LOADREGS的定義
#ifdef __STDC__
#define LOADREGS(cond, base, reglist...)/
ldm##cond base,reglist
#else
#define LOADREGS(cond, base, reglist...)/
ldm/**/cond base,reglist
#endif
這兩種形式有什麼區別?__STDC__是什麼意思?stdcall?
}
現在假設在usr mode發生了一個irq,arm硬件將保存當前pc(這句話不準確,應該是pc+4,但總之返回地址pc可以被確定)到lr_irq,和當前狀態寄存器cpsr到spsr_irq,經0x00處的異常向量跳躍至vector_IRQ處的處理程序:
vector_IRQ: @
@ save mode specific registers
@
ldr r13, .LCsirq
sub lr, lr, #4
str lr, [r13] @ save lr_IRQ
mrs lr, spsr
str lr, [r13, #4] @ save spsr_IRQ
@
@ now branch to the relevent MODE handling routine
@
mov r13, #I_BIT | MODE_SVC
msr spsr_c, r13 @ switch to SVC_32 mode
and lr, lr, #15
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ Changes mode and branches
.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32)
.word __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.word __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.word __irq_svc @ 3 (SVC_26 / SVC_32)
.word __irq_invalid @ 4
.word __irq_invalid @ 5
.word __irq_invalid @ 6
.word __irq_invalid @ 7
.word __irq_invalid @ 8
.word __irq_invalid @ 9
.word __irq_invalid @ a
.word __irq_invalid @ b
.word __irq_invalid @ c
.word __irq_invalid @ d
.word __irq_invalid @ e
.word __irq_invalid @ f
這段程序在前面已經被copy到viraddr 0x200後面的地方.這段代碼運行時cpu為irq mode,最後ldr pc,lr一句根據cpu模式跳轉入__irq_usr的同時還改變了cpu模式到svc mode,所以先臨時保存影子寄存器ir_IRQ和spsr_IRQ,也就是pc_usr和cpsr_usr.
可以看出,除了usr和svc mode外,在其他運行模式下不允許發生irq.
接下來在__irq_usr:
__irq_usr: sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ save r0 - r12
ldr r4, .LCirq
add r8, sp, #S_PC
ldmia r4, {r5 - r7} @ get saved PC, SPSR
stmia r8, {r5 - r7} @ save pc, psr, old_r0
stmdb r8, {sp, lr}^
alignment_trap r4, r7, __temp_irq
zero_fp
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
adrsvc ne, lr, 1b
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
bne do_IRQ
mov r4, #0
get_current_task r5
b ret_with_reschedule
arm cpu中真正的硬件中斷只有一個,不同的中斷靠中斷積存器的狀態位來區分,這裡get_irqnr_and_base的作用就是從0~31 bit依次檢查有哪一位被置一,也就是提取中斷號到r0.r0與sp隨後作為參數被傳遞給do_IRQ(),其中sp已經被壓入了中斷發生現場的各個register的值(這就是所謂的中斷上下文),也就是pt_reg結構:
struct pt_regs {
long uregs[17];
};
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[16] -----除了SWI調用,這玩藝兒一直是-1.
值得注意一下的是adrsvc ne, lr, 1b一句,通過設置do_IRQ()的返回地址lr,實現了對中斷的串行處理.當然,這樣做也是很自然的事.
接下來進入do_IRQ() (linux/arch/arm/kernel/irq.c),這個函數的實現與i386的實現十分相似.不過i386傳入中斷號是用的pt_reg結構中的org_eax,而arm裡除了SWI調用,這玩藝兒一直是-1.
asmlinkage void do_IRQ(int irq, struct pt_regs * regs)
{
struct irqdesc * desc;
struct irqaction * action;
int cpu;
irq = fixup_irq(irq);//null function
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
goto bad_irq;
desc = irq_desc + irq;
spin_lock(&irq_controller_lock);
desc->mask_ack(irq);
//mask_ack的函數原型在irq.h中定義,這裡假設用sa1100_mask_and_ack_GPIO0_10_irq,也就是gpio0~11中的一個發出的中斷,具體就假設是gpio1好了
spin_unlock(&irq_controller_lock);
cpu = smp_processor_id();
irq_enter(cpu, irq);
kstat.irqs[cpu][irq]++;
desc->triggered = 1;
/* Return with this interrupt masked if no action */
action = desc->action;//這個結構由driver通過request_irq()掛入,包括了具體的中斷處理程序入口和flags.一個中斷的irq_desc下面可能會掛幾個action(一個action隊列)來實現中斷的復用。也就是說幾個driver可以公用一個中斷號。
if (action) {
int status = 0;
if (desc->nomask) {//nomask置位的話,如果下面__sti把中斷打開,進行中斷處理時,gpio1再次發出中斷,那麼將造成gpio1中斷重入.
//若nomask不置位,則僅將中斷狀態寄存器icip中gpio1的相應位置1,而不會重入.
//nomask位由request_irq()->setup_arm_irq()根據action隊列的第一個成員的flags設置:
//irq_desc[irq].nomask = (new->flags & SA_IRQNOMASK) ? 1 : 0;
spin_lock(&irq_controller_lock);
desc->unmask(irq);//打開中斷使能寄存器icmr的相應位.並設置grer和gfer.
//GRER = (GRER & ~(1 << irq)) | (GPIO_IRQ_rising_edge & (1 << irq));
//GFER = (GFER & ~(1 << irq)) | (GPIO_IRQ_falling_edge & (1 << irq));
//ICMR |= (1 << irq);
spin_unlock(&irq_controller_lock);
}
程序從0x00處開始執行到現在,都是在關中斷的狀態下進行的。
if (!(action->flags & SA_INTERRUPT))
__sti();//清除cpsr的I_bit,開中斷。
通常如果中斷處理程序較長,為避免丟失其他中斷,會把flags的SA_INTERRUPT位清零,允許中斷嵌套。
如果在上面的nomask處判斷後,沒有執行unmask動作,那麼這裡的__sti只是允許不同中斷通道(即icip上不同的位)上的嵌套。假設現在gpio2上又發生了一次中斷。因為現在cpu處在svc mode,所以經過0x00處的異常向量->vector_IRQ->__irq_svc (entry-armv.s):
__irq_svc: sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ save r0 - r12
ldr r7, .LCirq
add r5, sp, #S_FRAME_SIZE @或許應該注意一下這句話。
ldmia r7, {r7 - r9}
add r4, sp, #S_SP
mov r6, lr
stmia r4, {r5, r6, r7, r8, r9} @ save sp_SVC, lr_SVC, pc, cpsr, old_ro
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrsvc ne, lr, 1b
bne do_IRQ
ldr r0, [sp, #S_PSR] @ irqs are already disabled
msr spsr, r0
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
執行完gpio2的中斷服務程序後,程序返回gpio1的服務程序的do_IRQ()中繼續下行.
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
值得注意的是:整個action隊列都會被調用,所以在driver裡要判定是否是屬於自己的中斷。
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli();
if (!desc->nomask && desc->enabled) {
可以用disable_irq()把desc->enabled=0,這樣在這裡icmr的相應位不會被恢復,也就是繼續禁止該中斷,相應的可以用enable_irq()打開中斷。
spin_lock(&irq_controller_lock);
desc->unmask(irq);//看起來,可以在中斷處理中通過設置GPIO_IRQ_rising_edge和GPIO_IRQ_falling_edge來控制下一次中斷由上升沿或下降沿觸發.
spin_unlock(&irq_controller_lock);
}
}
/*
* Debug measure - hopefully we can continue if an
* IRQ lockup problem occurs...
*/
check_irq_lock(desc, irq, regs);
irq_exit(cpu, irq);
if (softirq_active(cpu) & softirq_mask(cpu))
do_softirq();
return;
bad_irq:
irq_err_count += 1;
printk(KERN_ERR "IRQ: spurious interrupt %d/n", irq);
return;
}
從do_IRQ()中返回後,再用get_irqnr_and_base檢查是否發生了新的中斷而沒有處理,否則在get_current_task宏中通過sp得到task_struct的地址,跳入ret_with_reschedule:
ldr r0, [r5, #TSK_NEED_RESCHED]
ldr r1, [r5, #TSK_SIGPENDING]
teq r0, #0
bne ret_reschedule
teq r1, #0 @ check for signals
blne ret_signal
ret_from_all: restore_user_regs @ internal
ret_signal: mov r1, sp @ internal
mov r2, r4
b SYMBOL_NAME(do_signal) @ note the bl above sets lr
這個就不說了,跟進去我非死機不可^-^。
總體感覺,do_IRQ()中有一些值得商榷的地方.主要是中斷重入的問題.
Arm cpu的7個mode,linux還是只用了usr和svc兩個,其他的mode都是臨時擺擺架子.