編譯開關
系列篇編譯平臺為hi3516dv300,整個工程可前往查看. 預編譯處理過程會自動生成編譯開關menuconfig.h,供編譯階段選擇編譯,可前往查看.
//.... #define LOSCFG_ARCH_ARM_VER "armv7-a" #define LOSCFG_ARCH_CPU "cortex-a7" #define LOSCFG_PLATFORM "hi3516dv300" #define LOSCFG_PLATFORM_BSP_GIC_V2 1 #define LOSCFG_PLATFORM_ROOTFS 1 #define LOSCFG_KERNEL_CPPSUPPORT 1 #define LOSCFG_HW_RANDOM_ENABLE 1 #define LOSCFG_ARCH_CORTEX_A7 1 #define LOSCFG_DRIVERS_HDF_PLATFORM_RTC 1 #define LOSCFG_DRIVERS_HDF_PLATFORM_UART 1
中斷初始化
hi3516dv300中斷控制器選擇了LOSCFG_PLATFORM_BSP_GIC_V2,對應代碼為gic_v2.cGIC(Generic Interrupt Controller)是ARM公司提供的一個通用的中斷控制器. 看這種代碼因為涉及硬件部分,需要對照ARM中斷控制器 gic_v2.pdf文檔看.可前往地址下載查看.
//硬件中斷初始化 VOID HalIrqInit(VOID) { UINT32 i; /* set externel interrupts to be level triggered, active low. */ //將外部中斷設置為電平觸發,低電平激活 for (i = 32; i < OS_HWI_MAX_NUM; i += 16) { GIC_REG_32(GICD_ICFGR(i / 16)) = 0; } /* set externel interrupts to CPU 0 */ //將外部中斷設置為CPU 0 for (i = 32; i < OS_HWI_MAX_NUM; i += 4) { GIC_REG_32(GICD_ITARGETSR(i / 4)) = 0x01010101; } /* set priority on all interrupts */ //設置所有中斷的優先級 for (i = 0; i < OS_HWI_MAX_NUM; i += 4) { GIC_REG_32(GICD_IPRIORITYR(i / 4)) = GICD_INT_DEF_PRI_X4; } /* disable all interrupts. */ //禁用所有中斷。 for (i = 0; i < OS_HWI_MAX_NUM; i += 32) { GIC_REG_32(GICD_ICENABLER(i / 32)) = ~0; } HalIrqInitPercpu();//初始化當前CPU中斷信息 /* enable gic distributor control */ GIC_REG_32(GICD_CTLR) = 1; //使能分發中斷寄存器,該寄存器作用是允許給CPU發送中斷信號 #if (LOSCFG_KERNEL_SMP == YES) /* register inter-processor interrupt *///注冊核間中斷,啥意思?就是CPU各核直接可以發送中斷信號 //處理器間中斷允許一個CPU向系統其他的CPU發送中斷信號,處理器間中斷(IPI)不是通過IRQ線傳輸的,而是作為信號直接放在連接所有CPU本地APIC的總線上。 LOS_HwiCreate(LOS_MP_IPI_WAKEUP, 0xa0, 0, OsMpWakeHandler, 0);//注冊喚醒CPU的中斷處理函數 LOS_HwiCreate(LOS_MP_IPI_SCHEDULE, 0xa0, 0, OsMpScheduleHandler, 0);//注冊調度CPU的中斷處理函數 LOS_HwiCreate(LOS_MP_IPI_HALT, 0xa0, 0, OsMpScheduleHandler, 0);//注冊停止CPU的中斷處理函數 #endif } //給每個CPU core初始化硬件中斷 VOID HalIrqInitPercpu(VOID) { /* unmask interrupts */ //取消中斷屏蔽 GIC_REG_32(GICC_PMR) = 0xFF; /* enable gic cpu interface */ //啟用gic cpu接口 GIC_REG_32(GICC_CTLR) = 1; }
解讀
上來四個循環,是對中斷控制器寄存器組的初始化,也就是驅動程序,驅動程序是配置硬件寄存器的過程.寄存器分通用和專用寄存器.下圖為 gic_v2 的寄存器功能 ,這里對照代碼和datasheet重點說下中斷配置寄存器(GICD_ICFGRn)
以下是GICD_ICFGRn的介紹
The GICD_ICFGRs provide a 2-bit Int_config field for each interrupt supported by the GIC. This field identifies whether the corresponding interrupt is edge-triggered or level-sensitive
GICD_ICFGRs為GIC支持的每個中斷提供一個2位配置字段。此字段標識相應的中斷是邊緣觸發的還是電平觸發的
0xC00 - 0xCFC GICD_ICFGRn RW IMPLEMENTATION DEFINED Interrupt Configuration Registers #define GICD_ICFGR(n) (GICD_OFFSET + 0xc00 + (n) * 4) /* Interrupt Configuration Registers */ //中斷配置寄存器
如此一個32位寄存器可以記錄16個中斷的信息,這也是代碼中出現GIC_REG_32(GICD_ICFGR(i / 16))的原因.
GIC-v2支持三種類型的中斷
PPI:私有外設中斷(Private Peripheral Interrupt),是每個CPU私有的中斷。最多支持16個PPI中斷,硬件中斷號從ID16~ID31。PPI通常會送達到指定的CPU上,應用場景有CPU本地時鐘。
SPI:公用外設中斷(Shared Peripheral Interrupt),最多可以支持988個外設中斷,硬件中斷號從ID32~ID1019。
SGI:軟件觸發中斷(Software Generated Interrupt)通常用于多核間通訊,最多支持16個SGI中斷,硬件中斷號從ID0~ID15。SGI通常在內核中被用作 IPI 中斷(inter-processor interrupts),并會送達到系統指定的CPU上,函數的最后就注冊了三個核間中斷的函數.
typedef enum {//核間中斷 LOS_MP_IPI_WAKEUP, //喚醒CPU LOS_MP_IPI_SCHEDULE,//調度CPU LOS_MP_IPI_HALT, //停止CPU } MP_IPI_TYPE;
中斷相關的結構體
size_t g_intCount[LOSCFG_KERNEL_CORE_NUM] = {0};//記錄每個CPUcore的中斷數量 HwiHandleForm g_hwiForm[OS_HWI_MAX_NUM];//中斷注冊表 @note_why 用 form 來表示?有種寫 HTML的感覺 :P STATIC CHAR *g_hwiFormName[OS_HWI_MAX_NUM] = {0};//記錄每個硬中斷的名稱 STATIC UINT32 g_hwiFormCnt[OS_HWI_MAX_NUM] = {0};//記錄每個硬中斷的總數量 STATIC UINT32 g_curIrqNum = 0; //記錄當前中斷號 typedef VOID (*HWI_PROC_FUNC)(VOID); //中斷函數指針 typedef struct tagHwiHandleForm { HWI_PROC_FUNC pfnHook; //中斷處理函數 HWI_ARG_T uwParam; //中斷處理函數參數 struct tagHwiHandleForm *pstNext; //節點,指向下一個中斷,用于共享中斷的情況 } HwiHandleForm; typedef struct tagIrqParam { //中斷參數 int swIrq; // 軟件中斷 VOID *pDevId; // 設備ID const CHAR *pName; //名稱 } HwiIrqParam;
注冊硬中斷
/****************************************************************************** 創建一個硬中斷 中斷創建,注冊中斷號、中斷觸發模式、中斷優先級、中斷處理程序。中斷被觸發時, handleIrq會調用該中斷處理程序 ******************************************************************************/ LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum, //硬中斷句柄編號 默認范圍[0-127] HWI_PRIOR_T hwiPrio, //硬中斷優先級 HWI_MODE_T hwiMode, //硬中斷模式 共享和非共享 HWI_PROC_FUNC hwiHandler,//硬中斷處理函數 HwiIrqParam *irqParam) //硬中斷處理函數參數 { UINT32 ret; (VOID)hwiPrio; if (hwiHandler == NULL) {//中斷處理函數不能為NULL return OS_ERRNO_HWI_PROC_FUNC_NULL; } if ((hwiNum > OS_USER_HWI_MAX) || ((INT32)hwiNum < OS_USER_HWI_MIN)) {//中斷數區間限制 [32,96] return OS_ERRNO_HWI_NUM_INVALID; } #ifdef LOSCFG_NO_SHARED_IRQ //不支持共享中斷 ret = OsHwiCreateNoShared(hwiNum, hwiMode, hwiHandler, irqParam); #else ret = OsHwiCreateShared(hwiNum, hwiMode, hwiHandler, irqParam); #endif return ret; } //創建一個共享硬件中斷,共享中斷就是一個中斷能觸發多個響應函數 STATIC UINT32 OsHwiCreateShared(HWI_HANDLE_T hwiNum, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler, const HwiIrqParam *irqParam) { UINT32 intSave; HwiHandleForm *hwiFormNode = NULL; HwiHandleForm *hwiForm = NULL; HwiIrqParam *hwiParam = NULL; HWI_MODE_T modeResult = hwiMode & IRQF_SHARED; if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) { return OS_ERRNO_HWI_SHARED_ERROR; } HWI_LOCK(intSave);//中斷自旋鎖 hwiForm = &g_hwiForm[hwiNum];//獲取中斷處理項 if ((hwiForm->pstNext != NULL) && ((modeResult == 0) || (!(hwiForm->uwParam & IRQF_SHARED)))) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_SHARED_ERROR; } while (hwiForm->pstNext != NULL) {//pstNext指向 共享中斷的各處理函數節點,此處一直擼到最后一個 hwiForm = hwiForm->pstNext;//找下一個中斷 hwiParam = (HwiIrqParam *)(hwiForm->uwParam);//獲取中斷參數,用于檢測該設備ID是否已經有中斷處理函數 if (hwiParam->pDevId == irqParam->pDevId) {//設備ID一致時,說明設備對應的中斷處理函數已經存在了. HWI_UNLOCK(intSave); return OS_ERRNO_HWI_ALREADY_CREATED; } } hwiFormNode = (HwiHandleForm *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleForm));//創建一個中斷處理節點 if (hwiFormNode == NULL) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_NO_MEMORY; } hwiFormNode->uwParam = OsHwiCpIrqParam(irqParam);//獲取中斷處理函數的參數 if (hwiFormNode->uwParam == LOS_NOK) { HWI_UNLOCK(intSave); (VOID)LOS_MemFree(m_aucSysMem0, hwiFormNode); return OS_ERRNO_HWI_NO_MEMORY; } hwiFormNode->pfnHook = hwiHandler;//綁定中斷處理函數 hwiFormNode->pstNext = (struct tagHwiHandleForm *)NULL;//指定下一個中斷為NULL,用于后續遍歷找到最后一個中斷項(見于以上 while (hwiForm->pstNext != NULL)處) hwiForm->pstNext = hwiFormNode;//共享中斷 if ((irqParam != NULL) && (irqParam->pName != NULL)) { g_hwiFormName[hwiNum] = (CHAR *)irqParam->pName; } g_hwiForm[hwiNum].uwParam = modeResult; HWI_UNLOCK(intSave); return LOS_OK; }
解讀
內核將硬中斷進行編號,如:
#define NUM_HAL_INTERRUPT_TIMER0 33 #define NUM_HAL_INTERRUPT_TIMER1 33 #define NUM_HAL_INTERRUPT_TIMER2 34 #define NUM_HAL_INTERRUPT_TIMER3 34 #define NUM_HAL_INTERRUPT_TIMER4 35 #define NUM_HAL_INTERRUPT_TIMER5 35 #define NUM_HAL_INTERRUPT_TIMER6 36 #define NUM_HAL_INTERRUPT_TIMER7 36 #define NUM_HAL_INTERRUPT_DMAC 60 #define NUM_HAL_INTERRUPT_UART0 38 #define NUM_HAL_INTERRUPT_UART1 39 #define NUM_HAL_INTERRUPT_UART2 40 #define NUM_HAL_INTERRUPT_UART3 41 #define NUM_HAL_INTERRUPT_UART4 42 #define NUM_HAL_INTERRUPT_TIMER NUM_HAL_INTERRUPT_TIMER4例如:時鐘節拍處理函數OsTickHandler就是在HalClockInit中注冊的
//硬時鐘初始化 VOID HalClockInit(VOID) { // ... (void)LOS_HwiCreate(NUM_HAL_INTERRUPT_TIMER, 0xa0, 0, OsTickHandler, 0);//注冊OsTickHandler到中斷向量表 } //節拍中斷處理函數 ,鴻蒙默認10ms觸發一次 LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { UINT32 intSave; TICK_LOCK(intSave);//tick自旋鎖 g_tickCount[ArchCurrCpuid()]++;// 累加當前CPU核tick數 TICK_UNLOCK(intSave); OsTimesliceCheck();//時間片檢查 OsTaskScan(); /* task timeout scan *///掃描超時任務 例如:delay(300) #if (LOSCFG_BASE_CORE_SWTMR == YES) OsSwtmrScan();//掃描定時器,查看是否有超時定時器,加入隊列 #endif }
鴻蒙是支持中斷共享的,在OsHwiCreateShared中,將函數注冊到g_hwiForm中.中斷向量完成注冊后,就是如何觸發和回調的問題.觸發在鴻蒙內核源碼分析(總目錄)中斷切換篇中已經講清楚,觸發是從底層匯編向上調用,調用的C函數就是HalIrqHandler
中斷怎么觸發的?
分兩種情況:
通過硬件觸發,比如按鍵,USB的插拔這些中斷源向中斷控制器發送電信號(高低電平觸發或是上升/下降沿觸發),中斷控制器經過過濾后將信號發給對應的CPU處理,通過硬件改變PC和CPSR寄存值,直接跳轉到中斷向量(固定地址)執行.
b reset_vector @開機代碼 b _osExceptUndefInstrHdl @異常處理之CPU碰到不認識的指令 b _osExceptSwiHdl @異常處理之:軟中斷 b _osExceptPrefetchAbortHdl @異常處理之:取指異常 b _osExceptDataAbortHdl @異常處理之:數據異常 b _osExceptAddrAbortHdl @異常處理之:地址異常 b OsIrqHandler @異常處理之:硬中斷 b _osExceptFiqHdl @異常處理之:快中斷
通過軟件觸發,常見于核間中斷的情況, 核間中斷指的是幾個CPU之間相互通訊的過程.以下為某一個CPU向其他CPU(可以是多個)發起讓這些CPU重新調度LOS_MpSchedule的中斷請求信號.最終是寫了中斷控制器的GICD_SGIR寄存器,這是一個由軟件觸發中斷的寄存器.中斷控制器會將請求分發給對應的CPU處理中斷,即觸發了OsIrqHandler.
//給參數CPU發送調度信號 VOID LOS_MpSchedule(UINT32 target)//target每位對應CPU core { UINT32 cpuid = ArchCurrCpuid(); target &= ~(1U << cpuid);//獲取除了自身之外的其他CPU HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向目標CPU發送調度信號,核間中斷(Inter-Processor Interrupts),IPI } //SGI軟件觸發中斷(Software Generated Interrupt)通常用于多核間通訊 STATIC VOID GicWriteSgi(UINT32 vector, UINT32 cpuMask, UINT32 filter) { UINT32 val = ((filter & 0x3) << 24) | ((cpuMask & 0xFF) << 16) | (vector & 0xF); GIC_REG_32(GICD_SGIR) = val;//寫SGI寄存器 } //向指定核發送核間中斷 VOID HalIrqSendIpi(UINT32 target, UINT32 ipi) { GicWriteSgi(ipi, target, 0); }
中斷統一處理入口函數 HalIrqHandler
//硬中斷統一處理函數,這里由硬件觸發,調用見于 ..\arch\arm\arm\src\los_dispatch.S VOID HalIrqHandler(VOID) { UINT32 iar = GIC_REG_32(GICC_IAR);//從中斷確認寄存器獲取中斷ID號 UINT32 vector = iar & 0x3FFU;//計算中斷向量號 /* * invalid irq number, mainly the spurious interrupts 0x3ff, * gicv2 valid irq ranges from 0~1019, we use OS_HWI_MAX_NUM * to do the checking. */ if (vector >= OS_HWI_MAX_NUM) { return; } g_curIrqNum = vector;//記錄當前中斷ID號 OsInterrupt(vector);//調用上層中斷處理函數 /* use orignal iar to do the EOI */ GIC_REG_32(GICC_EOIR) = iar;//更新中斷結束寄存器 } VOID OsInterrupt(UINT32 intNum)//中斷實際處理函數 { HwiHandleForm *hwiForm = NULL; UINT32 *intCnt = NULL; intCnt = &g_intCount[ArchCurrCpuid()];//當前CPU的中斷總數量 ++ *intCnt = *intCnt + 1;//@note_why 這里沒看明白為什么要 +1 #ifdef LOSCFG_CPUP_INCLUDE_IRQ //開啟查詢系統CPU的占用率的中斷 OsCpupIrqStart();//記錄本次中斷處理開始時間 #endif #ifdef LOSCFG_KERNEL_TICKLESS OsTicklessUpdate(intNum); #endif hwiForm = (&g_hwiForm[intNum]);//獲取對應中斷的實體 #ifndef LOSCFG_NO_SHARED_IRQ //如果沒有定義不共享中斷 ,意思就是如果是共享中斷 while (hwiForm->pstNext != NULL) { //一直擼到最后 hwiForm = hwiForm->pstNext;//下一個繼續擼 #endif if (hwiForm->uwParam) {//有參數的情況 HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;//獲取回調函數 if (func != NULL) { UINTPTR *param = (UINTPTR *)(hwiForm->uwParam); func((INT32)(*param), (VOID *)(*(param + 1)));//運行帶參數的回調函數 } } else {//木有參數的情況 HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;//獲取回調函數 if (func != NULL) { func();//運行回調函數 } } #ifndef LOSCFG_NO_SHARED_IRQ } #endif ++g_hwiFormCnt[intNum];//中斷號計數器總數累加 *intCnt = *intCnt - 1; //@note_why 這里沒看明白為什么要 -1 #ifdef LOSCFG_CPUP_INCLUDE_IRQ //開啟查詢系統CPU的占用率的中斷 OsCpupIrqEnd(intNum);//記錄中斷處理時間完成時間 #endif } 解讀統一中斷處理函數是一個通過一個中斷號去找到注冊函數的過程,分四步走:
第一步:取號,這號是由中斷控制器的GICC_IAR寄存器提供的,這是一個專門保存當前中斷號的寄存器.
第二步:從注冊表g_hwiForm中查詢注冊函數,同時取出參數.
第三步:執行函數,也就是回調注冊函數,分有參和無參兩種情況func(...),在中斷共享的情況下,注冊函數會指向下一個注冊函數pstNext,依次執行回調函數,這是中斷共享的實現細節.
typedef struct tagHwiHandleForm { HWI_PROC_FUNC pfnHook; //中斷處理函數 HWI_ARG_T uwParam; //中斷處理函數參數 struct tagHwiHandleForm *pstNext; //節點,指向下一個中斷,用于共享中斷的情況 } HwiHandleForm;
第四步:銷號,本次中斷完成了就需要消除記錄,中斷控制器也有專門的銷號寄存器GICC_EOIR
另外的是一些統一數據,每次中斷號處理內核都會記錄次數,和耗時,以便定位/跟蹤/診斷問題.
編輯:hfy
-
寄存器
+關注
關注
31文章
5390瀏覽量
121901 -
中斷控制器
+關注
關注
0文章
59瀏覽量
9520 -
鴻蒙系統
+關注
關注
183文章
2638瀏覽量
67054
發布評論請先 登錄
相關推薦
評論