Timer -3- (Clock Sources Subsystem)

 

Timer -3- (Clock Sources Subsystem)

리눅스는 시간 관리를 위해 지속적으로 tick을 발생하는 타이머나 프로그램 가능한 tick을 발생하는 타이머 등이 사용된다. arm SoC 제조업체들에서 이러한 클럭 소스에 대해 아키텍처 종속적인 코드가 제공되어 왔었고 다양한 클럭 하드웨어 시스템으로 인해 소스의 유지 관리가 매우 어려워 이를 최대한 아키텍처 독립적인 코드 부분을 만들어 연동하고자 clock sources subsystem이라는 framework가 만들어졌다.

 

아키텍처 타이머 vs 메모리 mapped 아키텍처 타이머

armv7 또는 armv8 아키텍처에서 generic timer extension이 적용된 경우 각 코어에 내장된 generic 타이머이다. 이 타이머는 SoC 제조 업체에서 설계에 따라 다음 2 가지로 나뉘어 사용된다.

  • 보조프로세서 cp15를 통해 타이머 레지스터를 사용하는 방법
  • 타이머 레지스터들을 주소 공간에 매핑하여 사용하는 방법

이 들은 다음과 같이 불린다.

  • 아키텍처 타이머
  • Local 타이머
  • Generic 타이머라고도 불린다.

 

아키텍처 타이머 디바이스 드라이버 “arm,armv7-timer”

rpi2 시스템은 클럭 소스로  “arm,armv7-timer” 타이머 디바이스  드라이버를 사용한다.

  • 보조프로세서 cp15를 사용하한다.
  • 설정 시 user 영역에서도 타이머에 접근할 수 있다
  • GIC의 PPIs를 사용하여 인터럽트를 전달한다.

arch/arm/boot/dts/bcm2708_common.dtsi

timer {
        compatible = "arm,armv7-timer";
        clock-frequency = <19200000>;
        interrupt-parent = <&local_intc>;
        interrupts = <3 0>, // PHYS_SECURE_PPI
                     <3 1>, // PHYS_NONSECURE_PPI
                     <3 3>, // VIRT_PPI
                     <3 2>; // HYP_PPI
        always-on;
};

rpi 및 rpi2에서 사용하는 타이머 디바이스 드라이버이다.

  • compatible
    • 타이머 디바이스 드라이버명 “arm,armv7-timer”
  • clock-frequency
    • 19.2Mhz 클럭 주기를 사용한다.
  • interrupt-parent
    • 만료 시간이 되면 local_intc 인터럽트 컨트롤러에 인터럽트가 발생된다.
  • interrupts
    • 4개의 타이머 각각에 대해 인터럽트가 발생된다.
  • always-on
    • Power block으로 관리되어 전원(power)을 다운하는 일 없이 항상 on(present) 상태에 있다.

 

메모리 mapped 아키텍처 타이머 디바이스 드라이버 “arm,armv7-timer-mem”

  • 타이머 레지스터를 별도의 주소 공간에 매핑하므로 추가로 reg 속성을 사용한다.
  • 타이머당 8개까지의 프레임을 사용할 수 있다.
  • GIC의 SPIs를 사용하여 인터럽트를 전달한다.

arch/arm/boot/dts/qcom-apq8084.dtsi

soc: soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        compatible = "simple-bus";

        timer@f9020000 {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;
                compatible = "arm,armv7-timer-mem";
                reg = <0xf9020000 0x1000>;
                clock-frequency = <19200000>;

                frame@f9021000 {
                        frame-number = <0>;
                        interrupts = <0 8 0x4>,
                                     <0 7 0x4>;
                        reg = <0xf9021000 0x1000>,
                              <0xf9022000 0x1000>;
                };

               (...생략...)

 

ARMv7 Generic Timer (rpi2)

특징

  • 코어마다 4개의 32bit down 카운터 및 64bit up 카운터 제공
    • Security Extension을 사용하지 않는 경우 physical 타이머와 virtual 타이머만 제공
    • Security Extension만 사용 시 Non-secure physical 타이머, Secure physical 타이머, virtual 타이머와 같이 3개 제공
    • Security & Virtualization Extension을 사용 시 Non-secure PL1 타이머, Secure PL1 physical 타이머, Non-secure PL2 physical 타이머, virtual 타이머로 4개 전부 제공
  • 아키텍처에 Generic 타이머를 Local 타이머 또는 아키텍처 타이머로도 불린다.
  • Generic 타이머는 ARM 아키텍처가 Generic Timer Extension을 지원하는 경우에 사용할 수 있다.
    • ARMv7 및 ARMv8에 채용되었다.
  • 연속(continuous) 및 one shot event 지원
    • GIC의 PPI를 통해 만료 타임 시 인터럽트 시그널을 보낼 수 있다.
  • 고해상도 타이머(hrtimer)

 

Generic 타이머 레지스터

 

BCM2708 시스템 타이머 (rpi)

특징

  • 1개의 타이머 제공
  • 연속(continuous) 모드만 지원
  • extra 클럭 분주기 레지스터
  • extra stop 디버그 모드 컨트롤 비트
  • 32bit 프리 러닝 카운터
  • ARM 시스템 타이머를 일부 개량하였다.
  • rpi: 1Mhz 클럭을 사용

 

시스템 타이머 레지스터

  • Timer Load
  • Timer Value (RO)
  • Timer Control (RW)
    • 아래 그림 참고
  • Timer IRQ Clear/Ack (WO)
    • 1을 기록 시 인터럽트 pending bit를 클리어한다.
    • 리셋 값: 0x003e_0020
  • Timer RAW IRQ (RO)
    • 인터럽트 pending bit 상태를 알아온다.
      • 0=clear, 1=set
  • Timer Masked IRQ (RO)
    • 인터럽트 마스크 상태를 알아온다.
      • 0=인터럽트 disable(mask), 1=인터럽트 enabled(unmask)
  • Timer Reload (RW)
    • Load 레지스터의 사본과 동일하다 다만 다른 점은 값을 설정해도 리로드가 즉시 발생하지 않는다.
  • Timer Pre-divider (RW)
    • APB 클럭을 설정된 lsb 10비트 값 + 1로 분주한다.
      • timer_clock = apb_clock / (pre_divider + 1)
    • 리셋 시 0x7d가 설정된다.
  • Timer Free Running Counter

 

Timer Control Register

ARM의 SP804는 8비트만 사용하는데 Boradcomm이 이를 확장하여 24bit를 운용한다.

  • 8bit pre-scaler를 사용하여 시스템 클럭 / (prescaler 값 + 1)이 사용된다.
  • 리셋 값: 0x003e_0020
    • pre-scaler: 62 (0x3e)
    • timer interrupt enable (bit5)

 

Device Tree 기반 clock source 초기화

다음 그림은 armv7 아키텍처에 내장된 generic 타이머를 사용하는 드라이버의 초기화 함수를 호출하는 예를 보여준다.

clocksource_of_init()

drivers/clocksource/clksrc-of.c

void __init clocksource_of_init(void)
{
        struct device_node *np;
        const struct of_device_id *match;
        of_init_fn_1 init_func;
        unsigned clocksources = 0;

        for_each_matching_node_and_match(np, __clksrc_of_table, &match) {
                if (!of_device_is_available(np))
                        continue;

                init_func = match->data;
                init_func(np);
                clocksources++;
        }
        if (!clocksources)
                pr_crit("%s: no matching clocksources found\n", __func__);
}

__clksrc_of_table 섹션에 등록된 디바이스들의 이름과 Device Tree로 부터 읽은 노드의 디바이스명이 매치되는 드라이버들의 초기화 함수들을 호출한다.

 

Generic Timer 초기화 -1- (bcm2709 case)

arch_timer_of_init()

drivers/clocksource/arm_arch_timer.c

static void __init arch_timer_of_init(struct device_node *np)
{
        int i;

        if (arch_timers_present & ARCH_CP15_TIMER) {
                pr_warn("arch_timer: multiple nodes in dt, skipping\n");
                return;
        }

        arch_timers_present |= ARCH_CP15_TIMER;
        for (i = PHYS_SECURE_PPI; i < MAX_TIMER_PPI; i++)
                arch_timer_ppi[i] = irq_of_parse_and_map(np, i);

        arch_timer_detect_rate(NULL, np);

        arch_timer_c3stop = !of_property_read_bool(np, "always-on");

        /*
         * If we cannot rely on firmware initializing the timer registers then
         * we should use the physical timers instead.
         */
        if (IS_ENABLED(CONFIG_ARM) &&
            of_property_read_bool(np, "arm,cpu-registers-not-fw-configured"))
                        arch_timer_use_virtual = false;

        arch_timer_init();
}
CLOCKSOURCE_OF_DECLARE(armv7_arch_timer, "arm,armv7-timer", arch_timer_of_init);
CLOCKSOURCE_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_of_init);

armv7 아키텍처에 내장된 Generic 타이머를 사용하는 디바이스 트리용 디바이스 드라이버의 초기화 함수이다.

  • 커널 v4.1-rc1부터 Generic Timer Description Table을 사용하는 구조로 바뀌면서 arch_timer_init() 함수를 호출하기 전에 이 함수를 먼저 호출하여 Device Tree로부터 필요한 속성값을 추출한다.

 

  • 코드 라인 5~10에서 디바이스 트리에서 cp15를 사용하는 방식의 generic 타이머 노드가 이 드라이버를 사용하여 초기화 함수로 진입하는 경우 한 번만 초기화하고 그 이후 호출되는 경우 skip 한다.
  • 코드 라인 11~12에서 4개의 타이머만큼 루프를 돌며 타이머 노드의 “intrrupts = <>” 속성 값으로 타이머 irq에 대한 매핑을 만들고 지정한다.
  • 코드 라인 14에서 타이머 노드의 “clock-frequency” 속성 값을 읽어 arch_timer_rate에 저장한다. 만일 읽어오는데 실패하는 경우 CNTFRQ(Counter Frequency Register)로부터 rate 값을 읽어온다.
  • 코드 라인 16에서 타이머 노드의 “always-on” 속성이 없는 경우 전역 arch_timer_c3stop을 true로 설정한다.
    • deep-sleep 상태의 코어에 대해 타이머 장치나 인터럽트 장치도 절전을 위해 파워가 off될 수 있는데 이러한 기능이 없는 시스템에서는 “always-on”을 설정하여 arch_timer_c3stop을 true로 한다. 이렇게 하여 c3stop 기능을 사용하지 않는 것으로 한다.
  • 코드 라인 22~24에서 타이머 노드의 “arm,cpu-registers-not-fw-configured” 속성이 있는 경우 전역 arch_timer_user_virtual에 false를 대입한다.
    • 전역 arch_timer_user_virtual의 기본 값은 true이다.
  • 코드 라인 26에서 디바이스 트리로부터 알아온 속성 값들을 가지고 실제 디바이스 드라이버 초기화 함수를 호출한다.

 

다음 그림은 디바이스 트리의 타이머 노드 정보를 읽어오는 모습을 보여준다. (예: rpi2)

 

arch_timer_detect_rate()

drivers/clocksource/arm_arch_timer.c

static void
arch_timer_detect_rate(void __iomem *cntbase, struct device_node *np)
{
        /* Who has more than one independent system counter? */
        if (arch_timer_rate)
                return;

        /* Try to determine the frequency from the device tree or CNTFRQ */
        if (of_property_read_u32(np, "clock-frequency", &arch_timer_rate)) {
                if (cntbase)
                        arch_timer_rate = readl_relaxed(cntbase + CNTFRQ);
                else
                        arch_timer_rate = arch_timer_get_cntfrq();
        }

        /* Check the timer frequency. */
        if (arch_timer_rate == 0)
                pr_warn("Architected timer frequency not available\n");
}

Device Tree 타이머 노드의 “clock-frequency” 속성 값을 읽어 arch_timer_rate에 저장한다. 만일 읽어오는데 실패하는 경우 CNTFRQ(Counter Frequency Register)로부터 rate 값을 읽어온다.

  • 코드 라인 5~6에서 이미 rate가 설정된 경우 함수를 빠져나간다.
  • 코드 라인 9에서 Device Tree 타이머 노드의 “clock-frequency” 속성 값을 읽어 arch_timer_rate에 저장한다.
  • 코드 라인 10~11에서 속성 값을 읽어오지 못하였고 카운터 레지스터 base 주소가 지정된 경우 그 주소 + CNTFRQ에서 rate 값을 읽어온다.
  • 코드 라인 12~13에서 속성 값을 읽어오지 못하였고 카운터 레지스터 base 주소가 지정되지 않은 경우 CNTFRQ(Counter Frequency Register)로부터 rate 값을 읽어온다.

 

arch_timer_get_cntfrq()

arch/arm/include/asm/arch_timer.h

static inline u32 arch_timer_get_cntfrq(void)
{
        u32 val;
        asm volatile("mrc p15, 0, %0, c14, c0, 0" : "=r" (val));
        return val;
}

CNTFRQ(Counter Frequency Register)로부터 rate 값을 읽어 반환한다.

  • CNTFRQ(Counter Frequency Register)
    • 시스템 카운터의 주파수가 담겨있다. 32bit 값을 사용한다.
    • rpi2: 19,200,000 (19.2Mhz)

 

Generic Timer 초기화 -2- (bcm2709 case)

arch_timer_init()

drivers/clocksource/arm_arch_timer.c

static void __init arch_timer_init(void)
{
        /*
         * If HYP mode is available, we know that the physical timer
         * has been configured to be accessible from PL1. Use it, so
         * that a guest can use the virtual timer instead.
         *
         * If no interrupt provided for virtual timer, we'll have to
         * stick to the physical timer. It'd better be accessible...
         */
        if (is_hyp_mode_available() || !arch_timer_ppi[VIRT_PPI]) {
                arch_timer_use_virtual = false;

                if (!arch_timer_ppi[PHYS_SECURE_PPI] ||
                    !arch_timer_ppi[PHYS_NONSECURE_PPI]) {
                        pr_warn("arch_timer: No interrupt available, giving up\n");
                        return;
                }
        }

        arch_timer_register();
        arch_timer_common_init();
}

ARMv7 아키텍처 타이머를 사용하여 각 타이머들을 초기화한다.

  • 코드 라인 11~19에서 하이퍼 모드가 사용가능한 경우 arch_timer_use_virtual을 false로 바꾼다. 하이퍼 모드를 사용할 수 있는 경우 guest os에서 virtual 타이머를 사용하도록 양보하고 자신은 physical 타이머를 선택한다. 만일 physical  secure physical 타이머와  non-secure physical 타이머가 준비되지 않았으면 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 21에서 현재 커널이 사용하도록 지정된 Generic 타이머를 per-cpu 인터럽트에 등록하고 boot cpu용은 즉시 enable하고 클럭 이벤트 디바이스에 등록한다.
  • 코드 라인 22에서 현재 커널이 사용하도록 지정된 Generic 타이머를 클럭 소스 및 스케줄러 클럭으로 등록하고 딜레이 타이머로도 등록한다.

 

arch_timer_common_init()

drivers/clocksource/arm_arch_timer.c

static void __init arch_timer_common_init(void)
{
        unsigned mask = ARCH_CP15_TIMER | ARCH_MEM_TIMER;

        /* Wait until both nodes are probed if we have two timers */
        if ((arch_timers_present & mask) != mask) {
                if (!arch_timer_probed(ARCH_MEM_TIMER, arch_timer_mem_of_match))
                        return;
                if (!arch_timer_probed(ARCH_CP15_TIMER, arch_timer_of_match))
                        return;
        }

        arch_timer_banner(arch_timers_present);
        arch_counter_register(arch_timers_present);
        arch_timer_arch_init();
}

generic 타이머를 클럭 소스로 등록하고 스케줄러 클럭과 딜레이 타이머로 등록한다.

  • 코드 3~11에서 cp15를 사용하는 generic 타이머와 메모리 mapped generic 타이머 둘 다 사용하는 경우 두 드라이버 모드 각각 probe 되지 않은 경우 빠져나간다.
    • arm/arch/boot/dts/qcom-apq8084.dtsi 디바이스 트리에서 두 개의 generic 타이머를 사용하였다.
  • 코드 13에서 generic 타이머 정보를 출력한다.
    • rpi2: “Architected cp15 timer(s) running at 19.20MHz (virt).”를 출력한다.
    • rpi3: “Architected cp15 timer(s) running at 19.20MHz (phys).”를 출력한다.
  • 코드 14에서 generic 타이머를 클럭 소스, timercounter 및 스케쥴러 클럭으로 등록한다.
  • 코드 15에서 generic 타이머를 딜레이 루프 타이머로 등록한다.

 

 

arch_timer_probed()

drivers/clocksource/arm_arch_timer.c

static bool __init
arch_timer_probed(int type, const struct of_device_id *matches)
{
        struct device_node *dn;
        bool probed = true;

        dn = of_find_matching_node(NULL, matches);
        if (dn && of_device_is_available(dn) && !(arch_timers_present & type))
                probed = false;
        of_node_put(dn);

        return probed;
}

요청 generic 타이머 타입(cp15 or mem)에 대해 디바이스가 준비되었고 해당 타이머가 probe되지 않은 경우에만 false를 반환한다.

 

Generic Timer 초기화 -3- (Timecounter & 스케줄러 클럭)

arch_counter_register()

drivers/clocksource/arm_arch_timer.c

static void __init arch_counter_register(unsigned type)
{
        u64 start_count;

        /* Register the CP15 based counter if we have one */
        if (type & ARCH_CP15_TIMER) {
                if (IS_ENABLED(CONFIG_ARM64) || arch_timer_use_virtual)
                        arch_timer_read_counter = arch_counter_get_cntvct;
                else
                        arch_timer_read_counter = arch_counter_get_cntpct;
        } else {
                arch_timer_read_counter = arch_counter_get_cntvct_mem;

                /* If the clocksource name is "arch_sys_counter" the
                 * VDSO will attempt to read the CP15-based counter.
                 * Ensure this does not happen when CP15-based
                 * counter is not available.
                 */
                clocksource_counter.name = "arch_mem_counter";
        }

        start_count = arch_timer_read_counter();
        clocksource_register_hz(&clocksource_counter, arch_timer_rate);
        cyclecounter.mult = clocksource_counter.mult;
        cyclecounter.shift = clocksource_counter.shift;
        timecounter_init(&timecounter, &cyclecounter, start_count);

        /* 56 bits minimum, so we assume worst case rollover */
        sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate);
}

가능하면 generic 타이머 중 cp15 타이머를 우선하여 클럭 소스로 등록하고 스케줄러 클럭으로도 등록한다.

  • 코드 라인 6~10에서 가능하면 cp15 기반의 타이머를 우선 사용하도록 전역 arch_timer_read_counter에 virtual 또는 physical 타이머를 읽어내는 함수를 대입한다.
  • 코드 라인 11~20에서 cp15 기반이 준비가 안된 경우 메모리 mapped 방식의 타이머를 사용하므로 전역 arch_timer_read_counter에 메모리 mapped 방식의 virtual 타이머를 읽어내는 함수를 대입한다.
  • 코드 라인 22에서 타이머 값을 읽어 시작 카운터 값으로 사용한다.
  • 코드 라인 23에서 “arch_sys_counter”라는 이름의 56비트 클럭소스카운터를 클럭 소스로 등록한다.
  • 코드 라인 24~26에서 56비트 타임카운터/사이클카운터를 초기화한다.
    • rpi2: 아키텍처 Generic 타이머를 사용하여 타임카운터를 초기화한다.
      • mult=0x3415_5555, shift=24로 설정하여 19.2Mhz 클럭 카운터로 1 cycle 당 52ns가 소요되는 것을 알 수 있다.
      • cyclecounter_cyc2ns() 함수를 호출하여 변동된 cycle 카운터 값으로 소요 시간 ns를 산출한다.
  • 코드 라인 29에서 56비트 카운터를 사용하여 스케줄러 클럭으로 등록한다.

 

arch_counter_read_cc()

drivers/clocksource/arm_arch_timer.c

static cycle_t arch_counter_read_cc(const struct cyclecounter *cc)
{
        return arch_timer_read_counter();
}

64bit Virtual 카운터 레지스터 값을 읽어온다.

 

/*                    
 * Default to cp15 based access because arm64 uses this function for
 * sched_clock() before DT is probed and the cp15 method is guaranteed
 * to exist on arm64. arm doesn't use this before DT is probed so even
 * if we don't have the cp15 accessors we won't have a problem.
 */     
u64 (*arch_timer_read_counter)(void) = arch_counter_get_cntvct;

default로 64bit Virtual 카운터 레지스터 값을 읽어온다.

 

arch_counter_get_cntvct()

arch/arm/include/asm/arch_timer.h

static inline u64 arch_counter_get_cntvct(void)
{
        u64 cval;

        isb();
        asm volatile("mrrc p15, 1, %Q0, %R0, c14" : "=r" (cval));
        return cval;
}

CNTVCT(64bit Virtual Counter Register)을 읽어 반환한다.

 

Generic Timer 초기화 -4- (delay 타이머)

arch_timer_arch_init()

arch/arm/kernel/arch_timer.c

int __init arch_timer_arch_init(void)
{                       
        u32 arch_timer_rate = arch_timer_get_rate();

        if (arch_timer_rate == 0)
                return -ENXIO;
                
        arch_timer_delay_timer_register();

        return 0;
}

아키텍처 generic 타이머를 딜레이 루프 타이머로 등록한다.

 

arch_timer_delay_timer_register()

arch/arm/kernel/arch_timer.c

static void __init arch_timer_delay_timer_register(void)
{                       
        /* Use the architected timer for the delay loop. */
        arch_delay_timer.read_current_timer = arch_timer_read_counter_long;
        arch_delay_timer.freq = arch_timer_get_rate();
        register_current_timer_delay(&arch_delay_timer);
}

아키텍처 generic 타이머를 딜레이 루프 타이머로 등록하고 초기화한다.

 

fs_initcall 호출 시

clocksource_done_booting()

kernel/time/clocksource.c

/*
 * clocksource_done_booting - Called near the end of core bootup
 *
 * Hack to avoid lots of clocksource churn at boot time.
 * We use fs_initcall because we want this to start before
 * device_initcall but after subsys_initcall.
 */
static int __init clocksource_done_booting(void)
{
        mutex_lock(&clocksource_mutex);
        curr_clocksource = clocksource_default_clock();
        finished_booting = 1;
        /*
         * Run the watchdog first to eliminate unstable clock sources
         */
        __clocksource_watchdog_kthread();
        clocksource_select();
        mutex_unlock(&clocksource_mutex);
        return 0;
}
fs_initcall(clocksource_done_booting);

jiffies를 default 클럭 소스로 선택한 후 워치독 타이머를 가동하고 best 클럭 소스를 선택한다.

 

clocksource 등록

clocksource_register_hz()

include/linux/clocksource.h

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
        return __clocksource_register_scale(cs, 1, hz);
}

요청한 hz로 클럭 소스를 등록한다.

 

clocksource_register_khz()

include/linux/clocksource.h

static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)
{
        return __clocksource_register_scale(cs, 1000, khz);
}

요청한 khz로 클럭 소스를 등록한다.

 

__clocksource_register_scale()

kernel/time/clocksource.c

/**
 * __clocksource_register_scale - Used to install new clocksources
 * @cs:         clocksource to be registered
 * @scale:      Scale factor multiplied against freq to get clocksource hz
 * @freq:       clocksource frequency (cycles per second) divided by scale
 *
 * Returns -EBUSY if registration fails, zero otherwise.
 *
 * This *SHOULD NOT* be called directly! Please use the
 * clocksource_register_hz() or clocksource_register_khz helper functions.
 */
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{

        /* Initialize mult/shift and max_idle_ns */
        __clocksource_updatefreq_scale(cs, scale, freq);

        /* Add clocksource to the clocksource list */
        mutex_lock(&clocksource_mutex);
        clocksource_enqueue(cs);
        clocksource_enqueue_watchdog(cs);
        clocksource_select();
        mutex_unlock(&clocksource_mutex);
        return 0;
}
EXPORT_SYMBOL_GPL(__clocksource_register_scale);

요청한 배율(scale) 및 주파수(freq)로 클럭소스를 등록한다.

  • 코드 라인 16에서 요청한 배율(scale) 및 주파수(freq)로 클럭의 mult, shift, maxadj, max_idle_ns 등을 산출한다.
  • 코드 라인 20에서 클럭 소스를 큐에 등록한다.
  • 코드 라인 21에서 클럭 소스에 워치독을 설치한다.
  • 코드 라인 22에서 best 클럭 소스를 선택한다.

 

__clocksource_updatefreq_hz()

include/linux/clocksource.h

static inline void __clocksource_updatefreq_hz(struct clocksource *cs, u32 hz)
{
        __clocksource_updatefreq_scale(cs, 1, hz);
}

현재 클럭소스의 주파수가 요청한 hz로 변경 시 관련된 mult, shift, maxadj, max_idle_ns 등을 산출한다.

 

__clocksource_updatefreq_khz()

include/linux/clocksource.h

static inline void __clocksource_updatefreq_khz(struct clocksource *cs, u32 khz)
{
        __clocksource_updatefreq_scale(cs, 1000, khz);
}

현재 클럭소스의 주파수가 요청한 khz로 변경 시 관련된 mult, shift, maxadj, max_idle_ns 등을 산출한다.

 

__clocksource_updatefreq_scale()

kernel/time/clocksource.c

/**
 * __clocksource_updatefreq_scale - Used update clocksource with new freq
 * @cs:         clocksource to be registered
 * @scale:      Scale factor multiplied against freq to get clocksource hz
 * @freq:       clocksource frequency (cycles per second) divided by scale
 *
 * This should only be called from the clocksource->enable() method.
 *
 * This *SHOULD NOT* be called directly! Please use the
 * clocksource_updatefreq_hz() or clocksource_updatefreq_khz helper functions.
 */
void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq)
{
        u64 sec;
        /*
         * Calc the maximum number of seconds which we can run before
         * wrapping around. For clocksources which have a mask > 32bit
         * we need to limit the max sleep time to have a good
         * conversion precision. 10 minutes is still a reasonable
         * amount. That results in a shift value of 24 for a
         * clocksource with mask >= 40bit and f >= 4GHz. That maps to
         * ~ 0.06ppm granularity for NTP. We apply the same 12.5%
         * margin as we do in clocksource_max_deferment()
         */
        sec = (cs->mask - (cs->mask >> 3));
        do_div(sec, freq);
        do_div(sec, scale);
        if (!sec)
                sec = 1;
        else if (sec > 600 && cs->mask > UINT_MAX)
                sec = 600;

        clocks_calc_mult_shift(&cs->mult, &cs->shift, freq,
                               NSEC_PER_SEC / scale, sec * scale);

        /*
         * for clocksources that have large mults, to avoid overflow.
         * Since mult may be adjusted by ntp, add an safety extra margin
         *
         */
        cs->maxadj = clocksource_max_adjustment(cs);
        while ((cs->mult + cs->maxadj < cs->mult)
                || (cs->mult - cs->maxadj > cs->mult)) {
                cs->mult >>= 1;
                cs->shift--;
                cs->maxadj = clocksource_max_adjustment(cs);
        }
        
        cs->max_idle_ns = clocksource_max_deferment(cs);
}
EXPORT_SYMBOL_GPL(__clocksource_updatefreq_scale);

요청한 배율(scale) 및 주파수(freq)로 클럭소스의 mult, shift, maxadj, max_idle_ns 등을 갱신한다.

  • 코드 라인 14에서 클럭소스에서 마스크 값에서 12.5%의 여유 마진을 주기위해 1/8을 감소시켜 sec(초)에 대입한다.
  • 코드 라인 15~20에서 sec에서 freq 및 scale을 나눈다. 만일 그 값이 600을 초과하고 마스크가 0xffff_ffff(32비트 카운터)를 초과하는 경우 최대 값 600초로 제한한다.
  • 코드 라인 22~23에서 freq, scale 및 sec로 소요 시간(ns) 계산에 필요한 컨버팅 팩터인 mult 및 shift 값을 구해온다.
  • 코드 라인 30에서 multi 값의 최대 교정치 값으로 사용하기 위해 multi 값의 10%를 maxadj에 저장한다.
  • 코드 라인 31~36에서 최대 교정치 값을 더한 mult 값이 overflow 된 경우 multi 값과 shift 값을 1씩 줄인 후 overflow 되지 않을 때까지 다시 교정한다.
  • 코드 라인 38에서 카운터가 overflow되지 않는 범위의 ns 값을 약간 마진 12.5를 줄여 max_idle_ns에 저장한다.
    • 소요 시간(delta)이 max_idle_ns 값을 초과하는 경우 카운터가 overflow될 수 있음을 나타낸다.

 

아래 그림은 19.2Mhz 56비트 카운터를 사용하는 경우 설정되는 값들을 보여준다.

 

 

mult & shift

“from 값이 to가 되려면 얼마의 컨버팅 비율이 필요할까?”에 대한 대답은 ‘to / from’을 하면 컨버팅 팩터가 산출된다. 다음과 같이 주파수를 사용하는 사례를 적용해본다.

예) 19.2Mhz 주파수를 갖는 카운터의 1 펄스 값이 사용하는 시간은 나노초 단위로 얼마일까?

  • 디지탈 클럭 시스템에서 주파수 hz는 1초당 high/low 전압이 반복되는 개수를 의미한다. 따라서 19.2Mhz는 1초에 19.2M 번의 high/low 전압이 바뀌고 펄스 하나가 사용하는 초는 ‘1 초 / 19.2M’ 초를 갖는다. (1 / 19.2M = 52.08333333 (ns))

 

커널 코드는 성능과 호환성을 유지하기 위해 부동 소숫점(float)을 사용한 나눗셈 연산을 사용하지 않는다.  따라서 이러한 연산을 대체할 수 있는 방법으로 mult와 shift를 사용한다.

먼저 실수(float) 1.0이라는 수를 사용하지 않고 소숫점을 포함한 정수로 변환을 하여 사용할 때 10배를 곱하여 10이라는 정수를 1.0이라고 의미를 붙일 수 있다. 이렇게 실수를 변환하여 사용하는 정수는 이진수 시스템에서는 10진수와 다르게 2의 거듭제곱수를 사용한다. 이 값이 크면 클 수록 소숫점 이하 정밀도를 높여 표현할 수 있다.

예) 실수 52.08333333에 대해 소숫점이하 8자리의 정밀도를 정수로 표현하기 위해 10E8=100,000,000 배를 곱하여야 한다. 컴퓨터에서는 2 진수의 연산이 더 빠르므로 이를 직접 사용하기 보다는 2의 거듭제곱수를 사용한다. 100,000,000 보다 큰 유사한 2의 n 거듭 제곱수로 2^27=134,217,728을 사용하면 10진수의 8자리 소수를 해결할 수 있다.

다른 정밀도에 따라 사용되는 mult 값들을 살펴본다.

  • 아래 파란 라인만 참고해보면실수 1.0 기준으로 정밀도 shift=24를 사용하여 2^4=0x100_0000  기준 정수를 사용한 경우 실수 52.08333333을 변환한 정수는 0x3415_5555(873,813,333)이 된다.
    • 10진수의 소숫점 8자리를 해결하는 정밀도이다.

결국 정밀도가 높아야 하는 경우 float 1.0에 대한 정수 기준 값을 커져야 함을 알 수 있다. 이 정수 기준 값을 shift 연산에 사용할 예정이다.

“x / y 와 같은 형태의 나눗셈을 커널은 어떻게 처리할까?”

y로 나누는 값이 2의 배수일 경우 우측 쉬프트 연산자를 사용하여 간단히 나눗셈을 대체할 수 있으므로 ‘(x * mult) >> shift’ 형태로 바꿔서 사용할 수 있도록 한다. mult 값은 소숫점일 수 있으므로 정수형으로 변환하여 사용한다.

 

예) 주파수(freq)에 해당하는 from=19.2M를 나노초 단위의 주파수인 to=1G를 대상으로 설명하면

  • to(1G) / from(19.2M) = 52.08333333과 같이 from이 52.0833333배가 되어야 to가 됨을 알 수 있다. 따라서 from의 1 펄스를 52.0833333 나노초로 산출해야 함을 알 수 있다.
  • 실제 연산은 ‘(to(1G) * mult) >> shift’를 사용하므로 mult와 shift를 산출해야 한다. 먼저 정밀도를 위해 shift를 결정하고 mult 값은 실수를 사용하지 않고 shift 비트 수 만큼 배율 변화한 정수를 사용한다.

 

clocks_calc_mult_shift()

kernel/time/clocksource.c

/**
 * clocks_calc_mult_shift - calculate mult/shift factors for scaled math of clocks
 * @mult:       pointer to mult variable
 * @shift:      pointer to shift variable
 * @from:       frequency to convert from
 * @to:         frequency to convert to
 * @maxsec:     guaranteed runtime conversion range in seconds
 *
 * The function evaluates the shift/mult pair for the scaled math
 * operations of clocksources and clockevents.
 *
 * @to and @from are frequency values in HZ. For clock sources @to is
 * NSEC_PER_SEC == 1GHz and @from is the counter frequency. For clock
 * event @to is the counter frequency and @from is NSEC_PER_SEC.
 *
 * The @maxsec conversion range argument controls the time frame in
 * seconds which must be covered by the runtime conversion with the
 * calculated mult and shift factors. This guarantees that no 64bit
 * overflow happens when the input value of the conversion is
 * multiplied with the calculated mult factor. Larger ranges may
 * reduce the conversion accuracy by chosing smaller mult and shift
 * factors.
 */
void
clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
        u64 tmp;
        u32 sft, sftacc= 32;

        /*
         * Calculate the shift factor which is limiting the conversion
         * range:
         */
        tmp = ((u64)maxsec * from) >> 32;
        while (tmp) {
                tmp >>=1;
                sftacc--;
        }

        /*
         * Find the conversion shift/mult pair which has the best
         * accuracy and fits the maxsec conversion range:
         */
        for (sft = 32; sft > 0; sft--) {
                tmp = (u64) to << sft;
                tmp += from / 2;
                do_div(tmp, from);
                if ((tmp >> sftacc) == 0)
                        break;
        }
        *mult = tmp;
        *shift = sft;
}

최대초(maxsec)로 from 주파수를 to 주파수로 변환할 때 적용할 컨버팅 팩터인 mult 및 shift 값을 구해온다.

  • armv7 아키텍처의 로컬 타이머는 32비트 타이머로 from 주파수를 max초 기간만큼 곱하여 64비트로 옮겼을 때 64비트에서 남는 비트들과 배율 차이만큼의 비트들을 뺀 비트를 대상으로 최대 32비트에 한하여 정밀도(shift) 비트를 최대한 올려 사용할 수 있다.

 

  • 코드 라인 34~38에서 from과 maxsec 곱한 후 leading 0 비트 갯 수로 sftacc를 구한다.
    • sftacc는 0 ~ 32 범위로 제한한다. 32를 초과하는 경우 32로 계산한다. (최대 정밀도=32bit)
    • sftacc는 컨버전 팩터로 정확도를 제한하는데 숫자가 작을 수록 정확도가 낮아진다.
    • 측정하는 구간 소요 시간인 maxsec를 높이면 정밀도에 사용할 수 있는 비트가 줄어들므로 정확도가 낮아진다.
  • 코드 라인 44~52에서 to 값을 32 ~ 1까지 좌측 시프트한 값을 from 으로 나눈 값을 다시 sftacc 값 만큼 우측 시프트하여 0을 초과하는 경우 mult 및 shift와 값을 구한다. 만일 0 이하인 경우 to 값을 계속 감소시키며 루프를 돈다.

 

다음 그림은 from=1G, to=19.2M, maxsec=111 값이 주어질 때 mult=0x4ea_4a8c/shift=32 값으로 계산되는 모습을 보여준다. (ns=0)

 

다음 그림은 from=19.2Mhz, to=1G, maxsec=600 값이 주어질 때 mult=0x3415_5555/shift=24 값으로 계산되는 모습을 보여준다. (ns=52)

 

다음 그림은 from=19.2Mhz, to=1G, maxsec=3600 값이 주어질 때 mult=0x0682_aaab/shift=21 값으로 계산되는 모습을 보여준다. (ns=52)

 

clocksource_max_adjustment()

kernel/time/clocksource.c

/**
 * clocksource_max_adjustment- Returns max adjustment amount
 * @cs:         Pointer to clocksource
 *
 */
static u32 clocksource_max_adjustment(struct clocksource *cs)
{
        u64 ret;
        /*
         * We won't try to correct for more than 11% adjustments (110,000 ppm),
         */
        ret = (u64)cs->mult * 11;
        do_div(ret,100);
        return (u32)ret;
}

최대 조정값으로 mult 값의 11%를 반환한다.

 

clocksource_max_deferment()

kernel/time/clocksource.c

/**
 * clocksource_max_deferment - Returns max time the clocksource can be deferred
 * @cs:         Pointer to clocksource
 *
 */
static u64 clocksource_max_deferment(struct clocksource *cs)
{
        u64 max_nsecs;

        max_nsecs = clocks_calc_max_nsecs(cs->mult, cs->shift, cs->maxadj,
                                          cs->mask);
        /*
         * To ensure that the clocksource does not wrap whilst we are idle,
         * limit the time the clocksource can be deferred by 12.5%. Please
         * note a margin of 12.5% is used because this can be computed with
         * a shift, versus say 10% which would require division.
         */
        return max_nsecs - (max_nsecs >> 3);
}

클럭 소스로 cycle을 읽어 ns 값으로 변환하여 사용할 때 최대 사용가능한 보류(유예)할 수 있는 최대 ns 값을 산출한다. 현재 클럭 소스의 cycle 카운터 최대 값으로 변환 시킬 수 있는 최대 ns 값에서 마진으로 12.5%를 감소시켜 반환한다.

 

다음 그림은 현재 클럭소스의 56비트 카운터로 최대 보류(유예)할 수 있는 ns 값을 산출하는 모습을 보여준다. max_cycle=8G, max_idle_ns=약 348초

  • 마진 없이 계산하면 0x3415_5555 x 2G >> 24 = 약 447초
  • 마진 부여 후 계산하면(11% 감소 한 min_mult로 계산 후 최종 12.5% 추가 감소) = (0x3415_5555 – 0x5ba_aaa) x 2G >> 24 * 87.5% = 약 348초

 

clocks_calc_max_nsecs()

kernel/time/clocksource.c

/**
 * clocks_calc_max_nsecs - Returns maximum nanoseconds that can be converted
 * @mult:       cycle to nanosecond multiplier
 * @shift:      cycle to nanosecond divisor (power of two)
 * @maxadj:     maximum adjustment value to mult (~11%)
 * @mask:       bitmask for two's complement subtraction of non 64 bit counters
 */
u64 clocks_calc_max_nsecs(u32 mult, u32 shift, u32 maxadj, u64 mask)
{
        u64 max_nsecs, max_cycles;

        /*
         * Calculate the maximum number of cycles that we can pass to the
         * cyc2ns function without overflowing a 64-bit signed result. The
         * maximum number of cycles is equal to ULLONG_MAX/(mult+maxadj)
         * which is equivalent to the below.
         * max_cycles < (2^63)/(mult + maxadj)
         * max_cycles < 2^(log2((2^63)/(mult + maxadj)))
         * max_cycles < 2^(log2(2^63) - log2(mult + maxadj))
         * max_cycles < 2^(63 - log2(mult + maxadj))
         * max_cycles < 1 << (63 - log2(mult + maxadj))
         * Please note that we add 1 to the result of the log2 to account for
         * any rounding errors, ensure the above inequality is satisfied and
         * no overflow will occur.
         */
        max_cycles = 1ULL << (63 - (ilog2(mult + maxadj) + 1));

        /*
         * The actual maximum number of cycles we can defer the clocksource is
         * determined by the minimum of max_cycles and mask.
         * Note: Here we subtract the maxadj to make sure we don't sleep for
         * too long if there's a large negative adjustment.
         */
        max_cycles = min(max_cycles, mask);
        max_nsecs = clocksource_cyc2ns(max_cycles, mult - maxadj, shift);

        return max_nsecs;
}

최대 조정 cycle 값을 뺀 mult와 최대 cycle 수로 최대 사용 가능한 시간(ns)를 구해 반환한다.

  • 코드 라인 26에서 max_cycle 을 구하기 위해 63비트 중 multi+maxadj가 사용하는 비트를 제외한 비트로 만들 수 있는 경우의 수를 구한다.
    • 예) 30bit를 사용하는 multi 값인 경우 63-30=33 비트의 경우의 수 = 0x10_0000_0000 (8G, 0x0 ~ 0xf_ffff_ffff 까지 cycle 사용)
  • 코드 라인 34에서 max_cycles가 mask 값을 초과하지 않게 한다.
  • 코드 라인 35에서 계산된 max_cycles에 maxadj를 뺀 mult를 곱한 수를 우측으로 shift 하여 최대 소요 시간(ns)을 구해 반환한다.
    • maxadj 만큼의 마진을 감소시켜 최대 소요 시간을 11% 줄인다.

 

clocksource_cyc2ns()

include/linux/clocksource.h

/**
 * clocksource_cyc2ns - converts clocksource cycles to nanoseconds
 * @cycles:     cycles
 * @mult:       cycle to nanosecond multiplier
 * @shift:      cycle to nanosecond divisor (power of two) 
 *
 * Converts cycles to nanoseconds, using the given mult and shift.
 *
 * XXX - This could use some mult_lxl_ll() asm optimization
 */
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
        return ((u64) cycles * mult) >> shift;
}

cycle 값으로 nano 초를 컨버팅하여 반환한다.

  • 예) 256 cycle, mult=0x682a_aaab, shift=21
    • =13,333ns

 

다음 그림은 mult=0x682_aaab, shift=21인 상황에서 cycle=1이 주어진 경우 52ns의 소요 시간을 산출하는 모습을 보여준다.

 

클럭 소스 등록 및 선택

clocksource_enqueue()

kernel/time/clocksource.c

/*
 * Enqueue the clocksource sorted by rating
 */
static void clocksource_enqueue(struct clocksource *cs)
{
        struct list_head *entry = &clocksource_list;
        struct clocksource *tmp;
        
        list_for_each_entry(tmp, &clocksource_list, list)
                /* Keep track of the place, where to insert */
                if (tmp->rating >= cs->rating)
                        entry = &tmp->list;
        list_add(&cs->list, entry);
}

요청한 클럭 소스를 clocksource_list에 추가할 때 rating 값이 큰 순서대로 정렬한다. (descending sort)

 

다음 그림은 클럭 소스를 추가할 때 rating 값 순으로 소팅되어 등록되는 것을 보여준다.

 

clocksource_select()

kernel/time/clocksource.c

/**
 * clocksource_select - Select the best clocksource available
 *
 * Private function. Must hold clocksource_mutex when called.
 *
 * Select the clocksource with the best rating, or the clocksource,
 * which is selected by userspace override.
 */
static void clocksource_select(void)
{
        __clocksource_select(false);
}

현재 지정된 클럭을 포함하여 best 클럭 소스를 찾아 선택한다.

 

__clocksource_select()

kernel/time/clocksource.c

static void __clocksource_select(bool skipcur)
{
        bool oneshot = tick_oneshot_mode_active();
        struct clocksource *best, *cs;

        /* Find the best suitable clocksource */
        best = clocksource_find_best(oneshot, skipcur);
        if (!best)
                return;

        /* Check for the override clocksource. */
        list_for_each_entry(cs, &clocksource_list, list) {
                if (skipcur && cs == curr_clocksource)
                        continue;
                if (strcmp(cs->name, override_name) != 0)
                        continue;
                /*
                 * Check to make sure we don't switch to a non-highres
                 * capable clocksource if the tick code is in oneshot
                 * mode (highres or nohz)
                 */
                if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) {
                        /* Override clocksource cannot be used. */
                        pr_warn("Override clocksource %s is not HRT compatible - cannot switch while in HRT/NOHZ mode\n",
                                cs->name);
                        override_name[0] = 0;
                } else
                        /* Override clocksource can be used. */
                        best = cs;
                break;
        }
        
        if (curr_clocksource != best && !timekeeping_notify(best)) {
                pr_info("Switched to clocksource %s\n", best->name);
                curr_clocksource = best;
        }
}

best 클럭 소스를 선택한다. skip_cur에 true를 요청하는 경우 현재 선택된 클럭 소스는 제외하고 best 클럭 소스를 찾아 선택한다.

  • 코드 라인 3에서 현재 cpu의 tick_cpu_device 모드가 oneshot을 지원하는지 여부를 알아온다.
  • 코드 라인 7~9에서 먼저 best 클럭 소스를 알아온다. 만일 찾지 못한 경우 함수를 빠져나간다.
  • 코드 라인 12~31에서 “clocksource=” 커널 파라미터로 지정된 클럭 소스를 찾는다.
  • 코드 라인 33~36에서 현재 클럭 소스가 변경된 경우 클럭 소스가 바뀌었다는 정보를 출력한다.

 

다음 그림은 “t4” 클럭 소스를 지정하여 선택하는 것을 보여준다.

 

clocksource_find_best()

kernel/time/clocksource.c

static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur) 
{
        struct clocksource *cs;

        if (!finished_booting || list_empty(&clocksource_list))
                return NULL; 

        /*
         * We pick the clocksource with the highest rating. If oneshot
         * mode is active, we pick the highres valid clocksource with
         * the best rating.
         */
        list_for_each_entry(cs, &clocksource_list, list) {
                if (skipcur && cs == curr_clocksource)
                        continue;
                if (oneshot && !(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES))
                        continue;
                return cs;     
        }          
        return NULL;
}

best 클럭 소스를 찾아 반환한다. oneshot=1이 요청되는 경우 hrtimer에서만 찾는다. skipcur=true인 경우 현재 선택된 클럭 소스는 제외한다.

  • 코드 라인 5~6에서 아직 부트업으로 인한 초기화가 안되었거나 등록된 클럭 소스가 없는 경우 함수를 빠져나간다.
  • 코드 라인 13에서 rating 값 순으로 등록되어 있는 클럭 소스 리스트에서 조건을 만족하는 처음 클럭 소스를 찾는다. 현재 지정된 클럭 소스와 oneshot 요청 시 valid_for_hires 플래그가 없는 클럭 소스인 경우는 skip하는 조건이다.

 

다음 그림은 oneshot 설정 유무에 따라 best 클럭 소스를 찾는 과정을 보여준다.

 

clocksource 제어

모드 설정

arch_timer_set_mode_virt()

drivers/clocksource/arm_arch_timer.c

static void arch_timer_set_mode_virt(enum clock_event_mode mode,
                                     struct clock_event_device *clk)
{
        timer_set_mode(ARCH_TIMER_VIRT_ACCESS, mode, clk);
}

virtual 타이머의 모드를 설정한다.

  • unused 또는 shutdown 모드를 요청한 경우 타이머를 정지시키고 그 외의 경우 무시한다.

 

timer_set_mode()

drivers/clocksource/arm_arch_timer.c

static __always_inline void timer_set_mode(const int access, int mode,
                                  struct clock_event_device *clk)
{
        unsigned long ctrl;
        switch (mode) {
        case CLOCK_EVT_MODE_UNUSED:
        case CLOCK_EVT_MODE_SHUTDOWN:
                ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL, clk);
                ctrl &= ~ARCH_TIMER_CTRL_ENABLE;
                arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, clk);
                break;
        default:
                break;
        }
}

타이머의 요청 모드가 unused 또는 shutdown인 경우 타이머를 정지시킨다.

 

static __always_inline
u32 arch_timer_reg_read(int access, enum arch_timer_reg reg,
                        struct clock_event_device *clk)
{
        u32 val;

        if (access == ARCH_TIMER_MEM_PHYS_ACCESS) {
                struct arch_timer *timer = to_arch_timer(clk);
                switch (reg) {
                case ARCH_TIMER_REG_CTRL:
                        val = readl_relaxed(timer->base + CNTP_CTL);
                        break;
                case ARCH_TIMER_REG_TVAL:
                        val = readl_relaxed(timer->base + CNTP_TVAL);
                        break;
                }
        } else if (access == ARCH_TIMER_MEM_VIRT_ACCESS) {
                struct arch_timer *timer = to_arch_timer(clk);
                switch (reg) {
                case ARCH_TIMER_REG_CTRL:
                        val = readl_relaxed(timer->base + CNTV_CTL);
                        break;
                case ARCH_TIMER_REG_TVAL:
                        val = readl_relaxed(timer->base + CNTV_TVAL);
                        break;
                }
        } else {
                val = arch_timer_reg_read_cp15(access, reg);
        }

        return val;
}

요청한 타이머 레지스터 값 또는 컨트롤 레지스터 값을 읽어온다.

  • 코드 라인 7~16에서 메모리 mapped 아키텍처 타이머 디바이스 드라이버를 사용하고 physical 타이머를 요청하는 경우 매핑된 base 주소를 알아와서 요청하는 레지스터 값 또는 컨트롤 레지스터 값을 읽어낸다.
  • 코드 라인 17~26에서 메모리 mapped 아키텍처 타이머 디바이스 드라이버를 사용하고 virtual 타이머를 요청하는 경우 매핑된 base 주소를 알아와서 요청하는 레지스터 값 또는 컨트롤 레지스터 값을 읽어낸다.
  • 코드 라인 27~29에서 아키텍처 타이머 디바이스 드라이버를 사용한 경우 cp15를 사용하여 요청한 레지스터 값 또는 컨트롤 레지스터 값을 읽어낸다.

 

arch_timer_reg_read_cp15()

arch/arm/include/asm/arch_timer.h

static __always_inline
u32 arch_timer_reg_read_cp15(int access, enum arch_timer_reg reg)
{
        u32 val = 0; 

        if (access == ARCH_TIMER_PHYS_ACCESS) {
                switch (reg) {
                case ARCH_TIMER_REG_CTRL:
                        asm volatile("mrc p15, 0, %0, c14, c2, 1" : "=r" (val));
                        break;
                case ARCH_TIMER_REG_TVAL:
                        asm volatile("mrc p15, 0, %0, c14, c2, 0" : "=r" (val));
                        break;
                }
        } else if (access == ARCH_TIMER_VIRT_ACCESS) { 
                switch (reg) {
                case ARCH_TIMER_REG_CTRL:
                        asm volatile("mrc p15, 0, %0, c14, c3, 1" : "=r" (val));
                        break;
                case ARCH_TIMER_REG_TVAL:
                        asm volatile("mrc p15, 0, %0, c14, c3, 0" : "=r" (val));
                        break;
                }
        }

        return val;
}

보조프로세서 cp15를 통해 요청한 타이머의 값 또는 컨트롤 값을 읽어낸다. (cp15를 사용하는 경우 설정에 따라 user 영역에서도 타이머에 접근할 수 있다)

  • 코드 라인 6~14에서 physical 타이머의 값 또는 컨트롤 값을 읽어온다.
    • CNTP_CTL(PL1 Physical TImer Control Register)
    • CNTP_TVAL(PL1 Physical Timer Value Register)
  • 코드 라인 15~24에서 virtual 타이머의 값 또는 컨트롤 값을 읽어온다.
    • CNTV_CTL(Virtual TImer Control Register)
    • CNTV_TVAL(Virtual Timer Value Register)

 

다음은 ARMv7의 타이머 관련 레지스터들을 보여준다.

 

타이머 shutdown

timer_shutdonw()

drivers/clocksource/arm_arch_timer.c

static __always_inline int timer_shutdown(const int access,
                                          struct clock_event_device *clk)
{
        unsigned long ctrl;

        ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL, clk);
        ctrl &= ~ARCH_TIMER_CTRL_ENABLE;
        arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, clk);

        return 0;
}

타이머 컨트롤 레지스터를 읽어 bit0만 클리어 한 후 다시 기록하여 타이머 기능을 정지시킨다.

 

구조체

clocksource 구조체

include/linux/clocksource.h

/**
 * struct clocksource - hardware abstraction for a free running counter
 *      Provides mostly state-free accessors to the underlying hardware.
 *      This is the structure used for system time.
 *
 * @name:               ptr to clocksource name
 * @list:               list head for registration
 * @rating:             rating value for selection (higher is better)
 *                      To avoid rating inflation the following
 *                      list should give you a guide as to how
 *                      to assign your clocksource a rating
 *                      1-99: Unfit for real use
 *                              Only available for bootup and testing purposes.
 *                      100-199: Base level usability.
 *                              Functional for real use, but not desired.
 *                      200-299: Good.
 *                              A correct and usable clocksource.
 *                      300-399: Desired.
 *                              A reasonably fast and accurate clocksource.
 *                      400-499: Perfect
 *                              The ideal clocksource. A must-use where
 *                              available.
 * @read:               returns a cycle value, passes clocksource as argument
 * @enable:             optional function to enable the clocksource
 * @disable:            optional function to disable the clocksource
 * @mask:               bitmask for two's complement
 *                      subtraction of non 64 bit counters
 * @mult:               cycle to nanosecond multiplier
 * @shift:              cycle to nanosecond divisor (power of two)
 * @max_idle_ns:        max idle time permitted by the clocksource (nsecs)
 * @maxadj:             maximum adjustment value to mult (~11%)
 * @max_cycles:         maximum safe cycle value which won't overflow on multiplication
 * @flags:              flags describing special properties
 * @archdata:           arch-specific data
 * @suspend:            suspend function for the clocksource, if necessary
 * @resume:             resume function for the clocksource, if necessary
 * @owner:              module reference, must be set by clocksource in modules
 */
struct clocksource {
        /*
         * Hotpath data, fits in a single cache line when the
         * clocksource itself is cacheline aligned.
         */
        cycle_t (*read)(struct clocksource *cs);
        cycle_t mask;
        u32 mult;
        u32 shift;
        u64 max_idle_ns;
        u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
        struct arch_clocksource_data archdata;
#endif
        u64 max_cycles;
        const char *name;
        struct list_head list;
        int rating;
        int (*enable)(struct clocksource *cs);
        void (*disable)(struct clocksource *cs);
        unsigned long flags;
        void (*suspend)(struct clocksource *cs);
        void (*resume)(struct clocksource *cs);

        /* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
        /* Watchdog related data, used by the framework */
        struct list_head wd_list;
        cycle_t cs_last;
        cycle_t wd_last;
#endif
        struct module *owner;
} ____cacheline_aligned;
  • *name
    • clocksource 명
  • list
    • 등록 시 리스트에 연결
  • rating
    • 선택 등급으로 수치가 높을 수록 좋다.
  • (*read)
    • cycle 값을 알아온다.
  • (*enable)
    • clocksource를 enable할 수 있는 경우 사용된다. (option)
  • (*disable)
    • clocksource를 disable할 수 있는 경우 사용된다. (option)
  • mask
    • 64bit 카운터가 아닌 경우 마스크하여 사용한다.
  • mult
    • 1 cycle을 nano second로 변경 시 곱할 수
  • shift
    • 1 cycle을 nano second로 변경 시 나눌 수(2의 차수)
  • max_idle_ns
    • clocksource가 최대 idle할 수 있는 nano second
  • maxadj
    • mult로 사용할 수 있는 최대 조정 값 (~11%)
  • flags
    • 플래그
      • CLOCK_SOURCE_IS_CONTINUOUS(0x01)
      • CLOCK_SOURCE_MUST_VERIFY(0x02)
        • x86 TSC에서 사용
      • CLOCK_SOURCE_WATCHDOG(0x10)
      • CLOCK_SOURCE_VALID_FOR_HRES(0x20)
      • CLOCK_SOURCE_UNSTABLE(0x40)
      • CLOCK_SOURCE_SUSPEND_NONSTOP(0x80)
      • CLOCK_SOURCE_RESELECT(0x100)
  • archdata
    • 아키텍처 종속적인 데이터
  • (*suspend)
    • suspend 시 closksource를 suspend 할 수 있는 경우 사용된다. (option)
  • (*resume)
    • resume 시 closksource를 resume 할 수 있는 경우 사용된다. (option)
  • owner
    • 모듈에서 사용 시 반드시 설정되어야 하는 레퍼런스
  • max_cycle

 

FS를 통한 clocksource 현황 확인

다음과 같이 rpi2의 clocksource를 확인해 보았다.

# cd /sys/devices/system/clocksource
# ls 
clocksource0  power  uevent
# ls clocksource0
available_clocksource  power/                 uevent
current_clocksource    subsystem/             unbind_clocksource
# cat clocksource0/available_clocksource
arch_sys_counter
# cat clocksource0/current_clocksource
arch_sys_counter

 

참고

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.