目录
Generic Timer介绍
1.1 硬件结构
1.1.1 System Counter特性
1. 两种访问方式
2. CP15寄存器
3. MemoryMapped寄存器
1.1.2 Timer特性
1.2 SystemCounter时钟源
1.3 使用方法
GenericTimer源码分析
1.2 源码分析
1.2.1 初始化
1.2.2 启动Timer
1.2.3 中断处理
参考资料:
ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
《B8: The Generic Timer》
《D5: System Level Implementation of the Generic Timer》
STM32MP157芯片手册
DM00327659.pdf
《46 System timer generator (STGEN)》
Linux时间子系统之(十七):ARM generic timer驱动代码分析
《SEC_Exynos 4412 SCP_Users Manual_Ver.0.10.00_Preliminary》
Generic Timer介绍
1.1 硬件结构
在操作系统中,需要一个系统时钟,各类芯片都有自己的定时器,它们的编程方法互不相同,这给系统移植带来麻烦。 Generic Timer是ARM推荐的一种硬件实现实现,可以实现统一的编程方法。 Generic Timer分为两部分:共享的System Counter、各个Processor专有的Timer。
-
System Counter:给所有Processor提供统一的时间
-
Timer:可以设置周期性的事件,给Processor提供中断信号
下图是Generic Timer的硬件框图,红线表示时钟:System counter是时钟源,进入Porcessor中的Timer。 蓝线表示中断:Porcessor中的Timer产生的中断进入GIC,作为PPI(Private Peripheral Interrupt)传给Processor。 System Counter是系统级别的(System Level),给整个系统提供时钟,可以使用Memeory Mapped
的寄存器来访问。
每个Processor里都有一个Timer,这些Timer可以发出周期性的中断,给Processor提供系统时钟。Timer需要使用CP15协处理器命令来访问。
1.1.1 System Counter特性
规格 | 描述 |
---|---|
位宽(Width) | 至少56位,跟硬件实现。 读取时,可以得到64位的数值。 |
频率(Frequency) | 1M~50MHz,增加值可以调整: 比如时钟为8MHz时,每来一个时钟计数值增加1, 设置为4MHz时,每来一个时钟计数值增加2, 降低频率时可以降低功耗,同时增加步进值以维持时钟精度 |
溢出(Roll-over) | 不少于40年 |
精度(Accuracy) | 推荐:误差在24小时内不超过10秒 |
复位值(Start-up) | 从0开始 |
1. 两种访问方式
SystemCounter是给所有Processor使用的,它有两种访问方式:
-
CP15协处理器命令:某个Processor去访问它时可以使用CP15协处理器命令。
-
MemoryMapped寄存器:
-
既然它是给所有Processor使用的,那么应该提供更高级的访问方法(System Level)
-
而且有些Processor并没有实现CP15,所有也应该提供MemoryMapped的方法
-
2. CP15寄存器
下面这个表格列出了所有的寄存器,包括SystemCounter和Timer,不仅仅是SystemCounter。
3. MemoryMapped寄存器
这些寄存器在下列手册描述得比较清楚:
-
DM00327659.pdf
的《46 System timer generator (STGEN)》 -
ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
的Table D5-1(下图):
在u-boot代码中可以看到这样的结构体:
/* System Counter */ struct sctr_regs {u32 cntcr; // control register, 启动/停止u32 cntsr; // status register, 是否启动/停止, 使用哪个频率u32 cntcv1; // count value lower registeru32 cntcv2; // count value upper register, cntcv1和cntcv2组成64位的计数值u32 resv1[4];u32 cntfid0; // base frequency register, 必须等于SystemCounter的输入频率u32 cntfid1; // cntfid1和cntfid2:其他频率u32 cntfid2;u32 resv2[1001];u32 counterid[1]; };
1.1.2 Timer特性
每个Processor都有一个Timer,它有3个寄存器,只能使用协处理器命令方位(CP15):
-
64位的比较寄存器(CVAL):当SystemCounter的值等于它时,产生事件(中断)
-
SystemCounter总是增长的,所以Timer的64位比较寄存器也只能设置为大于SystemCounter的值
-
-
被称为upcounter
-
32位的TimerValue寄存器(TVAL)
-
它是downconter
-
比如设置为1000,表示再经过1000个时钟之后,就会产生事件(中断)
-
实质是:设置64位的比较寄存器,让它等于SystemCounter+1000
-
-
32位的控制寄存器(CTL)
-
使能/禁止Timer
-
使能输出:是否能产生事件(中断)
-
状态:是否能产生了事件(中断)
-
1.2 SystemCounter时钟源
SystemCounter的时钟源,跟芯片设计相关。
以STM32MP157为例:
1.3 使用方法
-
设置时钟源:芯片相关,一般u-boot里做好了
-
设置/启动SystemCounter
-
设置Processor的Timer:
-
设置比较值、使能中断、使能Timer
-
注册中断处理函数
-
(我不知道exynos4412太老了还是三星和其它厂商不一样,同样的ARM架构但是他的芯片里没有叫通用定时器的东西。只有下面这几个定时器加一个系统时钟,从功能上来看这个东西其实应该就是系统时钟,我的猜测不一定对。)
GenericTimer源码分析
1.2 源码分析
代码:kernel\liteos_a\platform\hw\arm\timer\arm_generic\arm_generic_timer.c
/** Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.* Copyright (c) 2020, Huawei Device Co., Ltd. All rights reserved.** Redistribution and use in source and binary forms, with or without modification,* are permitted provided that the following conditions are met:** 1. Redistributions of source code must retain the above copyright notice, this list of* conditions and the following disclaimer.** 2. Redistributions in binary form must reproduce the above copyright notice, this list* of conditions and the following disclaimer in the documentation and/or other materials* provided with the distribution.** 3. Neither the name of the copyright holder nor the names of its contributors may be used* to endorse or promote products derived from this software without specific prior written* permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/#include "los_hw_pri.h"
#include "los_tick_pri.h"
#include "los_sys_pri.h"
#include "gic_common.h"#define STRING_COMB(x, y, z) x ## y ## z#ifdef LOSCFG_ARCH_SECURE_MONITOR_MODE
#define TIMER_REG(reg) STRING_COMB(TIMER_REG_, CNTPS, reg)
#else
#define TIMER_REG(reg) STRING_COMB(TIMER_REG_, CNTP, reg)
#endif
#define TIMER_REG_CTL TIMER_REG(_CTL) /* 32 bits */
#define TIMER_REG_TVAL TIMER_REG(_TVAL) /* 32 bits */
#define TIMER_REG_CVAL TIMER_REG(_CVAL) /* 64 bits */
#define TIMER_REG_CT TIMER_REG(CT) /* 64 bits */#ifdef __LP64__#define TIMER_REG_CNTFRQ cntfrq_el0/* CNTP AArch64 registers */
#define TIMER_REG_CNTP_CTL cntp_ctl_el0
#define TIMER_REG_CNTP_TVAL cntp_tval_el0
#define TIMER_REG_CNTP_CVAL cntp_cval_el0
#define TIMER_REG_CNTPCT cntpct_el0/* CNTPS AArch64 registers */
#define TIMER_REG_CNTPS_CTL cntps_ctl_el1
#define TIMER_REG_CNTPS_TVAL cntps_tval_el1
#define TIMER_REG_CNTPS_CVAL cntps_cval_el1
#define TIMER_REG_CNTPSCT cntpct_el0#define READ_TIMER_REG32(reg) AARCH64_SYSREG_READ(reg)
#define READ_TIMER_REG64(reg) AARCH64_SYSREG_READ(reg)
#define WRITE_TIMER_REG32(reg, val) AARCH64_SYSREG_WRITE(reg, (UINT64)(val))
#define WRITE_TIMER_REG64(reg, val) AARCH64_SYSREG_WRITE(reg, val)#else /* Aarch32 */#define TIMER_REG_CNTFRQ CP15_REG(c14, 0, c0, 0)/* CNTP AArch32 registers */
#define TIMER_REG_CNTP_CTL CP15_REG(c14, 0, c2, 1)
#define TIMER_REG_CNTP_TVAL CP15_REG(c14, 0, c2, 0)
#define TIMER_REG_CNTP_CVAL CP15_REG64(c14, 2)
#define TIMER_REG_CNTPCT CP15_REG64(c14, 0)/* CNTPS AArch32 registers are banked and accessed though CNTP */
#define CNTPS CNTP#define READ_TIMER_REG32(reg) ARM_SYSREG_READ(reg)
#define READ_TIMER_REG64(reg) ARM_SYSREG64_READ(reg)
#define WRITE_TIMER_REG32(reg, val) ARM_SYSREG_WRITE(reg, val)
#define WRITE_TIMER_REG64(reg, val) ARM_SYSREG64_WRITE(reg, val)#endif#define OS_CYCLE_PER_TICK (g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND)UINT32 HalClockFreqRead(VOID)
{return READ_TIMER_REG32(TIMER_REG_CNTFRQ);
}VOID HalClockFreqWrite(UINT32 freq)
{WRITE_TIMER_REG32(TIMER_REG_CNTFRQ, freq);
}STATIC_INLINE VOID TimerCtlWrite(UINT32 cntpCtl)
{WRITE_TIMER_REG32(TIMER_REG_CTL, cntpCtl);
}STATIC_INLINE UINT64 TimerCvalRead(VOID)
{return READ_TIMER_REG64(TIMER_REG_CVAL);
}STATIC_INLINE VOID TimerCvalWrite(UINT64 cval)
{WRITE_TIMER_REG64(TIMER_REG_CVAL, cval);
}STATIC_INLINE VOID TimerTvalWrite(UINT32 tval)
{WRITE_TIMER_REG32(TIMER_REG_TVAL, tval);
}UINT64 HalClockGetCycles(VOID)
{UINT64 cntpct;cntpct = READ_TIMER_REG64(TIMER_REG_CT);return cntpct;
}LITE_OS_SEC_TEXT VOID OsTickEntry(VOID)
{TimerCtlWrite(0);OsTickHandler();/** use last cval to generate the next tick's timing is* absolute and accurate. DO NOT use tval to drive the* generic time in which case tick will be slower.*/TimerCvalWrite(TimerCvalRead() + OS_CYCLE_PER_TICK);TimerCtlWrite(1);
}LITE_OS_SEC_TEXT_INIT VOID HalClockInit(VOID)
{UINT32 ret;g_sysClock = HalClockFreqRead();ret = LOS_HwiCreate(OS_TICK_INT_NUM, MIN_INTERRUPT_PRIORITY, 0, OsTickEntry, 0);if (ret != LOS_OK) {PRINT_ERR("%s, %d create tick irq failed, ret:0x%x\n", __FUNCTION__, __LINE__, ret);}
}LITE_OS_SEC_TEXT_INIT VOID HalClockStart(VOID)
{HalIrqUnmask(OS_TICK_INT_NUM);/* triggle the first tick */TimerCtlWrite(0);TimerTvalWrite(OS_CYCLE_PER_TICK);TimerCtlWrite(1);
}VOID HalDelayUs(UINT32 usecs)
{UINT64 cycles = (UINT64)usecs * HalClockFreqRead() / OS_SYS_US_PER_SECOND;UINT64 deadline = HalClockGetCycles() + cycles;while (HalClockGetCycles() < deadline) {__asm__ volatile ("nop");}
}UINT64 hi_sched_clock(VOID)
{return LOS_CurrNanosec();
}UINT32 HalClockGetTickTimerCycles(VOID)
{UINT64 cval = TimerCvalRead();UINT64 cycles = HalClockGetCycles();return (UINT32)((cval > cycles) ? (cval - cycles) : 0);
}VOID HalClockTickTimerReload(UINT32 cycles)
{HalIrqMask(OS_TICK_INT_NUM);HalIrqClear(OS_TICK_INT_NUM);TimerCtlWrite(0);TimerCvalWrite(HalClockGetCycles() + cycles);TimerCtlWrite(1);HalIrqUnmask(OS_TICK_INT_NUM);
}VOID HalClockIrqClear(VOID) {}
1.2.1 初始化
它做了2件事:
-
读出SystemCounter的频率:以后设置中断周期时要用
-
注册中断处理函数
LITE_OS_SEC_TEXT_INIT VOID HalClockInit(VOID)
{UINT32 ret;
g_sysClock = HalClockFreqRead();ret = LOS_HwiCreate(OS_TICK_INT_NUM, MIN_INTERRUPT_PRIORITY, 0, OsTickEntry, 0);if (ret != LOS_OK) {PRINT_ERR("%s, %d create tick irq failed, ret:0x%x\n", __FUNCTION__, __LINE__,ret);}
}
1.2.2 启动Timer
它做了2件事:
-
使能中断:中断号是29
-
设置TimerValue寄存器:
-
OS_CYCLE_PER_TICK = g_sysClock / 100,也就是10MS之后产生中断
-
设置TimerValue寄存器的实质,就是设置比较寄存器(CVAL) = 当前SystemCounter值 + OS_CYCLE_PER_TICK
-
LITE_OS_SEC_TEXT_INIT VOID HalClockStart(VOID)
{HalIrqUnmask(OS_TICK_INT_NUM);
/* triggle the first tick */TimerCtlWrite(0);TimerTvalWrite(OS_CYCLE_PER_TICK);TimerCtlWrite(1);
}
1.2.3 中断处理
它做了2件事:
-
调用OsTickHandler
-
设置下一次中断时间:
-
设置比较寄存器(CVAL) = 当前比较寄存器值 + OS_CYCLE_PER_TICK
-
为什么不是当前SystemCounter值 + OS_CYCLE_PER_TICK?
-
因为处理中断也是要时间的,SystemCounter值一直在增加
-
要让两次中断的间隔非常精确的话,要使用发生中断时的SystemCounter值,也就是比较寄存器的当前值
-
LITE_OS_SEC_TEXT VOID OsTickEntry(VOID)
{TimerCtlWrite(0);
OsTickHandler();
/** use last cval to generate the next tick's timing is* absolute and accurate. DO NOT use tval to drive the* generic time in which case tick will be slower.*/TimerCvalWrite(TimerCvalRead() + OS_CYCLE_PER_TICK);TimerCtlWrite(1);
}
这张图片是对三个函数在定时器中的作用的表述,韦东山老师视频里画的,其实就是一个定时器的使用,没什么特别的东西,这期有点水哈哈哈哈哈,基本都是韦老师的笔记。因为定时器这个东西从学51开始我就学了,真的是没什么好说的的了。
STM32时钟与定时器_stm32定时器时钟-CSDN博客
Linux内核定时器-CSDN博客
我以前写过两篇相关的一个是M核的32用的库函数,一个是A核的跑了linux。这些都差不多。