一起來學習LiteOS中斷模組的原始碼

華為雲開發者社群發表於2021-03-12
摘要:本文帶領大家一起剖析了LiteOS中斷模組的原始碼。

本文我們來一起學習下LiteOS中斷模組的原始碼,文中所涉及的原始碼,均可以在LiteOS開源站點https://gitee.com/LiteOS/LiteOS 獲取。中斷原始碼、開發文件,示例程式程式碼如下:

  • LiteOS核心中斷原始碼

包括中斷模組的私有標頭檔案kernel\base\include\los_hwi_pri.h、標頭檔案kernel\include\los_hwi.h、C原始碼檔案kernel\base\los_hwi.c。

  • 中斷控制器實現程式碼

開源LiteOS支援的中斷控制器有通用中斷控制器GIC(General Interrupt Controller)、巢狀向量中斷控制器NVIC(Nested Vectored Interrupt Controller),本文以STM32F769IDISCOVERY為例,分析一下適用於Cortex-M核的NVIC。各中斷控制器的原始碼包含標頭檔案、原始檔,程式碼路徑如下:https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/include/https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/arm/interrupt/gic/https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/arm/interrupt/nvic/

  • 開關中斷彙編實現程式碼

開關中斷的函式UINT32 ArchIntLock(VOID)、UINT32 ArchIntUnlock(VOID)、ArchIntRestore(UINT32 intSave)是基於組合語言實現的,根據不同的CPU架構,分佈在下述檔案裡: arch\arm\cortex_a_r\include\arch\interrupt.h、arch\arm64\include\arch\interrupt.h、arch\arm\cortex_m\src\dispatch.S。

  • 開發指南中斷文件

線上文件https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E4%B8%AD%E6%96%AD

我們先看看中斷的相關概念,詳細的介紹,請參考LiteOS開發指南中斷文件。

1、中斷概念介紹

中斷是指出現需要時,CPU暫停執行當前程式,轉而執行新程式的過程。當外設需要CPU時,將通過產生中斷訊號使CPU立即中斷當前任務來響應中斷請求。在剖析中斷原始碼之前,下面介紹些中斷相關的硬體、中斷相關的概念。

1.1 中斷相關的硬體介紹

與中斷相關的硬體可以劃分為三類:裝置、中斷控制器、CPU本身。

  • 裝置

發起中斷的源,當裝置需要請求CPU時,產生一箇中斷訊號,該訊號連線至中斷控制器。

  • 中斷控制器

中斷控制器是CPU眾多外設中的一個,它一方面接收其它外設中斷引腳的輸入。另一方面,它會發出中斷訊號給CPU。可以通過對中斷控制器程式設計來開啟和關閉中斷源、設定中斷源的優先順序和觸發方式。

  • CPU

CPU會響應中斷源的請求,中斷當前正在執行的任務,轉而執行中斷處理程式。

1.2 中斷相關的概念

  • 中斷號

每個中斷請求訊號都會有特定的標誌,使得計算機能夠判斷是哪個裝置提出的中斷請求,這個標誌就是中斷號。

  • 中斷優先順序

為使系統能夠及時響應並處理所有中斷,系統根據中斷時間的重要性和緊迫程度,將中斷源分為若干個級別,稱作中斷優先順序。

  • 中斷處理程式

當外設產生中斷請求後,CPU暫停當前的任務,轉而響應中斷申請,即執行中斷處理程式。產生中斷的每個裝置都有相應的中斷處理程式。

  • 中斷向量

中斷服務程式的入口地址 。

  • 中斷向量表

儲存中斷向量的儲存區,中斷向量與中斷號對應,中斷向量在中斷向量表中按照中斷號順序儲存。

  • 中斷共享

當外設較少時,可以實現一個外設對應一箇中斷號,但為了支援更多的硬體裝置,可以讓多個裝置共享一箇中斷號,共享同一個中斷號的中斷處理程式形成一個連結串列。當外部裝置產生中斷申請時,系統會遍歷中斷號對應的中斷處理程式連結串列,直到找到對應裝置的中斷處理程式。在遍歷執行過程中,各中斷處理程式可以通過檢測裝置ID,判斷是否是這個中斷處理程式對應的裝置產生的中斷。

我們再看看LiteOS核心中斷原始碼。

2、LiteOS核心中斷原始碼

2.1 中斷相關的結構體

在檔案kernel\base\include\los_hwi_pri.h中定義了2個結構體,HwiHandleInfo和HwiControllerOps。HwiHandleInfo結構體記錄中斷處理程式的相關資訊,包括中斷處理程式、中斷共享模式或中斷處理程式引數、中斷處理程式執行的次數等。開啟共享中斷時,還包含指向下一個中斷處理程式結構體的連結串列指標,如⑴所示。中斷控制器操作項結構體HwiControllerOps包括中斷操作相關的函式,如觸發中斷、清除中斷、使能中斷、失能中斷、設定中斷優先順序、獲取當前中斷號、獲取中斷版本、根據中斷號獲取中斷處理程式資訊、處理呼叫中斷程式。對於SMP多核,還包括設定中斷CPU親和性,傳送核間中斷等函式,如⑵所示。

⑴  typedef struct tagHwiHandleForm {
        HWI_PROC_FUNC hook;         /* 中斷處理函式 */
        union {
            HWI_ARG_T shareMode;    /* 共享中斷時,頭節點使用此成員表示共享標誌位 */
            HWI_ARG_T registerInfo; /* 共享模式的裝置節點,非共享模式時,表示中斷處理函式的引數*/
        };
    #ifndef LOSCFG_NO_SHARED_IRQ
        struct tagHwiHandleForm *next;
    #endif
        UINT32 respCount; /* 中斷程式執行次數 */
    } HwiHandleInfo;

⑵  typedef struct {
        VOID (*triggerIrq)(HWI_HANDLE_T hwiNum);
        VOID (*clearIrq)(HWI_HANDLE_T hwiNum);
        VOID (*enableIrq)(HWI_HANDLE_T hwiNum);
        VOID (*disableIrq)(HWI_HANDLE_T hwiNum);
        UINT32 (*setIrqPriority)(HWI_HANDLE_T hwiNum, UINT8 priority);
        UINT32 (*getCurIrqNum)(VOID);
        CHAR *(*getIrqVersion)(VOID);
        HwiHandleInfo *(*getHandleForm)(HWI_HANDLE_T hwiNum);
        VOID (*handleIrq)(VOID);
    #ifdef LOSCFG_KERNEL_SMP
        VOID (*setIrqCpuAffinity)(HWI_HANDLE_T hwiNum, UINT32 cpuMask);
        VOID (*sendIpi)(UINT32 target, UINT32 ipi);
    #endif
    } HwiControllerOps;

kernel\include\los_hwi.h定義的結構體HWI_IRQ_PARAM_S,用於處理中斷處理程式的引數,包含中斷號、裝置Id、中斷名稱等。在建立、刪除中斷時,需要提供這個引數。

typedef struct tagIrqParam {
    int swIrq;          /**< 中斷號 */
    VOID *pDevId;       /**< 發起中斷的裝置Id */
    const CHAR *pName;  /**< 中斷名稱 */
} HWI_IRQ_PARAM_S;

2.2 中斷初始化OsHwiInit()

在系統啟動時,在kernel\init\los_init.c中呼叫OsHwiInit()進行中斷初始化。這個函式定義在kernel\base\los_hwi.c,然後進一步呼叫定義在targets\bsp\hw\arm\interrupt\nvic\nvic.c檔案中HalIrqInit()函式完成中斷向量初始化,並呼叫OsHwiControllerReg()註冊中斷控制器操作項。在下文分析NVIC時再分析該函式HalIrqInit()。

/* Initialization of the hardware interrupt */
LITE_OS_SEC_TEXT_INIT VOID OsHwiInit(VOID)
{
    HalIrqInit();
    return;
}

2.3 建立中斷UINT32 LOS_HwiCreate()

開發者可以呼叫函式UINT32 LOS_HwiCreate()建立中斷,註冊中斷處理程式。我們先看看這個函式的引數,HWI_HANDLE_T hwiNum是硬體中斷號,HWI_PRIOR_T hwiPrio中斷的優先順序,HWI_MODE_T hwiMode中斷模式,可以用來標記是否共享中斷。HWI_PROC_FUNC hwiHandler是需要註冊的中斷處理程式,中斷被觸發後會呼叫這個函式。HWI_IRQ_PARAM_S *irqParam是中斷處理程式的引數。

一起剖析下這個函式的原始碼,⑴處程式碼獲取對應中斷號hwiNum的中斷處理程式資訊,其中g_hwiOps是中斷初始化時註冊的中斷控制器操作項,後文分析NVIC時會分析該中斷控制器操作項。⑵、⑶處根據是否開啟了共享中斷LOSCFG_NO_SHARED_IRQ來分別配置建立中斷。⑷處在建立中斷成功,且支援配置中斷優先順序時,呼叫g_hwiOps->setIrqPriority(hwiNum, hwiPrio)函式設定中斷的優先順序。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum,
                                           HWI_PRIOR_T hwiPrio,
                                           HWI_MODE_T hwiMode,
                                           HWI_PROC_FUNC hwiHandler,
                                           HWI_IRQ_PARAM_S *irqParam)
{
    UINT32 ret;
    HwiHandleInfo *hwiForm = NULL;

    if (hwiHandler == NULL) {
        return OS_ERRNO_HWI_PROC_FUNC_NULL;
    }

⑴  hwiForm = g_hwiOps->getHandleForm(hwiNum);
    if (hwiForm == NULL) {
        return OS_ERRNO_HWI_NUM_INVALID;
    }
    LOS_TRACE(HWI_CREATE, hwiNum, hwiPrio, hwiMode, (UINTPTR)hwiHandler);

#ifdef LOSCFG_NO_SHARED_IRQ
⑵   ret = OsHwiCreateNoShared(hwiForm, hwiMode, hwiHandler, irqParam);
#else
⑶  ret = OsHwiCreateShared(hwiForm, hwiMode, hwiHandler, irqParam);
    LOS_TRACE(HWI_CREATE_SHARE, hwiNum, (UINTPTR)(irqParam != NULL ? irqParam->pDevId : NULL), ret);
#endif

    if ((ret == LOS_OK) && (g_hwiOps->setIrqPriority != NULL)) {
        if (!HWI_PRI_VALID(hwiPrio)) {
            return OS_ERRNO_HWI_PRIO_INVALID;
        }
⑷      ret = g_hwiOps->setIrqPriority(hwiNum, hwiPrio);
    }
    return ret;
}

2.3.1 不支援共享中斷時建立中斷UINT32 OsHwiCreateNoShared()

我們先看下沒有開啟共享中斷支援時,如何建立中斷。⑴處程式碼表示如果建立的中斷的模式hwiMode等於共享模式IRQF_SHARED,則返回錯誤OS_ERRNO_HWI_SHARED_ERROR。⑵處程式碼判斷中斷處理程式hwiForm->hook是否為空,為空時則把hwiHandler賦值賦值給它;hwiForm->hook不為空則執行⑹,說明中斷已經建立過,返回異常OS_ERRNO_HWI_ALREADY_CREATED;⑷處判斷irqParam是否為空,如果不為空,則為這個中斷處理程式的引數申請記憶體,設定陣列,並賦值給hwiForm->registerInfo。⑸處表示如果為中斷處理程式的引數申請記憶體失敗,則返回異常。

STATIC UINT32 OsHwiCreateNoShared(HwiHandleInfo *hwiForm, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler,
                                  const HWI_IRQ_PARAM_S *irqParam)
{
    UINT32 intSave;

⑴  if (hwiMode & IRQF_SHARED) {
        return OS_ERRNO_HWI_SHARED_ERROR;
    }
    HWI_LOCK(intSave);
⑵  if (hwiForm->hook == NULL) {
⑶      hwiForm->hook = hwiHandler;
⑷      if (irqParam != NULL) {
            hwiForm->registerInfo = OsHwiCpIrqParam(irqParam);
⑸          if (hwiForm->registerInfo == (HWI_ARG_T)NULL) {
                HWI_UNLOCK(intSave);
                return OS_ERRNO_HWI_NO_MEMORY;
            }
        }
    } else {
        HWI_UNLOCK(intSave);
⑹      return OS_ERRNO_HWI_ALREADY_CREATED;
    }
    HWI_UNLOCK(intSave);
    return LOS_OK;
}

可以結合示意圖理解建立非共享中斷,對應的中斷處理資訊表HwiHandleInfo *hwiForm結構體示意圖如下。建立中斷時,除了校驗,主要是設定中斷處理程式hwiForm->hook及其引數hwiForm->registerInfo。

一起來學習LiteOS中斷模組的原始碼

2.3.2 支援共享時建立中斷UINT32 OsHwiCreateShared()

我們先看下開啟共享中斷支援時,如何建立建立中斷。支援共享中斷時,可以建立共享中斷,也可以建立非共享的中斷,如果⑴處的modeResult == 1表示建立的是共享中斷,否則是非共享中斷。沒有開啟共享中斷支援時,使用HwiHandleInfo *hwiForm單節點維護中斷處理程式資訊,開啟共享中斷支援時,需要使用HwiHandleInfo *hwiForm單連結串列來維護中斷處理程式資訊。

首先做些基礎的引數校驗,⑵處表示,如果建立的是共享中斷,但是引數irqParam或引數的裝置Id為空,返回錯誤OS_ERRNO_HWI_SHARED_ERROR。也就是說,共享模式hwiMode和中斷處理程式的引數irqParam是必須的,因為需要指定特定的裝置來共享同一個中斷號。⑶處判斷表示,如果head->next不為空,說明此中斷號已經有其他裝置使用了,該中斷號需要被不同的裝置共享,此時如果hwiMode或head->shareMode不是共享模式,則返回錯誤。⑷處while迴圈程式碼處理此中斷號已經有其他裝置使用的情況,依次迴圈中斷處理資訊表的單連結串列,判斷是否已經存在同一個裝置已經註冊的情況,如果存在則返回錯誤OS_ERRNO_HWI_ALREADY_CREATED。迴圈完畢之後,hwiForm為連結串列中的最後一個裝置節點。如果建立的不是共享中斷,迴圈條件不滿足,此處程式碼就不會執行。

做完引數校驗後,⑸處為一個HwiHandleInfo節點申請記憶體,申請的這個節點是中斷的裝置節點,管理具體裝置的中斷處理程式資訊,引數列表中的節點HwiHandleInfo *head是中斷頭節點,只標記是不是共享中斷,可以參考下文的示意圖,來加深理解。如果申請失敗則返回錯誤OS_ERRNO_HWI_NO_MEMORY;申請成功繼續執行後面的語句,把hwiForm->respCount賦值為0,表示中斷處理程式還沒有執行過。⑹處程式碼判斷引數irqParam,不為空時申請記憶體空間儲存引數,否則引數賦值為0。⑺處把新增裝置的中斷處理程式賦值給hwiFormNode->hook,然後把新增的節點掛載連結串列的尾部。⑻處表示更新頭節點的共享模式。

STATIC UINT32 OsHwiCreateShared(HwiHandleInfo *head, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler,
                                const HWI_IRQ_PARAM_S *irqParam)
{
    UINT32 intSave;
    HwiHandleInfo *hwiFormNode = NULL;
    HWI_IRQ_PARAM_S *hwiParam = NULL;
⑴  HWI_MODE_T modeResult = hwiMode & IRQF_SHARED;
    HwiHandleInfo *hwiForm = NULL;

⑵  if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) {
        return OS_ERRNO_HWI_SHARED_ERROR;
    }

    HWI_LOCK(intSave);

⑶  if ((head->next != NULL) && ((modeResult == 0) || (!(head->shareMode & IRQF_SHARED)))) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_SHARED_ERROR;
    }

    hwiForm = head;
⑷  while (hwiForm->next != NULL) {
        hwiForm = hwiForm->next;
        hwiParam = (HWI_IRQ_PARAM_S *)(hwiForm->registerInfo);
        if (hwiParam->pDevId == irqParam->pDevId) {
            HWI_UNLOCK(intSave);
            return OS_ERRNO_HWI_ALREADY_CREATED;
        }
    }

⑸  hwiFormNode = (HwiHandleInfo *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleInfo));
    if (hwiFormNode == NULL) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_NO_MEMORY;
    }
    hwiForm->respCount = 0;

⑹  if (irqParam != NULL) {
        hwiFormNode->registerInfo = OsHwiCpIrqParam(irqParam);
        if (hwiFormNode->registerInfo == (HWI_ARG_T)NULL) {
            HWI_UNLOCK(intSave);
            (VOID) LOS_MemFree(m_aucSysMem0, hwiFormNode);
            return OS_ERRNO_HWI_NO_MEMORY;
        }
    } else {
        hwiFormNode->registerInfo = 0;
    }

⑺  hwiFormNode->hook = hwiHandler;
    hwiFormNode->next = (struct tagHwiHandleForm *)NULL;
    hwiForm->next = hwiFormNode;

⑻  head->shareMode = modeResult;

    HWI_UNLOCK(intSave);
    return LOS_OK;
}

配置完畢中斷後,對應的中斷處理資訊表HwiHandleInfo *hwiForm示意圖如下:

可以結合示意圖來理解在開啟共享中斷支援時,如何建立中斷。HwiHandleInfo *hwiForm結構體單連結串列示意圖如下。建立非共享中斷時,只需要兩個節點,左側頭結點中的head->shareMode等於0,表示非共享中斷,右側一個裝置節點,表示指定裝置的中斷處理程式,包含中斷處理程式hwiForm->hook及其引數hwiForm->registerInfo,hwiForm->next為空。建立共享中斷時,至少兩個節點,左側頭結點中的head->shareMode等於1,表示共享中斷,右側一到多個裝置節點,表示不同裝置的中斷處理程式,包含中斷處理程式hwiForm->hook及其引數hwiForm->registerInfo,hwiForm->next依次指向下一個裝置的節點,最後一個節點的hwiForm->next為空。

一起來學習LiteOS中斷模組的原始碼

2.4 刪除中斷UINT32 LOS_HwiDelete()

中斷刪除操作是建立操作的反向操作,也比較好理解。開發者可以呼叫函式UINT32 LOS_HwiDelete(HWI_HANDLE_T hwiNum, HWI_IRQ_PARAM_S *irqParam)刪除中斷。我們先看看這個函式的引數,HWI_HANDLE_T hwiNum是硬體中斷號,HWI_IRQ_PARAM_S *irqParam是中斷處理程式的引數,如果開啟了共享中斷需要該引數來刪除指定裝置的中斷資訊。

一起剖析下這個函式的原始碼,⑴處程式碼獲取對應中斷號hwiNum的中斷處理程式資訊,其中g_hwiOps是中斷初始化時註冊的中斷控制器操作項。⑵、⑶處根據是否開啟了共享中斷LOSCFG_NO_SHARED_IRQ來分別呼叫相應的函式刪除中斷。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiDelete(HWI_HANDLE_T hwiNum, HWI_IRQ_PARAM_S *irqParam)
{
    UINT32 ret;
⑴  HwiHandleInfo *hwiForm = g_hwiOps->getHandleForm(hwiNum);

    if (hwiForm == NULL) {
        return OS_ERRNO_HWI_NUM_INVALID;
    }
    LOS_TRACE(HWI_DELETE, hwiNum);

#ifdef LOSCFG_NO_SHARED_IRQ
    (VOID)irqParam;
⑵  ret = OsHwiDelNoShared(hwiForm);
#else
⑶  ret = OsHwiDelShared(hwiForm, irqParam);
    LOS_TRACE(HWI_DELETE_SHARE, hwiNum, (UINTPTR)(irqParam != NULL ? irqParam->pDevId : NULL), ret);
#endif
    return ret;
}

沒有開啟共享中斷支援時,刪除中斷呼叫OsHwiDelNoShared(hwiForm)函式,中斷處理程式hwiForm->hook,釋放記憶體等,程式碼簡單,讀者自行閱讀。我們主要來剖析下支援共享中斷時,是如何呼叫UINT32 OsHwiDelShared(HwiHandleInfo *head, const HWI_IRQ_PARAM_S *irqParam)刪除中斷的。

2.4.1 支援共享時刪除中斷UINT32 OsHwiDelShared()

我們來分析下開啟共享中斷支援時,刪除中斷的原始碼流程是什麼樣的。⑴處表示,如果刪除的是共享中斷,但是引數irqParam或引數的裝置Id為空,返回錯誤OS_ERRNO_HWI_SHARED_ERROR。⑵處表示刪除的是非共享中斷,如果wiForm->registerInfo不為空,則釋放引數佔用的記憶體,然後釋放裝置節點佔用的記憶體,並把head->next置空。

⑶處程式碼處理如何刪除共享中斷。⑷處while迴圈對中斷程式資訊連結串列上的裝置節點進行遍歷。⑸處表示如果沒有匹配到要刪除的裝置,則繼續遍歷下一個裝置節點。⑹處表示匹配到要刪除的裝置中斷處理程式節點,則釋放引數的記憶體、釋放裝置節點記憶體,並執行⑺,標記匹配到裝置節點,然後跳出迴圈。⑻處,如果迴圈遍歷完畢,還沒有匹配到裝置節點,則返回錯誤。⑼處表示如果刪除裝置節點後,只有一個頭結點,則把共享模式改為非共享。

STATIC UINT32 OsHwiDelShared(HwiHandleInfo *head, const HWI_IRQ_PARAM_S *irqParam)
{
    HwiHandleInfo *hwiFormtmp = NULL;
    HwiHandleInfo *hwiForm = NULL;
    UINT32 find = FALSE;
    UINT32 intSave;

    HWI_LOCK(intSave);

⑴  if ((head->shareMode & IRQF_SHARED) && ((irqParam == NULL) || (irqParam->pDevId == NULL))) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_SHARED_ERROR;
    }

⑵  if ((head->next != NULL) && !(head->shareMode & IRQF_SHARED)) {
        hwiForm = head->next;
        if (hwiForm->registerInfo) {
            (VOID) LOS_MemFree(m_aucSysMem0, (VOID *)hwiForm->registerInfo);
        }
        (VOID) LOS_MemFree(m_aucSysMem0, hwiForm);
        head->next = NULL;
        head->respCount = 0;

        HWI_UNLOCK(intSave);
        return LOS_OK;
    }

⑶  hwiFormtmp = head;
    hwiForm = head->next;
⑷  while (hwiForm != NULL) {
⑸      if (((HWI_IRQ_PARAM_S *)(hwiForm->registerInfo))->pDevId != irqParam->pDevId) {
            hwiFormtmp = hwiForm;
            hwiForm = hwiForm->next;
        } else {
⑹           hwiFormtmp->next = hwiForm->next;
            (VOID) LOS_MemFree(m_aucSysMem0, (VOID *)hwiForm->registerInfo);
            (VOID) LOS_MemFree(m_aucSysMem0, hwiForm);
⑺          find = TRUE;
            break;
        }
    }

⑻  if (!find) {
        HWI_UNLOCK(intSave);
        return OS_ERRNO_HWI_HWINUM_UNCREATE;
    }

⑼  if (head->next == NULL) {
        head->shareMode = 0;
    }

    HWI_UNLOCK(intSave);
    return LOS_OK;
}

2.5 中斷控制器操作項函式

其他操作程式碼結構非常類似,以UINT32 LOS_HwiEnable(HWI_HANDLE_T hwiNum)為例剖析一下,執行⑴處程式碼,呼叫NVIC中定義的中斷控制器操作項g_hwiOps->enableIrq對應的函式HalIrqUnmask(),然後呼叫arch\arm\cortex_m\cmsis\core_cm7.h中的NVIC_EnableIRQ()函式,CMSIS中的程式碼以後專門分析,此處不再深入剖析。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiEnable(HWI_HANDLE_T hwiNum)
{
    if (!HWI_NUM_VALID(hwiNum)) {
        return OS_ERRNO_HWI_NUM_INVALID;
    }
    OS_RETURN_ERR_FUNCPTR_IS_NULL(g_hwiOps->enableIrq, OS_ERRNO_HWI_PROC_FUNC_NULL);
    LOS_TRACE(HWI_ENABLE, hwiNum);
⑴  g_hwiOps->enableIrq(hwiNum);
    return LOS_OK;
}

中斷控制器操作項中,其他的各個函式呼叫對應關係如下:

一起來學習LiteOS中斷模組的原始碼

其中,第7行獲取中斷號的函式在arch\arm\cortex_m\cmsis\cmsis_armcc.h檔案中定義的uint32_t __get_IPSR(void)。

第9-11行,只適用於SMP多核,在NVIC中不支援。

2.6 中斷的其他操作

我們再來看看其他常用的函式操作。

2.6.1 IntActive()判斷是否有處理中的中斷

其他模組中經常使用OS_INT_ACTIVE或OS_INT_INACTIVE來判斷是否在處理中斷,下文原始碼⑴處的陣列g_intCount[ArchCurrCpuid()]表示各個CPU核中正在處理的中斷的數目。返回值大於1,則表示正在處理中斷。

......
#define OS_INT_ACTIVE IntActive()
#define OS_INT_INACTIVE (!(OS_INT_ACTIVE))
......
size_t IntActive()
{
    size_t intCount;
    UINT32 intSave = LOS_IntLock();
⑴  intCount = g_intCount[ArchCurrCpuid()];
    LOS_IntRestore(intSave);
    return intCount;
}

2.6.2 中斷處理函式VOID OsIntHandle()

在LiteOS歸一化核心中,VOID OsIntEntry(VOID)中斷程式處理入口函式適用於arm(cortex-a/r)/arm64平臺,VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *handleForm)適用於arm(cortex-m), xtensa, riscv等平臺,我們主要來剖析下後者。

⑴處獲取全域性變數指標&g_intCount[ArchCurrCpuid()],然後把它表示的中斷數量加1,在中斷執行完畢後,在⑸處再把活躍的中斷數量減1。

⑵、⑷處程式碼在開啟中斷巢狀、中斷搶佔LOSCFG_ARCH_INTERRUPT_PREEMPTION時,在執行中斷處理程式時,需要開、關中斷,具體的程式碼就是呼叫
LOS_IntUnLock()、LOS_IntLock(),讀者可以訪問LiteOS開源站點自行檢視。⑶處程式碼呼叫InterruptHandle(hwiForm)來執行中斷處理程式,繼續往下看來剖析這個函式。

VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *hwiForm)
{
    size_t *intCnt = NULL;

#ifdef LOSCFG_CPUP_INCLUDE_IRQ
    OsCpupIrqStart();
#endif
⑴   intCnt = &g_intCount[ArchCurrCpuid()];
    *intCnt = *intCnt + 1;

#ifdef LOSCFG_DEBUG_SCHED_STATISTICS
    OsHwiStatistics(hwiNum);
#endif

#ifdef LOSCFG_KERNEL_LOWPOWER
    if (g_intWakeupHook != NULL) {
        g_intWakeupHook(hwiNum);
    }
#endif
    LOS_TRACE(HWI_RESPONSE_IN, hwiNum);

⑵  OsIrqNestingActive(hwiNum);
⑶  InterruptHandle(hwiForm);
⑷  OsIrqNestingInactive(hwiNum);

    LOS_TRACE(HWI_RESPONSE_OUT, hwiNum);

⑸  *intCnt = *intCnt - 1;

#ifdef LOSCFG_CPUP_INCLUDE_IRQ
    OsCpupIrqEnd(hwiNum);
#endif
}

我們來具體看下這個函式VOID InterruptHandle(HwiHandleInfo *hwiForm),⑴處把中斷處理程式的執行次數加1。⑵處程式碼處理共享中斷的情況,會依次迴圈中斷號對應的中斷程式資訊連結串列,一箇中斷號對應的中斷髮生時,所有註冊在這個中斷後下的中斷處理程式都會執行。

⑶處判斷中斷處理程式的引數是否為空,不為空時執行⑷處程式碼,獲取中斷處理程式HWI_PROC_FUNC2 func,它需要2個引數,分別為中斷號,裝置Id。然後執行⑸獲取中斷處理程式的引數,接著執行⑹處程式碼呼叫中斷處理程式。如果中斷處理程式的引數為空,則獲取不需要引數的中斷處理程式HWI_PROC_FUNC0 func,然後執行。

STATIC INLINE VOID InterruptHandle(HwiHandleInfo *hwiForm)
{
⑴  hwiForm->respCount++;
#ifndef LOSCFG_NO_SHARED_IRQ
⑵  while (hwiForm->next != NULL) {
        hwiForm = hwiForm->next;
#endifif (hwiForm->registerInfo) {
⑷          HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->hook;
            if (func != NULL) {
⑸              UINTPTR *param = (UINTPTR *)(hwiForm->registerInfo);
⑹              func((INT32)(*param), (VOID *)(*(param + 1)));
            }
        } else {
⑺          HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->hook;
            if (func != NULL) {
                func();
            }
        }
#ifndef LOSCFG_NO_SHARED_IRQ
    }
#endif
}

我們下來看看LiteOS中封裝的NVIC程式碼。

3、NVIC巢狀向量中斷控制器程式碼

NVIC程式碼包含一個標頭檔案targets\bsp\hw\include\nvic.h和C原始碼檔案targets\bsp\hw\arm\interrupt\nvic\nvic.c。

3.1 nvic.h標頭檔案

NVIC標頭檔案nvic.h中,主要定義了一些巨集,如中斷暫存器的地址、各個系統中斷號等,宣告瞭如下三個函式,其中函式IrqEntryV7M(VOID)在targets\bsp\hw\arm\interrupt\nvic\nvic.c檔案中定義,用於處理中斷的程式,也叫做中斷向量;函式VOID Reset_Handler(VOID)在彙編啟動檔案targets\STM32F769IDISCOVERY\los_startup_gcc.S中定義的,用於復位、啟動時的處理;函式VOID osPendSV(VOID)定義在arch\arm\cortex_m\src\dispatch.S,處理PendSV異常。

/* hardware interrupt entry */
extern VOID IrqEntryV7M(VOID);

/* Reset handle entry */
extern VOID Reset_Handler(VOID);

extern VOID osPendSV(VOID);

3.2 nvic.c原始碼檔案

NVIC原始碼檔案nvic.c中,定義中斷向量函式、中斷初始化函式。我們一起學習下原始碼。

⑴、⑵處程式碼為系統支援的中斷定義了2個陣列,對於每一箇中斷號hwiNum,對應的陣列元素g_hwiForm[hwiNum]表示每一箇中斷對應的中斷處理程式的相關資訊,g_hwiVec[hwiNum]表示中斷髮生時需要執行的程式,該程式也叫中斷向量,這個陣列有時候也叫做中斷向量表。⑵處程式碼只定義了16個系統中斷號對應的中斷處理程式,其他在呼叫中斷初始化函式VOID HalIrqInit(VOID)時指定。其中1號中斷對應復位處理程式Reset_Handler,14號中斷對應osPendSV處理程式,15號中斷是tick中斷,還有些系統保留的中斷號。在LiteOS核心裡對中斷處理進行接管,當中斷髮生,執行的中斷處理函式在⑶處定義,這個函式會進一步呼叫使用者定義的中斷處理函式。

逐行分析下VOID IrqEntryV7M(VOID)函式,⑷處通過讀取ipsr暫存器獲取中斷號,__get_IPSR()定義在檔案arch\arm\cortex_m\cmsis\cmsis_armcc.h。然後,根據中斷號從中斷處理程式資訊表中獲取&g_hwiForm[hwiIndex],作為引數傳遞給函式VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *hwiForm)進一步處理,該函式上文已經分析過。

⑴   LITE_OS_SEC_BSS HwiHandleInfo g_hwiForm[LOSCFG_PLATFORM_HWI_LIMIT];

⑵   LITE_OS_SEC_DATA_VEC HWI_PROC_FUNC g_hwiVec[LOSCFG_PLATFORM_HWI_LIMIT] = {
        (HWI_PROC_FUNC)0,             /* [0] Top of Stack */
        (HWI_PROC_FUNC)Reset_Handler, /* [1] reset */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [2] NMI Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [3] Hard Fault Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [4] MPU Fault Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [5] Bus Fault Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [6] Usage Fault Handler */
        (HWI_PROC_FUNC)0,             /* [7] Reserved */
        (HWI_PROC_FUNC)0,             /* [8] Reserved */
        (HWI_PROC_FUNC)0,             /* [9] Reserved */
        (HWI_PROC_FUNC)0,             /* [10] Reserved */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [11] SVCall Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [12] Debug Monitor Handler */
        (HWI_PROC_FUNC)0,             /* [13] Reserved */
        (HWI_PROC_FUNC)osPendSV,      /* [14] PendSV Handler */
        (HWI_PROC_FUNC)IrqEntryV7M,   /* [15] SysTick Handler */
    };
⑶   LITE_OS_SEC_TEXT_MINOR VOID IrqEntryV7M(VOID)
    {
        UINT32 hwiIndex;
⑷      hwiIndex = __get_IPSR();
        g_curIrqNum = hwiIndex;
⑸      OsIntHandle(hwiIndex, &g_hwiForm[hwiIndex]);

        if (OsTaskProcSignal() != 0) {
            OsSchedPreempt();
        }
    }

繼續分析程式碼,一起看下中斷初始化做了些什麼,中斷控制器操作項有哪些。⑴處程式碼定義的函式VOID HalIrqPending(UINT32 hwiNum)會請求觸發指定中斷號的中斷處理程式,先對中斷號進行校驗,確保中斷號合法,減去系統中斷數量OS_SYS_VECTOR_CNT,然後呼叫⑵處程式碼執行定義在arch\arm\cortex_m\cmsis\core_cm7.h檔案內的函式NVIC_SetPendingIRQ((IRQn_Type)hwiNum),請求觸發中斷。接著,這個函式VOID HalIrqPending(UINT32 hwiNum)在執行⑶處程式碼時賦值給NVIC的中斷控制器操作項g_nvicOps的結構體成員.triggerIrq。除了觸發請求中斷,還有使能中斷、失能中斷、設定中斷優先順序,獲取當前中斷號,獲取中斷版本,獲取中斷處理資訊表等函式。這些HalXXXX函式,格式差不多,區別在呼叫不同的NVIC_XXX()函式,不再一一分析。

我們再看看中斷初始化函式,⑷處會把系統支援的系統中斷以後的中斷號對應的中斷處理程式都初始化為(HWI_PROC_FUNC)IrqEntryV7M這個中斷接管函式。如果是Cortex-M0核,執行⑸處程式碼,使能時鐘,重新對映SRAM記憶體,對於其他核,執行⑹處程式碼把中斷向量表賦值給SCB->VTOR。對於Cortex-M3及以上的CPU核,還需要執行⑺設定優先順序組。最後,呼叫定義在kernel\base\los_hwi.c裡的函式VOID OsHwiControllerReg(const HwiControllerOps *ops)註冊中斷控制器操作項,這樣LiteOS的中斷處理程式就可以呼叫NVIC裡定義的中斷相關的操作。

    ......
⑴  VOID HalIrqPending(UINT32 hwiNum)
    {
        UINT32 intSave;

        if ((hwiNum > OS_USER_HWI_MAX) || (hwiNum < OS_USER_HWI_MIN)) {
            return;
        }

        hwiNum -= OS_SYS_VECTOR_CNT;
        intSave = LOS_IntLock();
⑵      NVIC_SetPendingIRQ((IRQn_Type)hwiNum);
        LOS_IntRestore(intSave);
    }
    ......

⑶  STATIC const HwiControllerOps g_nvicOps = {
        .triggerIrq     = HalIrqPending,
        .enableIrq      = HalIrqUnmask,
        .disableIrq     = HalIrqMask,
        .setIrqPriority = HalIrqSetPriority,
        .getCurIrqNum   = HalCurIrqGet,
        .getIrqVersion  = HalIrqVersion,
        .getHandleForm  = HalIrqGetHandleForm,
    };

    VOID HalIrqInit(VOID)
    {
        UINT32 i;

⑷      for (i = OS_SYS_VECTOR_CNT; i < LOSCFG_PLATFORM_HWI_LIMIT; i++) {
            g_hwiVec[i] = (HWI_PROC_FUNC)IrqEntryV7M;
        }
⑸  #if (__CORTEX_M == 0x0U)  /* only for Cortex-M0*/
        __HAL_RCC_SYSCFG_CLK_ENABLE();
        __HAL_SYSCFG_REMAPMEMORY_SRAM();
    #else
⑹       SCB->VTOR = (UINT32)g_hwiVec;
    #endif
    #if (__CORTEX_M >= 0x03U) /* only for Cortex-M3 and above */
⑺      NVIC_SetPriorityGrouping(OS_NVIC_AIRCR_PRIGROUP);
    #endif

        /* register interrupt controller's operations */
⑻      OsHwiControllerReg(&g_nvicOps);
        return;
    }

4、開關中斷

最後,分享下開關中斷的相關知識,開、關中斷分別指的是:

  • 開中斷

執行完畢特定的短暫的程式,開啟中斷,可以響應中斷了。

  • 關中斷

為了保護執行的程式不被打斷,關閉相應外部的中斷。

對應的開關中斷的函式定義在kernel\include\los_hwi.h:

ArchIntLock(),ArchIntUnlock()這些基於組合語言實現的,讀者們自行閱讀檢視。我們看下開關斷函式的使用場景。⑴處的UINT32 LOS_IntLock(VOID)會關閉所有的中斷,與之對應,⑵處的函式UINT32 LOS_IntUnLock(VOID)會使能所有的中斷。⑶處的函式VOID LOS_IntRestore(UINT32 intSave)可以用來恢復UINT32 LOS_IntLock(VOID)函式關閉的中斷,UINT32 LOS_IntLock(VOID)的返回值作為VOID LOS_IntRestore(UINT32 intSave)的引數進行恢復中斷。

⑴  STATIC INLINE UINT32 LOS_IntLock(VOID)
    {
        return ArchIntLock();
    }

⑵  STATIC INLINE UINT32 LOS_IntUnLock(VOID)
    {
        return ArchIntUnlock();
    }

⑶  STATIC INLINE VOID LOS_IntRestore(UINT32 intSave)
    {
        ArchIntRestore(intSave);
    }

小結

本文帶領大家一起剖析了LiteOS中斷模組的原始碼,結合講解,參考官方示例程式程式碼,自己寫寫程式,實際編譯執行一下,加深理解。

感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/LiteOS/LiteOS/issues 。為了更容易找到LiteOS程式碼倉,建議訪問 https://gitee.com/LiteOS/LiteOS ,關注Watch、點贊Star、並Fork到自己賬戶下,如下圖,謝謝。

一起來學習LiteOS中斷模組的原始碼

本文分享自華為雲社群《LiteOS核心原始碼分析系列三 中斷Hwi》,原文作者:zhushy。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章