1.原理概述
SylixOS开发人员在编写中断驱动时经常会遇到多个中断源共用一个中断号的情况,但在驱动中为了保证代码独立性,各个外设的中断服务函数应该放在各自的C文件中编写,用统一的中断服务函数是不合理的。为了适应这种情况,SylixOS支持队列类型中断向量,即SylixOS内核将同一中断向量号的多个中断服务函数链接成队列,执行时依次执行。
2.技术实现
2.1应用举例
下面以at91sam9x25处理器为例,该处理器的调试串口,tick时钟等外设共用1号中断向量。如图2-1所示。
图2-1 中断向量
中断向量详见AT91SAM9X25芯片手册8.2节Peripheral Identifiers部分。
2.2代码分析
SylixOS开发人员需要将1号中断向量设置成队列类型中断向量,设置方法如程序清单1所示。
程序清单 1 中断初始化
VOID bspIntInit (VOID) { interruptInit(); /* 中断控制器初始化 */ API_InterVectorSetFlag(ID_SYS, LW_IRQ_FLAG_QUEUE); /* 设置ID_SYS为单向量、多服务 */ }
bspIntInit函数位于bspLib.c中,用来初始化中断控制器。
ID_SYS是1号中断向量,LW_IRQ_FLAG_QUEUE宏是队列类型中断向量的标志,该标志必须在安装任何一个驱动前设置, 且设置后不能再取消,因此应该在 bspIntInit函数中完成设置。
API_InterVectorSetFlag函数用来设置中断向量属性,代码实现如程序清单2所示。
程序清单 2 中断向量属性设置函数
LW_API ULONG API_InterVectorSetFlag (ULONG ulVector, ULONG ulFlag) { INTREG iregInterLevel; PLW_CLASS_INTDESC pidesc; if (_Inter_Vector_Invalid(ulVector)) { _ErrorHandle(ERROR_KERNEL_VECTOR_NULL); return (ERROR_KERNEL_VECTOR_NULL); } pidesc = LW_IVEC_GET_IDESC(ulVector); LW_SPIN_LOCK_QUICK(&pidesc->IDESC_slLock, &iregInterLevel); /* 关闭中断同时锁住 spinlock */ if (LW_IVEC_GET_FLAG(ulVector) & LW_IRQ_FLAG_QUEUE) { /* 已经是 QUEUE 类型中断向量 */ LW_IVEC_SET_FLAG(ulVector, ulFlag | LW_IRQ_FLAG_QUEUE); } else { LW_IVEC_SET_FLAG(ulVector, ulFlag); /* 中断向量选项设置 */ } LW_SPIN_UNLOCK_QUICK(&pidesc->IDESC_slLock, iregInterLevel); /* 打开中断, 同时打开 spinlock */ return (ERROR_NONE); }
设置完成后,不同外设多次调用API_InterVectorConnect 函数设置1号中断向量的多个中断服务函数,以AT91SAM9X25 调试串口中断服务函数和tick时钟中断服务函数为例,如程序清单3、程序清单4所示。
程序清单 3 tick中断服务函数安装
API_InterVectorConnect(ulVector, (PINT_SVR_ROUTINE)__tickTimerIsr, /* tick中断服务函数 */ LW_NULL, "tick_timer");
程序清单 4 串口中断服务函数安装
API_InterVectorConnect(sam9x25UartIntNum(iChannelNum), (PINT_SVR_ROUTINE)sam9x25SioIsr, /* 串口中断服务函数安装 */ (PVOID)psiochanUart, pcIsrName);
Tick和串口中断向量号均为1。
API_InterVectorConnect函数将中断向量号和中断服务函数进行挂接时,会判断中断向量是否是队列类型中断向量,若是队列类型中断向量,则将中断服务函数加入中断队列,部分代码实现如程序清单5所示。
程序清单 5 中断服务函数挂接
LW_API ULONG API_InterVectorConnectEx (ULONG ulVector, PINT_SVR_ROUTINE pfuncIsr, VOIDFUNCPTR pfuncClear, PVOID pvArg, CPCHAR pcName) { ... ... if (LW_IVEC_GET_FLAG(ulVector) & LW_IRQ_FLAG_QUEUE) { /* 队列服务类型向量 */ for (plineTemp = pidesc->IDESC_plineAction; plineTemp != LW_NULL; plineTemp = _list_line_get_next(plineTemp)) { piactionOld = _LIST_ENTRY(plineTemp, LW_CLASS_INTACT, IACT_plineManage); if ((piactionOld->IACT_pfuncIsr == pfuncIsr) && (piactionOld->IACT_pvArg == pvArg)) { /* 中断处理函数是否被重复安装 */ break; } } if (plineTemp) { /* 此中断被重复安装 */ bNeedFree = LW_TRUE; } else { /* * 将中断服务函数加入到对应的中断向量号 * 下的中断服务函数列表 */ _List_Line_Add_Ahead(&piaction->IACT_plineManage, &pidesc->IDESC_plineAction); bNeedFree = LW_FALSE; } } else { /* 非队列服务式中断向量 */ ... ... }
当产生中断并执行中断服务函数时,SylixOS内核总中断服务函数API_InterVectorIsr会找到对应中断号的中断服务函数链表,并遍历链表,部分代码如程序清单6所示。
程序清单 6 总中断服务函数
LW_API irqreturn_t API_InterVectorIsr (ULONG ulVector) { ... ... for (plineTemp = pidesc->IDESC_plineAction; plineTemp != LW_NULL; plineTemp = _list_line_get_next(plineTemp)) { piaction = _LIST_ENTRY(plineTemp, LW_CLASS_INTACT, IACT_plineManage); { irqret = piaction->IACT_pfuncIsr(piaction->IACT_pvArg, ulVector); } /* * 根据中断服务函数返回值判断,中断 * 是否被处理。返回值为1时表示被处理 * 跳出循环;为0时表示未被处理,继续 * 遍历服务函数链表。 */ if (LW_IRQ_RETVAL(irqret)) { /* 中断是否已经被处理 */ piaction->IACT_iIntCnt[pcpu->CPU_ulCPUId]++; if (piaction->IACT_pfuncClear) { piaction->IACT_pfuncClear(piaction->IACT_pvArg, ulVector); } break; /* 跳出循环 */ } } ... ... }
由程序清单6可知,当中断向量号是队列类型中断向量时,中断服务函数返回值显得尤为重要,为0时表示中断函数未被处理,返回值为1时表示中断函数已经被处理,所以串口中断服务函数编写如程序清单7所示。
程序清单 7 串口中断函数
static irqreturn_t sam9x25SioIsr (SIO_CHAN *psiochanChan, ULONG ulVector) { ... ... /* * 得出具体是哪一个串口中断 */ uiStatus = readl(DBGU_BA + REG_US_CSR); uiPending = uiStatus & readl(DBGU_BA + REG_US_IMR); if(uiPending != 0) { /* 串口中断 */ if (sam9x25UartIsTxInt(uiPending)) { /* 发送中断 */ if (psiochanUart->pcbGetTxChar(psiochanUart->pvGetTxArg, &cChar) != ERROR_NONE) { sam9x25UartTxIntDisable(psiochanUart->iChannelNum); /* 关闭发送中断 */ } else { sam9x25UartTxPut(psiochanUart->iChannelNum, cChar); /* 发送数据 */ } return (LW_IRQ_HANDLED); } if (sam9x25UartIsRxInt(uiPending)) { /* 接收中断 */ if (sam9x25UartRxGet(psiochanUart->iChannelNum, &cChar) /* 接收数据 */ == ERROR_NONE) { psiochanUart->pcbPutRcvChar(psiochanUart->pvPutRcvArg, cChar); } else { } /* * 返回值是1,表示是串口中断,中断处理结束 */ return (LW_IRQ_HANDLED); } } /* * 返回值是0,表示不是串口中断,继续遍历中断服务函数链表 */ return (LW_IRQ_NONE); }
Tick中断服务函数编写如程序清单8所示。
程序清单 8 tick中断函数
static irqreturn_t __tickTimerIsr (VOID) { INT uiPending; uiPending = readl(REG_PIT_SR); if (uiPending & 1) { /* tick 中断 */ API_KernelTicksContext(); /* 保存被时钟中断的线程控制块 */ API_KernelTicks(); /* 内核 TICKS 通知 */ API_TimerHTicks(); /* 高速 TIMER TICKS 通知 */ timerIsr(); return (LW_IRQ_HANDLED); /* tick中断,中断结束 */ } /* * 不是tick中断,继续遍历中断服务函数 */ return (LW_IRQ_NONE); }
SylixOS开发人员在编写单向量、多中断中断服务函数时,中断服务函数中应该首先读取具体外设的状态寄存器以判断是否是该外设产生的中断,否则会进入其他外设中断,结束遍历中断服务函数链表,导致错误。