Linux Kernel 5.14 arm64异常向量表解读-中断处理解读
★★★ 個人博客導讀首頁—點擊此處 ★★★
.
說明:
在默認情況下,本文講述的都是ARMV8-aarch64架構,linux kernel 5.14
文章目錄
- 1、armv8-aarch64的異常向量表介紹
- 2、armv8的VBAR_ELx寄存器
- 3、Linux Kernel arm64中斷向量表的定義
- 4、Linux Kernel arm64設置中斷向量表的基地址
- 5、kernel_ventry宏的介紹
- 6、未實現的異常向量: elx_yyy_invalid
- 7、el1_irq的介紹 - 跳轉到注冊的handler函數
- 7、handle_domain_irq
- 8、關于中斷級聯的介紹
1、armv8-aarch64的異常向量表介紹
我們可以看出,實際上有四組表,每組表有四個異常入口,分別對應同步異常,IRQ,FIQ和serror。
- 如果發生異常后并沒有exception level切換,并且發生異常之前使用的棧指針是SP_EL0,那么使用第一組異常向量表。
- 如果發生異常后并沒有exception level切換,并且發生異常之前使用的棧指針是SP_EL1/2/3,那么使用第二組異常向量表。
- 如果發生異常導致了exception level切換,并且發生異常之前的exception
level運行在AARCH64模式,那么使用第三組異常向量表。 - 如果發生異常導致了exception level切換,并且發生異常之前的exception
level運行在AARCH32模式,那么使用第四組異常向量表。
另外我們還可以看到的一點是,每一個異常入口不再僅僅占用4bytes的空間,而是占用0x80 bytes空間,也就是說,每一個異常入口可以放置多條指令,而不僅僅是一條跳轉指令
2、armv8的VBAR_ELx寄存器
armv8定義了VBAR_EL1、VBAR_EL2、VBAR_EL3三個基地址寄存器
思考:
1、VBAR_EL1、VBAR_EL2、VBAR_EL3寫入的基地址,是物理地址還是虛擬地址?
2、基地址不再放0x00000000的位置嗎?
3、異常向量表中,沒有reset offset了?
4、異常向量表中的每一個offset為啥是0x80(128)地址空間? 以前是多少?
5、VBAR_ELx中,為啥末尾11個bit是reserved?
3、Linux Kernel arm64中斷向量表的定義
(linux/arch/arm64/kernel/entry.S)/** Exception vectors.*/.pushsection ".entry.text", "ax".align 11 SYM_CODE_START(vectors)kernel_ventry 1, sync_invalid // Synchronous EL1tkernel_ventry 1, irq_invalid // IRQ EL1tkernel_ventry 1, fiq_invalid // FIQ EL1tkernel_ventry 1, error_invalid // Error EL1tkernel_ventry 1, sync // Synchronous EL1hkernel_ventry 1, irq // IRQ EL1hkernel_ventry 1, fiq // FIQ EL1hkernel_ventry 1, error // Error EL1hkernel_ventry 0, sync // Synchronous 64-bit EL0kernel_ventry 0, irq // IRQ 64-bit EL0kernel_ventry 0, fiq // FIQ 64-bit EL0kernel_ventry 0, error // Error 64-bit EL0#ifdef CONFIG_COMPATkernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0kernel_ventry 0, fiq_compat, 32 // FIQ 32-bit EL0kernel_ventry 0, error_compat, 32 // Error 32-bit EL0 #elsekernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0 #endif SYM_CODE_END(vectors)思考:
1、這里有沒有按照armv8定義的異常向量表排列?不是每一個offset只有128bytes地址空間嗎,如何做到的?
2、Linux Kernel arm64體系中不是沒有實現FIQ嗎,這里為何實現了?
3、第一組異常向量為何沒有實現?
4、Linux Kernel arm64設置中斷向量表的基地址
(linux/arch/arm64/kernel/head.S)SYM_FUNC_START_LOCAL(__primary_switched)adrp x4, init_thread_unionadd sp, x4, #THREAD_SIZEadr_l x5, init_taskmsr sp_el0, x5 // Save thread_infoadr_l x8, vectors // load VBAR_EL1 with virtualmsr vbar_el1, x8 // vector table addressisb......b start_kernel SYM_FUNC_END(__primary_switched)思考:
1、設置VBAR_EL1,如果系統系統里有8個ARM Core,那么8個Core都需要設置嗎,分別如何設置的?
5、kernel_ventry宏的介紹
(linux/arch/arm64/kernel/entry.S).macro kernel_ventry, el, label, regsize = 64.align 7 #ifdef CONFIG_UNMAP_KERNEL_AT_EL0.if \el == 0 alternative_if ARM64_UNMAP_KERNEL_AT_EL0.if \regsize == 64mrs x30, tpidrro_el0msr tpidrro_el0, xzr.elsemov x30, xzr.endif alternative_else_nop_endif.endif #endifsub sp, sp, #PT_REGS_SIZE #ifdef CONFIG_VMAP_STACK/** Test whether the SP has overflowed, without corrupting a GPR.* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)* should always be zero.*/add sp, sp, x0 // sp' = sp + x0sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sptbnz x0, #THREAD_SHIFT, 0fsub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = spb el\()\el\()_\label0:/** Either we've just detected an overflow, or we've taken an exception* while on the overflow stack. Either way, we won't return to* userspace, and can clobber EL0 registers to free up GPRs.*//* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */msr tpidr_el0, x0/* Recover the original x0 value and stash it in tpidrro_el0 */sub x0, sp, x0msr tpidrro_el0, x0/* Switch to the overflow stack */adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0/** Check whether we were already on the overflow stack. This may happen* after panic() re-enables interrupts.*/mrs x0, tpidr_el0 // sp of interrupted contextsub x0, sp, x0 // delta with top of overflow stacktst x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?b.ne __bad_stack // no? -> bad stack pointer/* We were already on the overflow stack. Restore sp/x0 and carry on. */sub sp, sp, x0mrs x0, tpidrro_el0 #endifb el\()\el\()_\label.endm注意.align=7,說明該段代碼是以2^7=128字節對其的,這和向量表中每一個offset的大小是一致的
代碼看似非常復雜,其實最終跳轉到了b el\()\el\()_\label, 翻譯一下,其實就是跳轉到了如下這樣的函數中
6、未實現的異常向量: elx_yyy_invalid
未實現的向量定義為了elx_yyy_invalid函數, 該invalid函數其實也是一種實現,它最終調用了panic函數
例如el1_irq_invalid的Flow : el1_irq_invalid --> bl bad_mode --> panic(“bad mode”)
7、el1_irq的介紹 - 跳轉到注冊的handler函數
拋開事務看本質,el1_interrupt_handler handle_arch_irq其實就是調用handle_arch_irq, 而handle_arch_irq指向irq-gic-v3.c中定義的handler函數
.align 6 SYM_CODE_START_LOCAL_NOALIGN(el1_irq)kernel_entry 1el1_interrupt_handler handle_arch_irqkernel_exit 1 SYM_CODE_END(el1_irq)這里我們就不再深究kernel_entry和kernel_exit,它倆里面干得事情非常多。當前我們需要了解,一個是保存general purpose寄存器,一個是恢復就可以了。
.macro kernel_entry, el, regsize = 64 .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] ...... .macro kernel_exit, el ...... msr elr_el1, x21 // set up the return data msr spsr_el1, x22 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr lr, [sp, #S_LR] add sp, sp, #PT_REGS_SIZE // restore sp ......
我們再來剖析gic_handle_irq()函數,其實就是涉及gic的讀寫了,從gic中讀取硬件中斷號,然后調用handle_domain_irq函數,找到相匹配的中斷hander函數,然后回調。
另外注意一點,在Linux Kernel5.0之后,gic中的handler處理函數,發生了一些細微的變化,如下所示:
7、handle_domain_irq
補充IRQ Domain介紹
在linux kernel中,我們使用下面兩個ID來標識一個來自外設的中斷:
1、IRQ number。CPU需要為每一個外設中斷編號,我們稱之IRQ Number。這個IRQ number是一個虛擬的interrupt ID,和硬件無關,僅僅是被CPU用來標識一個外設中斷。
2、HW interrupt ID。對于interrupt controller而言,它收集了多個外設的interrupt request line并向上傳遞,因此,interrupt controller需要對外設中斷進行編碼。Interrupt controller用HW interrupt ID來標識外設的中斷。在interrupt controller級聯的情況下,僅僅用HW interrupt ID已經不能唯一標識一個外設中斷,還需要知道該HW interrupt ID所屬的interrupt controller(HW interrupt ID在不同的Interrupt controller上是會重復編碼的)。
這樣,CPU和interrupt controller在標識中斷上就有了一些不同的概念,但是,對于驅動工程師而言,我們和CPU視角是一樣的,我們只希望得到一個IRQ number,而不關系具體是那個interrupt controller上的那個HW interrupt ID。這樣一個好處是在中斷相關的硬件發生變化的時候,驅動軟件不需要修改。因此,linux kernel中的中斷子系統需要提供一個將HW interrupt ID映射到IRQ number上來的機制…
(本段轉載自:http://www.wowotech.net/linux_kenrel/irq-domain.html)
思考:
1、上文提到"在interrupt controller級聯的情況下", 為什么會有中斷級聯,一個gic控制器可以連接好幾千個中斷難道還不夠嗎?
handle_domain_irq的處理流程如下所示,最終是調用到了我們request_irq注冊的中斷處理函數.
8、關于中斷級聯的介紹
這也是我想不通的地方,一個gic控制器可以連接好幾千個中斷難道還不夠嗎? 也許是為了SOC方便設計。例如某平臺(mt6785)就使用到了級聯的方式
- interrupts : 一個計算機系統中大量設備都是通過中斷請求CPU服務的,所以設備節點就需要在指定中斷號。常用的屬性;
- interrupt-controller : 一個空屬性用來聲明這個node接收中斷,即一個node是一個中斷控制器;
- #interrupt-cells,是中斷控制器節點的屬性,用來標識這個控制器需要幾個單位做中斷描述符,用來描述子節點"interrupts"屬性使用了父節點中的interrupt屬性的具體哪個值;一般,如果父節點的該屬性的值為3,則子節點的interrupts一個cell的三個32bits的整數值分別為:<中斷域 中斷 觸發方式>,如果父節點的該屬性為2,則是<中斷 觸發方式> interrupt-parent,標識此設備節點屬于哪一個中斷控制器,如果沒有設置這個屬性,會自動依附父節點的;
注意在dts中#不是注釋的意思,#也是一個有效的字符
另外例如我們再看mobicore和utos node時,在該node沒有interrupt-parent屬性,那么認為其父節點/就是其父節點,如果在父節點下依然沒有interrupt-parent屬性,那么還是繼續再往上一級去尋找父節點。在父節點下(本示例為/)找到interrupt-parent屬性。該屬性引用的標簽為sysirq。 所以mobicore、utos中的interrupt連接的sysirq,而不是直接連的gic。
歡迎添加微信、微信群,多多交流
總結
以上是生活随笔為你收集整理的Linux Kernel 5.14 arm64异常向量表解读-中断处理解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CA/TA通信的share memory
- 下一篇: Linux Kernel 5.10 aa