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;
}
复制代码
深圳网站建设www.sz886.com