Understanding Zephyr System Timer Implementation

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.

Leave a Comment