The kernel time of Zephyr is driven by sys_clock_announce
. Regardless of the SOC type, the system timer is driven by interfacing with this API. For details, refer to the Zephyr Tick Clock Introduction. All system timer drivers for Zephyr are located under zephyr/drivers/timer
.
How Zephyr Chooses Which Driver to Use
All system timer drivers supported by Zephyr are placed under zephyr/drivers/timer
. During the build process, Zephyr identifies the timer in the device tree, converting the device tree into Kconfig configuration items. The Kconfig files under zephyr/drivers/timer
select the corresponding driver to include in the build based on these configuration items. For example, the Cortex-M series kernel can choose to use the systick timer on the SOC as the system timer, specified in the device tree.
&systick { status = "okay";};
The complete information for the corresponding node is as follows:
systick: timer@e000e010 { compatible = "arm,armv7m-systick"; reg = <0xe000e010 0x10>; };
During the build, Zephyr’s devicetree/kconfig build system converts the device tree into build/Kconfig/Kconfig.dts
, which includes:
DT_COMPAT_ARM_ARMV7M_SYSTICK := arm,armv7m-systick
config DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED def_bool $( dt_compat_enabled,$ ( DT_COMPAT_ARM_ARMV7M_SYSTICK ))
With arm,armv7m-systick
present, DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
is set to true. The Kconfig in zephyr/drivers/timer
includes the Kconfig for systick.
source "drivers/timer/Kconfig.cortex_m_systick"
In zephyr/drivers/timer/Kconfig.cortex_m_systick
, since DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
is true, CORTEX_M_SYSTICK
is selected.
config CORTEX_M_SYSTICK bool "Cortex-M SYSTICK timer" depends on CPU_CORTEX_M_HAS_SYSTICK default y depends on DT_HAS_ARM_ARMV6M_SYSTICK_ENABLED ||
DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED ||
DT_HAS_ARM_ARMV8M_SYSTICK_ENABLED ||
DT_HAS_ARM_ARMV8_1M_SYSTICK_ENABLED
In zephyr/drivers/timer/CMakeLists.txt
, there is the following content:
zephyr_library_sources_ifdef ( CONFIG_CORTEX_M_SYSTICK cortex_m_systick.c )
Since CORTEX_M_SYSTICK
is selected, cortex_m_systick.c
will be included in the build, selecting the correct system clock driver.
Implementation and Configuration of System Clock Driver
The driver for the system timer depends on the hardware but can generally be divided into the following parts:
Initialization
Implement the timer initialization function to initialize the system timer based on the chip characteristics, which involves writing to registers.
static int sys_clock_driver_init ( void ){
NVIC_SetPriority ( SysTick_IRQn, _IRQ_PRIO_OFFSET ) ; last_load = CYC_PER_TICK - 1; overflow_cyc = 0U; SysTick->LOAD = last_load; SysTick->VAL = 0; /* resets timer to last_load */ SysTick->CTRL |= ( SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_CLKSOURCE_Msk ) ; return 0;}
The timer is used to generate ticks, calculating how many cycles each tick requires:
# define CYC_PER_TICK ( sys_clock_hw_cycles_per_sec ( )
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC )
sys_clock_hw_cycles_per_sec ( )
typically returns CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
. Thus, the configuration of the system timer depends on both CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
and CONFIG_SYS_CLOCK_TICKS_PER_SEC
. The Kconfig in Zephyr’s SOC reads the system-clock
node’s clock-frequency
from the device tree as the default value for CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
. For example, in the rt series SOC, the file zephyr/soc/arm/nxp_imx/rt/Kconfig.defconfig.series
contains:
config SYS_CLOCK_HW_CYCLES_PER_SEC default $( dt_node_int_prop_int,$ ( DT_SYSCLK_PATH ) ,clock-frequency ) if SOC_SERIES_IMX_RT10XX && CORTEX_M_SYSTICK default 32768 if MCUX_GPT_TIMER
Reading the default system-clock from the device tree:
sysclk: system-clock { compatible = "fixed-clock"; clock-frequency = <600000000>; #clock-cells = <0>; };
Of course, if different boards need to adjust the frequency of the system timer, they can modify CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
in the configuration file under the board. Similarly, application configuration files can also modify CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
. The number of ticks per second CONFIG_SYS_CLOCK_TICKS_PER_SEC
is an operating system-level configuration and has no direct relation to the hardware. Zephyr directly provides a default value in zephyr/kernel/Kconfig
:
config SYS_CLOCK_TICKS_PER_SEC int "System tick frequency ( in ticks/second ) " default 100 if QEMU_TARGET || SOC_POSIX default 10000 if TICKLESS_KERNEL default 100
In tickless mode, it uses 10K. Generally, to reduce the overhead of context switching in the kernel, tickless is enabled, so the number of ticks per second in Zephyr is usually 10K. However, different SOCs may adjust this based on their characteristics. For example, the Nordic NRF series SOC specifies a different value in zephyr/soc/arm/nordic_nrf/Kconfig.defconfig
:
config SYS_CLOCK_TICKS_PER_SEC default 128 if !TICKLESS_KERNEL default 32768
Interrupt Handling
In the interrupt, ticks are calculated, and sys_clock_announce
is used for updates. For details, refer to the Zephyr Tick Clock Introduction.
API
The API for the system timer is defined in zephyr/include/zephyr/drivers/timer/system_timer.h
and needs to be implemented based on software and hardware characteristics.
extern void sys_clock_set_timeout ( int32_t ticks, bool idle ) ;extern void sys_clock_idle_exit ( void ) ;extern void sys_clock_announce ( int32_t ticks ) ;extern uint32_t sys_clock_elapsed ( void ) ;extern void sys_clock_disable ( void ) ;uint32_t sys_clock_cycle_get_32 ( void ) ;uint64_t sys_clock_cycle_get_64 ( void ) ;
Except for sys_clock_cycle_get_64
, other APIs have been discussed in the Zephyr Tick Clock Introduction. However, that article is quite old, and readers should replace z_clock
with sys_clock
when reading. sys_clock_cycle_get_64
and sys_clock_cycle_get_32
have similar functions, but 64 has higher counting precision and will not overflow during effective use.
How Zephyr Chooses Between Two System Clocks in the Device Tree
For the NXP RT series, in addition to the systick, there is also a GPT timer on the SOC that can serve as a system timer in Zephyr. It can be enabled in the device tree:
&gpt_hw_timer{ status = "okay";}
The corresponding node for GPT is:
gpt_hw_timer: gpt@401ec000 { compatible = "nxp,gpt-hw-timer"; reg = <0x401ec000 0x4000>; interrupts = <100 0>; status = "disabled"; };
During the build, Zephyr’s devicetree/kconfig build system converts the device tree into build/Kconfig/Kconfig.dts
, which includes:
DT_COMPAT_ARM_ARMV7M_SYSTICK := arm,armv7m-systick
config DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED def_bool $( dt_compat_enabled,$ ( DT_COMPAT_ARM_ARMV7M_SYSTICK ))
With arm,armv7m-systick
, DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
is set to true. The Kconfig in zephyr/drivers/timer
includes the Kconfig for GPT, setting DT_HAS_NXP_GPT_HW_TIMER_ENABLED
to true based on the state of the GPT device tree.
DT_COMPAT_NXP_GPT_HW_TIMER := nxp,gpt-hw-timer
config DT_HAS_NXP_GPT_HW_TIMER_ENABLED def_bool $( dt_compat_enabled,$ ( DT_COMPAT_NXP_GPT_HW_TIMER ))
In zephyr/drivers/timer/Kconfig.mcux_gpt
, the condition for DT_HAS_NXP_GPT_HW_TIMER_ENABLED
will enable MCUX_GPT_TIMER
.
config MCUX_GPT_TIMER bool "MCUX GPT Event timer" default y depends on PM depends on DT_HAS_NXP_GPT_HW_TIMER_ENABLED
In zephyr/drivers/timer/CMakeLists.txt
, mcux_gpt_timer.c
will be included in the build:
zephyr_library_sources_ifdef ( CONFIG_MCUX_GPT_TIMER mcux_gpt_timer.c )
Now you might wonder if both gpt_hw_timer and systick are enabled in the device tree, and sys_clock_announce is driven by both timers, will there be confusion? This has already been handled in zephyr/soc/arm/nxp_imx/rt/Kconfig.defconfig.series
:
config CORTEX_M_SYSTICK default n if MCUX_GPT_TIMER
When MCUX_GPT_TIMER
is enabled, CORTEX_M_SYSTICK
will be disabled, meaning cortex_m_systick.c
will not be included in the build, allowing only GPT to drive sys_clock_announce
without confusion.