本節參考資料:https://www.bilibili.com/video/BV1yE411h7uQ?p=30&vd_source=432ba293ecfc949a4174ab91ccc526d6
本文主要用來學習GPIO中斷原理,不深入理解GIC控制器、CP15協處理器等大模組
Cortex-A7中斷系統使用中斷控制器為GIC-V2
GIC-V2控制器
GIC-V2的基本框圖如下:
GIC最多支援8個處理器(processor0~ processor7)。不同處理器的GIC功能是相同的,只看其中一個即可。 GIC主要分為分發器(Distributor)和CPU介面(CPU interface/Virtual CPU interface)。
分發器:
分發器用於管理CPU所有中斷源,確定每個中斷的優先順序,管理中斷的遮蔽和中斷搶佔。最終將優先順序最高的中斷轉發到一個或者多個CPU介面。CPU的中斷源分為三類,如下:
SGIs(software generated interrupts):最大 16 個軟體可產生的中斷,中斷號0-15,這類中斷是軟體寫分發器的軟體生成中斷暫存器GICD_SGIR產生的,通常用於實現處理器之間的中斷IPI
PPIs(private peripheral interrupts):中斷號16-31;這類中斷是每個處理器的私有元件產生的,例如定時器中斷,每個處理器都有各自私有定時器,每個私有定時器使用相同的硬體中斷號,只能給自己的處理器發中斷。
SPIs(shared peripheral interrupts):SPI中斷號32~1020,這類中斷是所有處理器共享的外設中斷,這類中斷一般是邊沿觸發或電平觸發的。
分發器提供了一些程式設計介面或者說是暫存器,可以透過分發器的程式設計介面實現如下操作。
- 全域性的開啟或關閉CPU的中斷。
- 控制任意一箇中斷請求的開啟和關閉。
- 設定每個中斷請求的中斷優先順序。
- 指定中斷髮生時將中斷請求傳送到那些CPU(i.MX 6U是單核)。
- 設定每個”外部中斷”的觸發方式(邊緣觸發或者電平觸發)。
CPU介面:
CPU介面為連結到GIC的處理器提供介面,與分發器類似它也提供了一些程式設計介面,可以透過CPU介面實現以下功能:
- 開啟或關閉向處理器傳送中斷請求.。
- 確認中斷(acknowledging an interrupt)。
- 指示中斷處理的完成。
- 為處理器設定中斷優先順序掩碼。
- 定義處理器的搶佔策略
- 確定掛起的中斷請求中優先順序最高的中斷請求。
深入理解GIC-V2可參考:https://blog.csdn.net/weixin_44821644/article/details/126006071
CP15:
這裡僅說明為啥需要CP15:
GIC介面暫存器講解部分只給出了”偏移地址”,GIC的基地址儲存在CP15協處理器中。 我們修改系統控制暫存器以及設定中斷向量表地址都會用到CP15協處理器。
協處理器 CP15 中的 c0 暫存器是 ARM 處理器中的控制暫存器,用於配置和控制處理器的一些基本特性。這個暫存器通常被用來控制快取、頁表和系統控制等功能。具體的功能包括:
- 控制和配置快取:c0 暫存器中的位可以用來控制處理器的 L1 快取和 L2 快取,例如啟用或禁用快取、配置快取的大小、設定快取的工作模式等。
- 系統控制:c0 暫存器中的部分位用來控制處理器的一些基本系統特性,例如設定處理器的工作模式(使用者模式、特權模式等)、配置異常向量表的基地址、設定處理器的端序模式等。
- 控制頁表和記憶體管理:c0 暫存器中的位也可以用來配置處理器的頁表結構、啟用或禁用 MMU(記憶體管理單元)以及設定記憶體訪問許可權等。
深入理解CP15可參考:
https://zhuanlan.zhihu.com/p/663981666
具體實現:
start.S
.global _start /* 全域性標號 */ /* * 描述: _start函式,首先是中斷向量表的建立 * 參考文件:ARM Cortex-A(armV7)程式設計手冊V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM處理器模型和暫存器) * ARM Cortex-A(armV7)程式設計手冊V4.0.pdf P165 11.1.1 Exception priorities(異常) */ _start: ldr pc, =Reset_Handler /* 復位中斷 */ ldr pc, =Undefined_Handler /* 未定義中斷 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中斷 */ ldr pc, =PrefAbort_Handler /* 預取終止中斷 */ ldr pc, =DataAbort_Handler /* 資料終止中斷 */ ldr pc, =NotUsed_Handler /* 未使用中斷 */ ldr pc, =IRQ_Handler /* IRQ中斷 */ ldr pc, =FIQ_Handler /* FIQ(快速中斷)未定義中斷 */ /* 復位中斷 */ Reset_Handler: cpsid i /* 關閉全域性中斷 */ /* 關閉I,DCache和MMU * 採取讀-改-寫的方式。 */ mrc p15, 0, r0, c1, c0, 0 /* 讀取CP15的C1暫存器到R0中 */ bic r0, r0, #(0x1 << 12) /* 清除C1暫存器的bit12位(I位),關閉I Cache */ bic r0, r0, #(0x1 << 2) /* 清除C1暫存器的bit2(C位),關閉D Cache */ bic r0, r0, #0x2 /* 清除C1暫存器的bit1(A位),關閉對齊 */ bic r0, r0, #(0x1 << 11) /* 清除C1暫存器的bit11(Z位),關閉分支預測 */ bic r0, r0, #0x1 /* 清除C1暫存器的bit0(M位),關閉MMU */ mcr p15, 0, r0, c1, c0, 0 /* 將r0暫存器中的值寫入到CP15的C1暫存器中 */ /* 設定各個模式下的棧指標, * 注意:IMX6UL的堆疊是向下增長的! * 堆疊指標地址一定要是4位元組地址對齊的!!! * DDR範圍:0X80000000~0X9FFFFFFF */ /* 進入IRQ模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 將r0暫存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */ msr cpsr, r0 /* 將r0 的資料寫入到cpsr_c中 */ ldr sp, =0x80600000 /* 設定IRQ模式下的棧首地址為0X80600000,大小為2MB */ /* 進入SYS模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 將r0暫存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */ msr cpsr, r0 /* 將r0 的資料寫入到cpsr_c中 */ ldr sp, =0x80400000 /* 設定SYS模式下的棧首地址為0X80400000,大小為2MB */ /* 進入SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 將r0暫存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */ msr cpsr, r0 /* 將r0 的資料寫入到cpsr_c中 */ ldr sp, =0X80200000 /* 設定SVC模式下的棧首地址為0X80200000,大小為2MB */ cpsie i /* 開啟全域性中斷 */ b main /* 跳轉到main函式 */ /* 未定義中斷 */ Undefined_Handler: ldr r0, =Undefined_Handler bx r0 /* SVC中斷 */ SVC_Handler: ldr r0, =SVC_Handler bx r0 /* 預取終止中斷 */ PrefAbort_Handler: ldr r0, =PrefAbort_Handler bx r0 /* 資料終止中斷 */ DataAbort_Handler: ldr r0, =DataAbort_Handler bx r0 /* 未使用的中斷 */ NotUsed_Handler: ldr r0, =NotUsed_Handler bx r0 /* IRQ中斷!重點!!!!! */ IRQ_Handler: push {lr} /* 儲存lr地址 */ push {r0-r3, r12} /* 儲存r0-r3,r12暫存器 */ mrs r0, spsr /* 讀取spsr暫存器 */ push {r0} /* 儲存spsr暫存器 */ mrc p15, 4, r1, c15, c0, 0 /* 從CP15的C0暫存器內的值到R1暫存器中 * 參考文件ARM Cortex-A(armV7)程式設計手冊V4.0.pdf P49 * Cortex-A7 Technical ReferenceManua.pdf P68 P138 */ add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU介面端基地址 */ ldr r0, [r1, #0XC] /* GIC的CPU介面端基地址加0X0C就是GICC_IAR暫存器, * GICC_IAR暫存器儲存這當前發生中斷的中斷號,我們要根據 * 這個中斷號來絕對呼叫哪個中斷服務函式 */ push {r0, r1} /* 儲存r0,r1 */ cps #0x13 /* 進入SVC模式,允許其他中斷再次進去 */ push {lr} /* 儲存SVC模式的lr暫存器 */ ldr r2, =system_irqhandler /* 載入C語言中斷處理函式到r2暫存器中*/ blx r2 /* 執行C語言中斷處理函式,帶有一個引數,儲存在R0暫存器中 */ pop {lr} /* 執行完C語言中斷服務函式,lr出棧 */ cps #0x12 /* 進入IRQ模式 */ pop {r0, r1} str r0, [r1, #0X10] /* 中斷執行完成,寫EOIR */ pop {r0} msr spsr_cxsf, r0 /* 恢復spsr */ pop {r0-r3, r12} /* r0-r3,r12出棧 */ pop {lr} /* lr出棧 */ subs pc, lr, #4 /* 將lr-4賦給pc */ /* FIQ中斷 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0
bsp_int.c
.
/* * @description : 初始化中斷服務函式表 * @param : 無 * @return : 無 */ void system_irqtable_init(void) { unsigned int i = 0; irqNesting = 0; /* 先將所有的中斷服務函式設定為預設值 */ for(i = 0; i < NUMBER_OF_INT_VECTORS; i++) { system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL); } } /* * @description : 給指定的中斷號註冊中斷服務函式 * @param - irq : 要註冊的中斷號 * @param - handler : 要註冊的中斷處理函式 * @param - usrParam : 中斷服務處理函式引數 * @return : 無 */ void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; irqTable[irq].userParam = userParam; } /* * @description : C語言中斷服務函式,irq彙編中斷服務函式會 呼叫此函式,此函式透過在中斷服務列表中查 找指定中斷號所對應的中斷處理函式並執行。 * @param - giccIar : 中斷號 * @return : 無 */ void system_irqhandler(unsigned int giccIar) { uint32_t intNum = giccIar & 0x3FFUL; /* 檢查中斷號是否符合要求 */ if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)) { return; } irqNesting++; /* 中斷巢狀計數器加一 */ /* 根據傳遞進來的中斷號,在irqTable中呼叫確定的中斷服務函式*/ irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); irqNesting--; /* 中斷執行完成,中斷巢狀暫存器減一 */ }