日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux Kernel 5.14 arm64异常向量表解读-中断处理解读

發布時間:2025/3/21 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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, 翻譯一下,其實就是跳轉到了如下這樣的函數中

el1_sync_invalid el1_irq_invalid el1_fiq_invalid el1_error_invalidel1_sync el1_irq el1_fiq el1_error el0_sync el0_irq el0_fiq el0_error

6、未實現的異常向量: elx_yyy_invalid

未實現的向量定義為了elx_yyy_invalid函數, 該invalid函數其實也是一種實現,它最終調用了panic函數
例如el1_irq_invalid的Flow : el1_irq_invalid --> bl bad_mode --> panic(“bad mode”)

SYM_CODE_START_LOCAL(el1_irq_invalid)inv_entry 1, BAD_IRQ SYM_CODE_END(el1_irq_invalid)/** Bad Abort numbers*-----------------*/ #define BAD_SYNC 0 #define BAD_IRQ 1 #define BAD_FIQ 2 #define BAD_ERROR 3/** Invalid mode handlers*/.macro inv_entry, el, reason, regsize = 64kernel_entry \el, \regsizemov x0, spmov x1, #\reasonmrs x2, esr_el1bl bad_modeASM_BUG().endm /** bad_mode handles the impossible case in the exception vector. This is always* fatal.*/asmlinkage void notrace bad_mode(struct pt_regs *regs, int reason, unsigned int esr){arm64_enter_nmi(regs);console_verbose();pr_crit("Bad mode in %s handler detected on CPU%d, code 0x%08x -- %s\n",handler[reason], smp_processor_id(), esr,esr_get_class_string(esr));__show_regs(regs);local_daif_mask();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/drivers/irqchip/irq-gic-v3.c)static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) {u32 irqnr;irqnr = do_read_iar(regs);/* Check for special IDs first */if ((irqnr >= 1020 && irqnr <= 1023))return;if (gic_supports_nmi() &&unlikely(gic_read_rpr() == GICD_INT_NMI_PRI)) {gic_handle_nmi(irqnr, regs);return;}if (gic_prio_masking_enabled()) {gic_pmr_mask_irqs();gic_arch_enable_irqs();}if (static_branch_likely(&supports_deactivate_key))gic_write_eoir(irqnr);elseisb();if (handle_domain_irq(gic_data.domain, irqnr, regs)) {WARN_ONCE(true, "Unexpected interrupt received!\n");gic_deactivate_unhandled(irqnr);} }


另外注意一點,在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)就使用到了級聯的方式

/ {model = "MT6785";compatible = "mediatek,MT6785";interrupt-parent = <&sysirq>;#address-cells = <2>;#size-cells = <2>;gic: interrupt-controller {compatible = "arm,gic-v3";#interrupt-cells = <3>;#address-cells = <2>;#size-cells = <2>;#redistributor-regions = <1>;interrupt-parent = <&gic>;interrupt-controller;reg = <0 0x0c000000 0 0x40000>, // distributor<0 0x0c040000 0 0x200000>,// redistributor<0 0x0c53a650 0 0x50>; // INT_POLinterrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;};sysirq: intpol-controller@0 {compatible = "mediatek,mt6577-sysirq";interrupt-controller;#interrupt-cells = <3>;interrupt-parent = <&gic>;reg = <0 0x0c53a650 0 0x50>;};pio: pinctrl {compatible = "mediatek,mt6785-pinctrl";reg_bases = <&gpio>,<&iocfg_rm>,<&iocfg_br>,<&iocfg_bl>,<&iocfg_lb>,<&iocfg_rt>,<&iocfg_lt>,<&iocfg_tl>;reg_base_eint = <&eint>;pins-are-numbered;gpio-controller;gpio-ranges = <&pio 0 0 210>;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <4>;interrupts = <GIC_SPI 204 IRQ_TYPE_LEVEL_HIGH>;};/* Trustonic Mobicore SW IRQ number 121 = 32 + 89 */mobicore {compatible = "trustonic,mobicore";interrupts = <GIC_SPI 89 IRQ_TYPE_EDGE_RISING>;};/* Microtrust SW IRQ number 91(123) ~ 95(127) & 331(363) */utos {compatible = "microtrust,utos";interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,<GIC_SPI 92 IRQ_TYPE_EDGE_RISING>;};
  • 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异常向量表解读-中断处理解读的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。