实现系统TICK初始化
通常情况下我们会选择一个定时器用于产生系统所需要的TICK中断,在全志R16平台上有两个24bit硬件定时器,使用时用户设置一个初始值,启动后定时器进行递减,如果减到0则可以产生一个中断。如果用户使用自动重载功能,硬件自动重新从初始值再次开始递,直到再次减到0产生中断,依次往复:
全志R16的定时器输入时钟和分频值可以通过通过自身寄存器进行设置,在本次教程中,我们固定使用24M进行2分频,也就是定时器输入时钟频率是12MHz。同时定时器工作模式选择为自动重载和使能中断模式。根据以上信息我们可以封装出三个接口:
#define __SYLIXOS_KERNEL #include <SylixOS.h> #include <linux/compat.h> #include "timer.h" #define TIMER_BASE 0x01c20c00 #define TIMER_IRQ_EN 0x00 #define TIMER_IRQ_STA 0x04 #define TIMER0_CTRL 0x10 #define TIMER0_INTV 0x14 #define TIMER0_CUR 0x18 #define TIMER1_CTRL 0x20 #define TIMER1_INTV 0x24 #define TIMER1_CUR 0x28 VOID timerStart (INT32 iNum, UINT32 uiHZ) { UINT32 uiCount; UINT32 uiIntvOffset; UINT32 uiCtrlOffset; if (0 == iNum) { uiIntvOffset = TIMER0_INTV; uiCtrlOffset = TIMER0_CTRL; } else if (1 == iNum) { uiIntvOffset = TIMER1_INTV; uiCtrlOffset = TIMER1_CTRL; } else { return ; } uiCount = TIMER_FREQ; uiCount /= uiHZ; writel(uiCount, TIMER_BASE + uiIntvOffset); writel(BIT(1) | BIT(2) | BIT(4), TIMER_BASE + uiCtrlOffset); while ((readl(TIMER_BASE + uiCtrlOffset) >> 1) & 0x01); writel(readl(TIMER_BASE + uiCtrlOffset) | BIT(0), TIMER_BASE + uiCtrlOffset); writel(readl(TIMER_BASE + TIMER_IRQ_EN) | BIT(iNum), TIMER_BASE + TIMER_IRQ_EN); } VOID timerIntClear (INT32 iNum) { if ((0 != iNum) && (1 != iNum)) return ; writel(readl(TIMER_BASE + TIMER_IRQ_STA) | BIT(iNum), TIMER_BASE + TIMER_IRQ_STA); } BOOL timerIsIntPending (INT32 iNum) { if ((0 != iNum) && (1 != iNum)) return FALSE; return (readl(TIMER_BASE + TIMER_IRQ_STA) & BIT(iNum)) ? TRUE : FALSE; }
- timerStart:使能定时器,需要将定时器输出的频率作为参数传入。
- timerIntClear:清除定时器模块中断状态。
- timerIsIntPending:检测定时器模块是否产生了中断。
因为这时候我们还没有初始化中断控制器,所以CPU是无法执行定时器对应的中断处理函数的。但是我们可以通过轮询定时器的中断状态寄存器来判断定时器是否正常工作了,因为如果正常工作的话是会产生中断的。
在driver目录下新建timer目录,然后将定时器驱动文件放在timer目录中,另外我们将定时器的频率和中断号放在timer.h头文件中供外部使用:
#define TIMER_FREQ (24 * 1000 * 1000 / 2) #define TIMER_VECTOR(x) ((x) + 50) VOID timerStart(INT32 iNum, UINT32 uiHZ); VOID timerIntClear(INT32 iNum); BOOL timerIsIntPending(INT32 iNum);
系统TICK的初始化是在bspLib.c中的bspTickInit 接口中实现的,在创建BSP模板时这个接口的大部分功能已经被实现了:
VOID bspTickInit (VOID) { #if TICK_IN_THREAD > 0 LW_CLASS_THREADATTR threadattr; #endif /* TICK_IN_THREAD > 0 */ ULONG ulVector = 0; #if TICK_IN_THREAD > 0 API_ThreadAttrBuild(&threadattr, (8 * LW_CFG_KB_SIZE), LW_PRIO_T_TICK, LW_OPTION_THREAD_STK_CHK | LW_OPTION_THREAD_UNSELECT | LW_OPTION_OBJECT_GLOBAL | LW_OPTION_THREAD_SAFE, LW_NULL); htKernelTicks = API_ThreadCreate("t_tick", (PTHREAD_START_ROUTINE)__tickThread, &threadattr, NULL); #endif /* TICK_IN_THREAD > 0 */ /* * TODO: 初始化硬件定时器, 频率为 LW_TICK_HZ, 类型为自动重装, 启动硬件定时器 * * 并将硬件定时器的向量中断号赋给 ulVector 变量 */ API_InterVectorConnect(ulVector, (PINT_SVR_ROUTINE)__tickTimerIsr, LW_NULL, "tick_timer"); API_InterVectorEnable(ulVector); }
通过其中的注释我们能很容易的明白这个接口要做的事情:初始化并启动定时器,然后注册中断处理函数并能使定时器对应的中断号。
SylixOS中使用API_InterVectorConnect 接口来注册中断处理函数:
LW_API ULONG API_InterVectorConnect(ULONG ulVector, PINT_SVR_ROUTINE pfuncIsr, PVOID pvArg, CPCHAR pcName);
- ulVector:中断号。
- pfuncIsr:中断处理函数。
- pvArg:中断处理函数参数。
- pcName:中断名字。
在arm平台上,这里的中断号就是芯片手册上对应外设中断的中断号。注册了中断处理函数之后,再使用API_InterVectorEnable 接口来使能这个中断号:
LW_API ULONG API_InterVectorEnable(ULONG ulVector);
这个接口最终会调用到bspLib.c中的bspIntVectorEnable 接口来操作中断控制器使能对应的中断。当然这里我们还没有实现这个接口,所以并不会真正的使能中断。
这个接口我们需要添加的代码就是调用定时器驱动中的timerStart 接口来初始化定时器:
VOID bspTickInit (VOID) { #if TICK_IN_THREAD > 0 LW_CLASS_THREADATTR threadattr; #endif /* TICK_IN_THREAD > 0 */ ULONG ulVector = TIMER_VECTOR(0); #if TICK_IN_THREAD > 0 API_ThreadAttrBuild(&threadattr, (8 * LW_CFG_KB_SIZE), LW_PRIO_T_TICK, LW_OPTION_THREAD_STK_CHK | LW_OPTION_THREAD_UNSELECT | LW_OPTION_OBJECT_GLOBAL | LW_OPTION_THREAD_SAFE, LW_NULL); htKernelTicks = API_ThreadCreate("t_tick", (PTHREAD_START_ROUTINE)__tickThread, &threadattr, NULL); #endif /* TICK_IN_THREAD > 0 */ /* * TODO: 初始化硬件定时器, 频率为 LW_TICK_HZ, 类型为自动重装, 启动硬件定时器 * * 并将硬件定时器的向量中断号赋给 ulVector 变量 */ API_InterVectorConnect(ulVector, (PINT_SVR_ROUTINE)__tickTimerIsr, LW_NULL, "tick_timer"); API_InterVectorEnable(ulVector); timerStart(0, LW_TICK_HZ); }
同时将ulVector 初始化为TIMER_VECTOR(0),因为我们用定时器0作为系统TICK中断来源。定时器初始化代码添加好之后我们来看看系统TICK的中断处理函数:
static irqreturn_t __tickTimerIsr (VOID) { /* * TODO: 通过设置硬件寄存器, 清除 tick 定时器中断 */ API_KernelTicksContext(); /* 保存被时钟中断的线程控制块 */ #if TICK_IN_THREAD > 0 API_ThreadResume(htKernelTicks); #else API_KernelTicks(); /* 内核 TICKS 通知 */ API_TimerHTicks(); /* 高速 TIMER TICKS 通知 */ #endif /* TICK_IN_THREAD > 0 */ return (LW_IRQ_HANDLED); }
中断处理函数中会调用一些系统接口以进行时间相关的处理工作,通过注释我们可以看出,我们需要将定时器的中断状态清除操作放在函数的最开始,因为如果不及时清除中断的话,中断处理结束后会不停地产生中断从而导致cpu一直在处理中断处理函数而不能干别的事情。我们只需要在开始调用timerIntClear 接口即可:
static irqreturn_t __tickTimerIsr (VOID) { /* * TODO: 通过设置硬件寄存器, 清除 tick 定时器中断 */ timerIntClear(0); API_KernelTicksContext(); /* 保存被时钟中断的线程控制块 */ #if TICK_IN_THREAD > 0 API_ThreadResume(htKernelTicks); #else API_KernelTicks(); /* 内核 TICKS 通知 */ API_TimerHTicks(); /* 高速 TIMER TICKS 通知 */ #endif /* TICK_IN_THREAD > 0 */ return (LW_IRQ_HANDLED); }
现在代码都设置完了,但是中断控制器驱动还没有编写,我们怎么知道定时器正常工作产生中断了呢?可答案就是可以在bspTickInit 的结尾添加定时器中断状态检测代码来辅助判断:
bspDebugMsg("timer start!\r\n"); while(!timerIsIntPending(0)); bspDebugMsg("there is a timer int!\r\n");
如果定时器工作异常没有产生中断的话,就会一直执行while语句,那么第二个信息就不会被打印出来,如果有第二句打印就表示定时器正常工作并产生中断了。
我们在这里可以将启动参数中的kdlog 改回为no ,因为在前面的教程中我们已经将SylixOS成功启动到Logo界面了,可以将内核日志输出关闭减少打印信息方便我们调试。
API_KernelStartParam("ncpus=1 kdlog=no kderror=yes kfpu=no heapchk=yes " "sldepcache=yes hz=1000 hhz=1000 " "rfsmap=/:/dev/ram");
到这里我们就可以重新编译内核启动看看效果了,但是在这之前还有一步工作需要做,那就是在bspMap.h中添加定时器寄存器的静态映射关系:
重新编译BSP工程,将新的SylixOS镜像拷贝到SD卡中用U-Boot启动:
可以看到我们之前添加的打印,说明定时器确实产生中断了。但是定时器产生中断的频率对不对呢?我们可以在初始化时将定时器的输出频率设置为1,也就是1s产生一个中断,这样在打印时我们会先看到timer start! 信息然后大概1s之后我们再看到there is a timer int! 信息。通过这种方法来辅助判断定时器输出频率是否正常:
timerStart(0, 1);
2023年6月2日 23:06 1F
啊:dan ge wo di shen