鴻蒙內(nèi)核源碼分析(中斷管理篇) | 硬中斷的實現(xiàn)<>觀察者模式
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
關(guān)于中斷部分系列篇將用三篇詳細說明整個過程.
● 中斷概念篇 中斷概念很多,比如中斷控制器,中斷源,中斷向量,中斷共享,中斷處理程序等等.本篇做一次整理.先了解透概念才好理解中斷過程.用海公公打比方說明白中斷各個概念.可前往鴻蒙內(nèi)核源碼分析查看.
● 中斷管理篇(本篇) 從中斷初始化HalIrqInit開始,到注冊中斷的LOS_HwiCreate函數(shù),到消費中斷函數(shù)的 HalIrqHandler,剖析鴻蒙內(nèi)核實現(xiàn)中斷的過程,很像設(shè)計模式中的觀察者模式.
● 中斷切換篇 用自下而上的方式,從中斷源頭純匯編代碼往上跟蹤代碼細節(jié).說清楚保存和恢復(fù)中斷現(xiàn)場TaskIrqContext過程.可前往鴻蒙內(nèi)核源碼分析查看.
編譯開關(guān)
系列篇編譯平臺為 hi3516dv300,整個工程可前往查看. 預(yù)編譯處理過程會自動生成編譯開關(guān) 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 ,對應(yīng)代碼為 gic_v2.c GIC(Generic Interrupt Controller)是ARM公司提供的一個通用的中斷控制器. 看這種代碼因為涉及硬件部分,需要對照ARM中斷控制器 gic_v2.pdf文檔看.可前往地址查看.
- //硬件中斷初始化
- VOID HalIrqInit(VOID)
- {
- UINT32 i;
- /* set externel interrupts to be level triggered, active low. */ //將外部中斷設(shè)置為電平觸發(fā),低電平激活
- for (i = 32; i < OS_HWI_MAX_NUM; i += 16) {
- GIC_REG_32(GICD_ICFGR(i / 16)) = 0;
- }
- /* set externel interrupts to CPU 0 */ //將外部中斷設(shè)置為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 */ //設(shè)置所有中斷的優(yōu)先級
- 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();//初始化當(dāng)前CPU中斷信息
- /* enable gic distributor control */
- GIC_REG_32(GICD_CTLR) = 1; //使能分發(fā)中斷寄存器,該寄存器作用是允許給CPU發(fā)送中斷信號
- #if (LOSCFG_KERNEL_SMP == YES)
- /* register inter-processor interrupt *///注冊核間中斷,啥意思?就是CPU各核直接可以發(fā)送中斷信號
- //處理器間中斷允許一個CPU向系統(tǒng)其他的CPU發(fā)送中斷信號,處理器間中斷(IPI)不是通過IRQ線傳輸?shù)?,而是作為信號直接放在連接所有CPU本地APIC的總線上。
- LOS_HwiCreate(LOS_MP_IPI_WAKEUP, 0xa0, 0, OsMpWakeHandler, 0);//注冊喚醒CPU的中斷處理函數(shù)
- LOS_HwiCreate(LOS_MP_IPI_SCHEDULE, 0xa0, 0, OsMpScheduleHandler, 0);//注冊調(diào)度CPU的中斷處理函數(shù)
- LOS_HwiCreate(LOS_MP_IPI_HALT, 0xa0, 0, OsMpScheduleHandler, 0);//注冊停止CPU的中斷處理函數(shù)
- #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;
- }
解讀
● 上來四個循環(huán),是對中斷控制器寄存器組的初始化,也就是驅(qū)動程序,驅(qū)動程序是配置硬件寄存器的過程.寄存器分通用和專用寄存器.圖為 gic_v2 的寄存器功能 ,這里對照代碼和datasheet重點說下中斷配置寄存器
● 以下是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位配置字段。此字段標(biāo)識相應(yīng)的中斷是邊緣觸發(fā)的還是電平觸發(fā)的
● GIC-v2支持三種類型的中斷
◊ PPI:私有外設(shè)中斷(Private Peripheral Interrupt),是每個CPU私有的中斷。最多支持16個PPI中斷,硬件中斷號從ID16~ID31。PPI通常會送達到指定的CPU上,應(yīng)用場景有CPU本地時鐘。
◊ SPI:公用外設(shè)中斷(Shared Peripheral Interrupt),最多可以支持988個外設(shè)中斷,硬件中斷號從ID32~ID1019。
◊ SGI:軟件觸發(fā)中斷(Software Generated Interrupt)通常用于多核間通訊,最多支持16個SGI中斷,硬件中斷號從ID0~ID15。SGI通常在內(nèi)核中被用作 IPI 中斷(inter-processor interrupts),并會送達到系統(tǒng)指定的CPU上,函數(shù)的最后就注冊了三個核間中斷的函數(shù).
- typedef enum {//核間中斷
- LOS_MP_IPI_WAKEUP, //喚醒CPU
- LOS_MP_IPI_SCHEDULE,//調(diào)度CPU
- LOS_MP_IPI_HALT, //停止CPU
- } MP_IPI_TYPE;
中斷相關(guān)的結(jié)構(gòu)體
- size_t g_intCount[LOSCFG_KERNEL_CORE_NUM] = {0};//記錄每個CPUcore的中斷數(shù)量
- 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};//記錄每個硬中斷的總數(shù)量
- STATIC UINT32 g_curIrqNum = 0; //記錄當(dāng)前中斷號
- typedef VOID (*HWI_PROC_FUNC)(VOID); //中斷函數(shù)指針
- typedef struct tagHwiHandleForm {
- HWI_PROC_FUNC pfnHook; //中斷處理函數(shù)
- HWI_ARG_T uwParam; //中斷處理函數(shù)參數(shù)
- struct tagHwiHandleForm *pstNext; //節(jié)點,指向下一個中斷,用于共享中斷的情況
- } HwiHandleForm;
- typedef struct tagIrqParam { //中斷參數(shù)
- int swIrq; // 軟件中斷
- VOID *pDevId; // 設(shè)備ID
- const CHAR *pName; //名稱
- } HwiIrqParam;
注冊硬中斷
- /******************************************************************************
- 創(chuàng)建一個硬中斷
- 中斷創(chuàng)建,注冊中斷號、中斷觸發(fā)模式、中斷優(yōu)先級、中斷處理程序。中斷被觸發(fā)時,
- handleIrq會調(diào)用該中斷處理程序
- ******************************************************************************/
- LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum, //硬中斷句柄編號 默認范圍[0-127]
- HWI_PRIOR_T hwiPrio, //硬中斷優(yōu)先級
- HWI_MODE_T hwiMode, //硬中斷模式 共享和非共享
- HWI_PROC_FUNC hwiHandler,//硬中斷處理函數(shù)
- HwiIrqParam *irqParam) //硬中斷處理函數(shù)參數(shù)
- {
- UINT32 ret;
- (VOID)hwiPrio;
- if (hwiHandler == NULL) {//中斷處理函數(shù)不能為NULL
- return OS_ERRNO_HWI_PROC_FUNC_NULL;
- }
- if ((hwiNum > OS_USER_HWI_MAX) || ((INT32)hwiNum < OS_USER_HWI_MIN)) {//中斷數(shù)區(qū)間限制 [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;
- }
- //創(chuàng)建一個共享硬件中斷,共享中斷就是一個中斷能觸發(fā)多個響應(yīng)函數(shù)
- 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指向 共享中斷的各處理函數(shù)節(jié)點,此處一直擼到最后一個
- hwiForm = hwiForm->pstNext;//找下一個中斷
- hwiParam = (HwiIrqParam *)(hwiForm->uwParam);//獲取中斷參數(shù),用于檢測該設(shè)備ID是否已經(jīng)有中斷處理函數(shù)
- if (hwiParam->pDevId == irqParam->pDevId) {//設(shè)備ID一致時,說明設(shè)備對應(yīng)的中斷處理函數(shù)已經(jīng)存在了.
- HWI_UNLOCK(intSave);
- return OS_ERRNO_HWI_ALREADY_CREATED;
- }
- }
- hwiFormNode = (HwiHandleForm *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleForm));//創(chuàng)建一個中斷處理節(jié)點
- if (hwiFormNode == NULL) {
- HWI_UNLOCK(intSave);
- return OS_ERRNO_HWI_NO_MEMORY;
- }
- hwiFormNode->uwParam = OsHwiCpIrqParam(irqParam);//獲取中斷處理函數(shù)的參數(shù)
- if (hwiFormNode->uwParam == LOS_NOK) {
- HWI_UNLOCK(intSave);
- (VOID)LOS_MemFree(m_aucSysMem0, hwiFormNode);
- return OS_ERRNO_HWI_NO_MEMORY;
- }
- hwiFormNode->pfnHook = hwiHandler;//綁定中斷處理函數(shù)
- hwiFormNode->pstNext = (struct tagHwiHandleForm *)NULL;//指定下一個中斷為NULL,用于后續(xù)遍歷找到最后一個中斷項(見于以上 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;
- }
解讀
● 內(nèi)核將硬中斷進行編號,如:
- #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
例如:時鐘節(jié)拍處理函數(shù) OsTickHandler 就是在 HalClockInit中注冊的
- //硬時鐘初始化
- VOID HalClockInit(VOID)
- {
- // ...
- (void)LOS_HwiCreate(NUM_HAL_INTERRUPT_TIMER, 0xa0, 0, OsTickHandler, 0);//注冊O(shè)sTickHandler到中斷向量表
- }
- //節(jié)拍中斷處理函數(shù) ,鴻蒙默認10ms觸發(fā)一次
- LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
- {
- UINT32 intSave;
- TICK_LOCK(intSave);//tick自旋鎖
- g_tickCount[ArchCurrCpuid()]++;// 累加當(dāng)前CPU核tick數(shù)
- TICK_UNLOCK(intSave);
- OsTimesliceCheck();//時間片檢查
- OsTaskScan(); /* task timeout scan *///掃描超時任務(wù) 例如:delay(300)
- #if (LOSCFG_BASE_CORE_SWTMR == YES)
- OsSwtmrScan();//掃描定時器,查看是否有超時定時器,加入隊列
- #endif
- }
● 鴻蒙是支持中斷共享的,在OsHwiCreateShared中,將函數(shù)注冊到g_hwiForm中.中斷向量完成注冊后,就是如何觸發(fā)和回調(diào)的錯誤.觸發(fā)在 鴻蒙內(nèi)核源碼分析中斷切換篇中已經(jīng)講清楚,觸發(fā)是從底層匯編向上調(diào)用,調(diào)用的C函數(shù)就是HalIrqHandler
中斷怎么觸發(fā)的?
分兩種情況:
● 通過硬件觸發(fā),比如按鍵,USB的插拔這些中斷源向中斷控制器發(fā)送電信號(高低電平觸發(fā)或是上升/下降沿觸發(fā)),中斷控制器經(jīng)過過濾后將信號發(fā)給對應(yīng)的CPU處理,通過硬件改變PC和CPSR寄存值,直接跳轉(zhuǎn)到中斷向量(固定地址)執(zhí)行.
- b reset_vector @開機代碼
- b _osExceptUndefInstrHdl @異常處理之CPU碰到不認識的指令
- b _osExceptSwiHdl @異常處理之:軟中斷
- b _osExceptPrefetchAbortHdl @異常處理之:取指異常
- b _osExceptDataAbortHdl @異常處理之:數(shù)據(jù)異常
- b _osExceptAddrAbortHdl @異常處理之:地址異常
- b OsIrqHandler @異常處理之:硬中斷
- b _osExceptFiqHdl @異常處理之:快中斷
● 通過軟件觸發(fā),常見于核間中斷的情況, 核間中斷指的是幾個CPU之間相互通訊的過程.以下為某一個CPU向其他CPU(可以是多個)發(fā)起讓這些CPU重新調(diào)度LOS_MpSchedule的中斷請求信號.最終是寫了中斷控制器的GICD_SGIR寄存器,這是一個由軟件觸發(fā)中斷的寄存器.中斷控制器會將請求分發(fā)給對應(yīng)的CPU處理中斷,即觸發(fā)了OsIrqHandler.
- //給參數(shù)CPU發(fā)送調(diào)度信號
- VOID LOS_MpSchedule(UINT32 target)//target每位對應(yīng)CPU core
- {
- UINT32 cpuid = ArchCurrCpuid();
- target &= ~(1U << cpuid);//獲取除了自身之外的其他CPU
- HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向CPU發(fā)送調(diào)度信號,核間中斷(Inter-Processor Interrupts),IPI
- }
- //SGI軟件觸發(fā)中斷(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寄存器
- }
- //向指定核發(fā)送核間中斷
- VOID HalIrqSendIpi(UINT32 target, UINT32 ipi)
- {
- GicWriteSgi(ipi, target, 0);
- }
中斷統(tǒng)一處理入口函數(shù) HalIrqHandler
- //硬中斷統(tǒng)一處理函數(shù),這里由硬件觸發(fā),調(diào)用見于 ..\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;//記錄當(dāng)前中斷ID號
- OsInterrupt(vector);//調(diào)用上層中斷處理函數(shù)
- /* use orignal iar to do the EOI */
- GIC_REG_32(GICC_EOIR) = iar;//更新中斷結(jié)束寄存器
- }
- VOID OsInterrupt(UINT32 intNum)//中斷實際處理函數(shù)
- {
- HwiHandleForm *hwiForm = NULL;
- UINT32 *intCnt = NULL;
- intCnt = &g_intCount[ArchCurrCpuid()];//當(dāng)前CPU的中斷總數(shù)量 ++
- *intCnt = *intCnt + 1;//@note_why 這里沒看明白為什么要 +1
- #ifdef LOSCFG_CPUP_INCLUDE_IRQ //開啟查詢系統(tǒng)CPU的占用率的中斷
- OsCpupIrqStart();//記錄本次中斷處理開始時間
- #endif
- #ifdef LOSCFG_KERNEL_TICKLESS
- OsTicklessUpdate(intNum);
- #endif
- hwiForm = (&g_hwiForm[intNum]);//獲取對應(yīng)中斷的實體
- #ifndef LOSCFG_NO_SHARED_IRQ //如果沒有定義不共享中斷 ,意思就是如果是共享中斷
- while (hwiForm->pstNext != NULL) { //一直擼到最后
- hwiForm = hwiForm->pstNext;//下一個繼續(xù)擼
- #endif
- if (hwiForm->uwParam) {//有參數(shù)的情況
- HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;//獲取回調(diào)函數(shù)
- if (func != NULL) {
- UINTPTR *param = (UINTPTR *)(hwiForm->uwParam);
- func((INT32)(*param), (VOID *)(*(param + 1)));//運行帶參數(shù)的回調(diào)函數(shù)
- }
- } else {//木有參數(shù)的情況
- HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;//獲取回調(diào)函數(shù)
- if (func != NULL) {
- func();//運行回調(diào)函數(shù)
- }
- }
- #ifndef LOSCFG_NO_SHARED_IRQ
- }
- #endif
- ++g_hwiFormCnt[intNum];//中斷號計數(shù)器總數(shù)累加
- *intCnt = *intCnt - 1; //@note_why 這里沒看明白為什么要 -1
- #ifdef LOSCFG_CPUP_INCLUDE_IRQ //開啟查詢系統(tǒng)CPU的占用率的中斷
- OsCpupIrqEnd(intNum);//記錄中斷處理時間完成時間
- #endif
- }
解讀 統(tǒng)一中斷處理函數(shù)是一個通過一個中斷號去找到注冊函數(shù)的過程,分四步走:
● 第一步:取號,這號是由中斷控制器的 GICC_IAR寄存器提供的,這是一個專門保存當(dāng)前中斷號的寄存器.
● 第二步:從注冊表g_hwiForm中查詢注冊函數(shù),同時取出參數(shù).
● 第三步:執(zhí)行函數(shù),也就是回調(diào)注冊函數(shù),分有參和無參兩種情況 func(...),在中斷共享的情況,注冊函數(shù)會指向之后的注冊函數(shù)pstNext,依次執(zhí)行回調(diào)函數(shù),這是中斷共享的實現(xiàn)細節(jié).
- typedef struct tagHwiHandleForm {
- HWI_PROC_FUNC pfnHook; //中斷處理函數(shù)
- HWI_ARG_T uwParam; //中斷處理函數(shù)參數(shù)
- struct tagHwiHandleForm *pstNext; //節(jié)點,指向下一個中斷,用于共享中斷的情況
- } HwiHandleForm;
● 第四步:銷號,本次中斷完成了就需要消除記錄,中斷控制器也有專門的銷號寄存器GICC_EOIR
● 另外的是一些統(tǒng)一數(shù)據(jù),每次中斷號處理內(nèi)核都會記錄次數(shù),和耗時,以便定位/跟蹤/診斷錯誤.
參與貢獻
● 訪問注解倉庫地址
● Fork 本倉庫 >> 新建 Feat_xxx 分支 >> 提交代碼注解 >> 新建 Pull Request
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)