baremetal GPIO中斷

lethe1203發表於2024-03-17
本節參考資料:https://www.bilibili.com/video/BV1yE411h7uQ?p=30&vd_source=432ba293ecfc949a4174ab91ccc526d6
本文主要用來學習GPIO中斷原理,不深入理解GIC控制器、CP15協處理器等大模組
Cortex-A7中斷系統使用中斷控制器為GIC-V2

GIC-V2控制器

GIC-V2的基本框圖如下:
0
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 處理器中的控制暫存器,用於配置和控制處理器的一些基本特性。這個暫存器通常被用來控制快取、頁表和系統控制等功能。具體的功能包括:
  1. 控制和配置快取:c0 暫存器中的位可以用來控制處理器的 L1 快取和 L2 快取,例如啟用或禁用快取、配置快取的大小、設定快取的工作模式等。
  2. 系統控制:c0 暫存器中的部分位用來控制處理器的一些基本系統特性,例如設定處理器的工作模式(使用者模式、特權模式等)、配置異常向量表的基地址、設定處理器的端序模式等。
  3. 控制頁表和記憶體管理: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--;    /* 中斷執行完成,中斷巢狀暫存器減一 */

}

相關文章