乐趣区

关于code:一起来学习LiteOS中断模块的源代码

摘要:本文率领大家一起分析了 LiteOS 中断模块的源代码。

本文咱们来一起学习下 LiteOS 中断模块的源代码,文中所波及的源代码,均能够在 LiteOS 开源站点 https://gitee.com/LiteOS/LiteOS 获取。中断源代码、开发文档,示例程序代码如下:

  • LiteOS 内核中断源代码

包含中断模块的公有头文件 kernelbaseincludelos_hwi_pri.h、头文件 kernelincludelos_hwi.h、C 源代码文件 kernelbaselos_hwi.c。

  • 中断控制器实现代码

开源 LiteOS 反对的中断控制器有通用中断控制器 GIC(General Interrupt Controller)、嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller),本文以 STM32F769IDISCOVERY 为例,剖析一下实用于 Cortex- M 核的 NVIC。各中断控制器的源代码蕴含头文件、源文件,代码门路如下:https://gitee.com/LiteOS/Lite…、https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/arm/interrupt/gic/、https://gitee.com/LiteOS/Lite…。

  • 开关中断汇编实现代码

开关中断的函数 UINT32 ArchIntLock(VOID)、UINT32 ArchIntUnlock(VOID)、ArchIntRestore(UINT32 intSave)是基于汇编语言实现的,依据不同的 CPU 架构,散布在下述文件里:archarmcortex_a_rincludearchinterrupt.h、archarm64includearchinterrupt.h、archarmcortex_msrcdispatch.S。

  • 开发指南中断文档

在线文档 https://gitee.com/LiteOS/Lite…。

咱们先看看中断的相干概念,具体的介绍,请参考 LiteOS 开发指南中断文档。

1、中断概念介绍

中断是指呈现须要时,CPU 暂停执行以后程序,转而执行新程序的过程。当外设须要 CPU 时,将通过产生中断信号使 CPU 立刻中断当前任务来响应中断请求。在分析中断源代码之前,上面介绍些中断相干的硬件、中断相干的概念。

1.1 中断相干的硬件介绍

与中断相干的硬件能够划分为三类:设施、中断控制器、CPU 自身。

  • 设施

发动中断的源,当设施须要申请 CPU 时,产生一个中断信号,该信号连贯至中断控制器。

  • 中断控制器

中断控制器是 CPU 泛滥外设中的一个,它一方面接管其它外设中断引脚的输出。另一方面,它会收回中断信号给 CPU。能够通过对中断控制器编程来关上和敞开中断源、设置中断源的优先级和触发形式。

  • CPU

CPU 会响应中断源的申请,中断以后正在执行的工作,转而执行中断处理程序。

1.2 中断相干的概念

  • 中断号

每个中断请求信号都会有特定的标记,使得计算机可能判断是哪个设施提出的中断请求,这个标记就是中断号。

  • 中断优先级

为使零碎可能及时响应并解决所有中断,零碎依据中断工夫的重要性和紧迫水平,将中断源分为若干个级别,称作中断优先级。

  • 中断处理程序

当外设产生中断请求后,CPU 暂停以后的工作,转而响应中断申请,即执行中断处理程序。产生中断的每个设施都有相应的中断处理程序。

  • 中断向量

中断服务程序的入口地址。

  • 中断向量表

存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中依照中断号顺序存储。

  • 中断共享

当外设较少时,能够实现一个外设对应一个中断号,但为了反对更多的硬件设施,能够让多个设施共享一个中断号,共享同一个中断号的中断处理程序造成一个链表。当外部设备产生中断申请时,零碎会遍历中断号对应的中断处理程序链表,直到找到对应设施的中断处理程序。在遍历执行过程中,各中断处理程序能够通过检测设施 ID,判断是否是这个中断处理程序对应的设施产生的中断。

咱们再看看 LiteOS 内核中断源代码。

2、LiteOS 内核中断源代码

2.1 中断相干的构造体

在文件 kernelbaseincludelos_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;

kernelincludelos_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()

在系统启动时,在 kernelinitlos_init.c 中调用 OsHwiInit()进行中断初始化。这个函数定义在 kernelbaselos_hwi.c,而后进一步调用定义在 targetsbsphwarminterruptnvicnvic.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(),而后调用 archarmcortex_mcmsiscore_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 行获取中断号的函数在 archarmcortex_mcmsiscmsis_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;
#endif
⑶      if (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 代码蕴含一个头文件 targetsbsphwincludenvic.h 和 C 源代码文件 targetsbsphwarminterruptnvicnvic.c。

3.1 nvic.h 头文件

NVIC 头文件 nvic.h 中,次要定义了一些宏,如中断寄存器的地址、各个系统中断号等,申明了如下三个函数,其中函数 IrqEntryV7M(VOID)在 targetsbsphwarminterruptnvicnvic.c 文件中定义,用于解决中断的程序,也叫做中断向量;函数 VOID Reset_Handler(VOID)在汇编启动文件 targetsSTM32F769IDISCOVERYlos_startup_gcc.S 中定义的,用于复位、启动时的解决;函数 VOID osPendSV(VOID)定义在 archarmcortex_msrcdispatch.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()定义在文件 archarmcortex_mcmsiscmsis_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,而后调用⑵处代码执行定义在 archarmcortex_mcmsiscore_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 核,还须要执行⑺设置优先级组。最初,调用定义在 kernelbaselos_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、开关中断

最初,分享下开关中断的相干常识,开、关中断别离指的是:

  • 开中断

执行结束特定的短暂的程序,关上中断,能够响应中断了。

  • 关中断

为了爱护执行的程序不被打断,敞开相应内部的中断。

对应的开关中断的函数定义在 kernelincludelos_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/Lite…。为了更容易找到 LiteOS 代码仓,倡议拜访 https://gitee.com/LiteOS/LiteOS,关注 Watch、点赞Star、并Fork 到本人账户下,如下图,谢谢。

本文分享自华为云社区《LiteOS 内核源码剖析系列三 中断 Hwi》,原文作者:zhushy。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版