into entry-armv.s

    技术2022-05-13  23

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


    最新回复(0)