现在的位置: 首页 > 综合 > 正文

into entry-armv.s

2013年10月13日 ⁄ 综合 ⁄ 共 7731字 ⁄ 字号 评论关闭

原文转自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都是臨時擺擺架子. 

抱歉!评论已关闭.