摘要:本文带领大家一起剖析了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。

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为空。

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;
}

中断控制器操作项中,其他的各个函数调用对应关系如下:

其中,第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内核源码分析系列三 中断Hwi》,原文作者:zhushy。

 

点击关注,第一时间了解华为云新鲜技术~

版权声明:本文为huaweiyun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/huaweiyun/p/14522376.html