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

 

참고

 

hrtimers_init()

 

HRTimer 초기화

hrtimers_init()

kernel/time/hrtimer.c

void __init hrtimers_init(void)
{
        hrtimer_cpu_notify(&hrtimers_nb, (unsigned long)CPU_UP_PREPARE,
                          (void *)(long)smp_processor_id());
        register_cpu_notifier(&hrtimers_nb);
#ifdef CONFIG_HIGH_RES_TIMERS
        open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
#endif
}

현재 cpu에 대한 hrtimer를 초기화하고 cpu notifier chain에 hrtimer 초기화 함수를 등록한다. 그리고 hrtimer용 softirq로 run_hrtimer_softirq() 함수를 등록한다.

  • 코드 라인 3~4에서 현재 cpu에 대해 CPU_UP_PRPARE 상태로 전역 hrtimers_nb라는 이름의 notifier_block에 등록된 hrtimer_cpu_notify() 함수를 직접 호출한다.
    • bootup cpu는 이미 on되어 있는 상황이므로 여기서 hrtimer 초기화 루틴을 수행하도록 호출한다. 이 때 실제 hrimer의 초기화 함수 init_hrtimers_cpu()가 호출 된다.
  • 코드 라인 5에서 cpu들 상태가 바뀔 때마다 호출되도록 cpu notifier chain에 등록한다.
    • cpu가 on될 때마다 hrtimer framework의 초기화를 수행한다.
    • armv7 아키텍처는 각 cpu마다 hrtimer가 4개씩 내장되어 있다.
  • 코드 라인 6~8에서 CONFIG_HIGH_RES_TIMERS 커널 옵션을 사용하는 경우 run_hrtimer_softirq() 함수를 softirq에 등록한다.
    • 커널 v4.2-rc1에서는 hrtimer용 softirq를 사용하지 않는다.

 

kernel/time/hrtimer.c

static struct notifier_block hrtimers_nb = {
        .notifier_call = hrtimer_cpu_notify,
};

cpu 상태 변화에 따라 호출될 hrtimer의 notifier block이다.

 

hrtimer_cpu_notify()

kernel/time/hrtimer.c”

static int hrtimer_cpu_notify(struct notifier_block *self,
                                        unsigned long action, void *hcpu)
{
        int scpu = (long)hcpu;

        switch (action) {

        case CPU_UP_PREPARE:
        case CPU_UP_PREPARE_FROZEN:
                init_hrtimers_cpu(scpu);
                break;

#ifdef CONFIG_HOTPLUG_CPU
        case CPU_DYING:
        case CPU_DYING_FROZEN:
                clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DYING, &scpu);
                break;
        case CPU_DEAD:
        case CPU_DEAD_FROZEN:
        {
                clockevents_notify(CLOCK_EVT_NOTIFY_CPU_DEAD, &scpu);
                migrate_hrtimers(scpu);
                break;
        }
#endif

        default:
                break;
        }

        return NOTIFY_OK;
}

cpu 상태가 변화함에 따라 통지되어 hrtimer가 처리해야 할 일들을 수행한다.

  • 코드 라인 6~11에서 cpu가 up이 되면 해당 cpu에 대한 hrtimer 프레임워크를 초기화한다.
  • 코드 라인 13~17에서 cpu가 dying 상태가 되면 해당 cpu를 사용하는 클럭이벤트들에 대해 dying 상태를 통지한다.
  • 코드 라인 18~24에서 cpu가 dead 상태가 되면 해당 cpu를 사용하는 클럭이벤트들에 대해 dead 상태를 통지하고 해당 cpu의 hrtimer를 사용하는 요청들을 현재 cpu로 migration 한다.

 

init_hrtimers_cpu()

kernel/time/hrtimer.c

/*
 * Functions related to boot-time initialization:
 */
static void init_hrtimers_cpu(int cpu)
{
        struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu);
        int i;

        for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
                cpu_base->clock_base[i].cpu_base = cpu_base;
                timerqueue_init_head(&cpu_base->clock_base[i].active);
        }

        cpu_base->cpu = cpu;
        hrtimer_init_hres(cpu_base);
}

요청  cpu에 대한 hritimer의 cpu_base와 clock_base를 초기화한다.

  • 코드 라인 6에서 요청 cpu의 hrtimer_bases를 알아와서 cpu_base에 대입한다.
  • 코드 라인 9~12에서 HRTIMER_MAX_CLOCK_BASES(4)개 까지 루프를 돌며 멤버 cpu_base를 지정하게 하고 active 타이머큐를 초기화한다.
  • 코드 라인 15에서 요청 cpu에 대해 동작중인 hrtimer를 0개로 초기화한다.

 

다음 그림은 0번 cpu에 대한 hrtimer 구조를 초기화한 모습을 보여준다.

 

 

timerqueue_init_head()

include/linux/timerqueue.h

static inline void timerqueue_init_head(struct timerqueue_head *head)
{
        head->head = RB_ROOT;
        head->next = NULL;
}

hrtimer 큐를 초기화한다.

 

 hrtimer_init_hres()

kernel/time/hrtimer.c

/*
 * Initialize the high resolution related parts of cpu_base
 */
static inline void hrtimer_init_hres(struct hrtimer_cpu_base *base) 
{
        base->expires_next.tv64 = KTIME_MAX;
        base->hres_active = 0;
}

동작 중인 hrtimer를 0으로 초기화한다.

 

참고

Common Clock Framework -2- (APIs)

 

Common Clock Framework -2- (APIs)

클럭 등록 시 사용하는 플래그

다음의 플래그들은 최상위 framework인 common clock framework에서 유효하다.

include/linux/clk-provider.h

/*
 * flags used across common struct clk.  these flags should only affect the
 * top-level framework.  custom flags for dealing with hardware specifics
 * belong in struct clk_foo
 */
#define CLK_SET_RATE_GATE       BIT(0) /* must be gated across rate change */
#define CLK_SET_PARENT_GATE     BIT(1) /* must be gated across re-parent */
#define CLK_SET_RATE_PARENT     BIT(2) /* propagate rate change up one level */
#define CLK_IGNORE_UNUSED       BIT(3) /* do not gate even if unused */
#define CLK_IS_ROOT             BIT(4) /* root clk, has no parent */
#define CLK_IS_BASIC            BIT(5) /* Basic clk, can't do a to_clk_foo() */
#define CLK_GET_RATE_NOCACHE    BIT(6) /* do not use the cached clk rate */
#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */
#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */
  • CLK_SET_RATE_GATE
    • rate 변경 시 반드시 unprepare 되어 있어야 한다. (gate가 닫혀 있어야 한다)
  • CLK_SET_PARENT_GATE
    • 입력 클럭 소스(부모 클럭)를 선택 시 반드시 unprepare 되어 있어야 한다. (gate가 닫혀 있어야 한다)
  • CLK_SET_RATE_PARENT
    • rate 변경 시 부모에 전파(propogation) 하여 부모 클럭에서 rate를 변경하게 한다.
  • CLK_IGNORE_UNUSED
    • 사용하지 않아도 gate를 닫지 않는다.
  • CLK_IS_ROOT
    • 루트 클럭으로 부모가 없다.
  • CLK_IS_BASIC
    • clk_foo()와 같은 파생 클럭이 아닌 클럭이다.
    • common clock framework에 구현되어 있는 8개의 클럭 디바이스 드라이버는 모두 CLK_IS_BASIC 플래그가 설정되어 있다.
  • CLK_GET_RATE_NOCACHE
    • 캐시된 clock rate를 사용하지 못한다.
  • CLK_SET_RATE_NO_REPARENT
    • mux 기능 사용 시 rate 변경 시 부모 클럭을 선택하지 못하는 상태에서 현재 연결된 부모 클럭에게 rate 변경 요청을 하게한다.
  • CLK_GET_ACCURACY_NOCACHE
    • 캐시된 accuracy를 사용하지 못한다.

rate 설정 -1- (set_rate)

clk_set_rate()

drivers/clk/clk.c

/**
 * clk_set_rate - specify a new rate for clk
 * @clk: the clk whose rate is being changed
 * @rate: the new rate for clk
 *
 * In the simplest case clk_set_rate will only adjust the rate of clk.
 *
 * Setting the CLK_SET_RATE_PARENT flag allows the rate change operation to
 * propagate up to clk's parent; whether or not this happens depends on the
 * outcome of clk's .round_rate implementation.  If *parent_rate is unchanged
 * after calling .round_rate then upstream parent propagation is ignored.  If
 * *parent_rate comes back with a new rate for clk's parent then we propagate
 * up to clk's parent and set its rate.  Upward propagation will continue
 * until either a clk does not support the CLK_SET_RATE_PARENT flag or
 * .round_rate stops requesting changes to clk's parent_rate.
 *
 * Rate changes are accomplished via tree traversal that also recalculates the
 * rates for the clocks and fires off POST_RATE_CHANGE notifiers.
 *
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_set_rate(struct clk *clk, unsigned long rate)
{
        int ret; 

        if (!clk)
                return 0;

        /* prevent racing with updates to the clock topology */
        clk_prepare_lock();
 
        ret = clk_core_set_rate_nolock(clk->core, rate);
        
        clk_prepare_unlock();
                
        return ret;     
} 
EXPORT_SYMBOL_GPL(clk_set_rate);

클럭의 rate 설정을 변경 요청한다.

  • CLK_SET_RATE_PARENT 플래그가 사용된 클럭은 rate 설정 시 상위 클럭으로 전파되도록 한다.

 

clk_core_set_rate_nolock()

drivers/clk/clk.c

static int clk_core_set_rate_nolock(struct clk_core *clk,
                                    unsigned long req_rate)
{
        struct clk_core *top, *fail_clk;
        unsigned long rate = req_rate;
        int ret = 0;

        if (!clk)
                return 0;

        /* bail early if nothing to do */
        if (rate == clk_core_get_rate_nolock(clk))
                return 0;

        if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count)
                return -EBUSY;

        /* calculate new rates and get the topmost changed clock */
        top = clk_calc_new_rates(clk, rate);
        if (!top)
                return -EINVAL;

        /* notify that we are about to change rates */
        fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
        if (fail_clk) {
                pr_debug("%s: failed to set %s rate\n", __func__,
                                fail_clk->name);
                clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
                return -EBUSY;
        }

        /* change the rates */
        clk_change_rate(top);

        clk->req_rate = req_rate;

        return ret;
}

클럭의 rate 설정을 한다. 성공 시에 0을 반환한다.

  • 코드 라인 12~13에서 클럭의 rate 값을 반환한다. rate 값이 기존 값과 변화가 없으면 0을 반환한다.
  • 코드 라인 15~16에서 CLK_SET_RATE_GATE 플래그 설정된 경우이며넛 prepare_count가 있으면 -EBUSY 에러를 반환한다.
  • 코드 라인 19~21에서 rate가 적용될 상위 클럭부터 new rate가 적용되고 하위 클럭들이 재계산된다. 적용된 상위 클럭을 알아온다.
    • CLK_SET_RATE_PARENT 플래그가 사용된 클럭들은 그 상위 클럭에서 rate를 결정한다.
    • rate를 결정하는 클럭의 타입에 따라 다음과 같이 재계산된다.
      • Mux 타입
        • determine_rate() 함수를 사용하여 어떤 부모 클럭을 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
      • 그 외 배율 조절 타입
        • round_rate() 함수를 사용하여 어떤 배율의 클럭을 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
  • 코드 라인 24~30에서 PRE_RATE_CHANGE 상태 를 통지 받아야 하는 클럭 노드들에 전달하여 클럭 rate 재설정을 준비한다. 만일 실패하는 경우 ABORT_RATE_CHANGE를 다시 통지하여 rollback을 알린다.
    • 상위 부모 클럭까지 rate를 설정하게 할 때 상위 전달(propagation) 과정에서 notify되는 함수에서 해당 클럭의 설정을 허용할지 여부를 결정하게 한다.
  • 코드 라인 33에서 적용될 상위 클럭부터 하위 방향 까지 자식 노드로 연결된 노드들을 재계산하여 rate를 적용한다. 이 때 POST_RATE_CHANGE를 다시 통지하여 commit한다.

 

다음 그림은 클럭 F에서 rate를 바꿀 때 클럭 F->G까지 어떠한 단계로 바뀌는지 그 과정을 보여준다. (성공 예)

 

다음 그림은 위의 방법으로 실패 사례를 보여준다.

 

clk_core_get_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_get_rate_nolock(struct clk_core *clk)
{
        unsigned long ret;

        if (!clk) {
                ret = 0;
                goto out;
        }

        ret = clk->rate;

        if (clk->flags & CLK_IS_ROOT)
                goto out;

        if (!clk->parent)
                ret = 0;

out:
        return ret;
}

클럭의 rate 값을 반환한다. 만일 루트 클럭이 아닌데 아직 부모 설정되지 않은 경우 0을 반환한다.

 

rate 설정 -2- (new rates 산출)

clk_calc_new_rates()

drivers/clk/clk.c

/*
 * calculate the new rates returning the topmost clock that has to be
 * changed.
 */
static struct clk_core *clk_calc_new_rates(struct clk_core *clk,
                                           unsigned long rate)
{
        struct clk_core *top = clk;
        struct clk_core *old_parent, *parent;
        struct clk_hw *parent_hw;
        unsigned long best_parent_rate = 0;
        unsigned long new_rate;
        unsigned long min_rate;
        unsigned long max_rate;
        int p_index = 0;

        /* sanity */
        if (IS_ERR_OR_NULL(clk))
                return NULL;

        /* save parent rate, if it exists */
        parent = old_parent = clk->parent;
        if (parent)
                best_parent_rate = parent->rate;

        clk_core_get_boundaries(clk, &min_rate, &max_rate);

        /* find the closest rate and parent clk/rate */
        if (clk->ops->determine_rate) {
                parent_hw = parent ? parent->hw : NULL;
                new_rate = clk->ops->determine_rate(clk->hw, rate,
                                                    min_rate,
                                                    max_rate,
                                                    &best_parent_rate,
                                                    &parent_hw);
                parent = parent_hw ? parent_hw->core : NULL;
        } else if (clk->ops->round_rate) {
                new_rate = clk->ops->round_rate(clk->hw, rate,
                                                &best_parent_rate);
                if (new_rate < min_rate || new_rate > max_rate)
                        return NULL;
        } else if (!parent || !(clk->flags & CLK_SET_RATE_PARENT)) {
                /* pass-through clock without adjustable parent */
                clk->new_rate = clk->rate;
                return NULL;
        } else {
                /* pass-through clock with adjustable parent */
                top = clk_calc_new_rates(parent, rate);
                new_rate = parent->new_rate;
                goto out;
        }

        /* some clocks must be gated to change parent */
        if (parent != old_parent &&
            (clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) {
                pr_debug("%s: %s not gated but wants to reparent\n",
                         __func__, clk->name);
                return NULL;
        }

        /* try finding the new parent index */
        if (parent && clk->num_parents > 1) {
                p_index = clk_fetch_parent_index(clk, parent);
                if (p_index < 0) {
                        pr_debug("%s: clk %s can not be parent of clk %s\n",
                                 __func__, parent->name, clk->name);
                        return NULL;
                }
        }

        if ((clk->flags & CLK_SET_RATE_PARENT) && parent &&
            best_parent_rate != parent->rate)
                top = clk_calc_new_rates(parent, best_parent_rate);

out:
        clk_calc_subtree(clk, new_rate, parent, p_index);

        return top;
}

최상위 클럭의 rate 값이 바뀌어서 현재 클럭 rate로 변경될 값을 알아온다.

  • 코드 라인 22에서 요청 클럭의 부모 클럭을 알아와서 parent 및 old_parent에 보관한다.
  • 코드 라인 23~24에서 부모 클럭이 존재하는 경우 부모 클럭의 rate를 best_parent_rate에 보관한다.
  • 코드 라인 26에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 29~36에서 mux h/w 드라이버의 ops->determine_rate 콜백 함수를 호출하여 실제 클럭 hw가 설정할 수 있는 최적의 new_rate를 알아온다.
    • 참고: drivers/clk/ti/mux.c – __clk_mux_determine_rate()
  • 코드 라인 37~41에서 배율이 변경될 수 있는 클럭(divide, fractional, fixed factor) h/w 드라이버의 ops->round_rate 콜백 함수를 호출하여 실제 클럭 hw가 설정할 수 있는 최적의 new_rate를 알아온다.
    • 참고: drivers/clk/ti/divider.c – ti_clk_divider_round_rate()
  • 코드 라인 42~45에서 부모 클럭이 없거나 CLK_SET_RATE_PARENT 플래그가 없는 경우 현재 클럭의 rate를 new_rate로 사용한다.
  • 코드 라인 46~51에서 부모 클럭의 rate를 산출하기 위해 인수로 부모 클럭과 요청 rate 값을 가지고 이 함수를 재귀호출하여 new_rate를 알아온다.
    • ops->determine_rate 및 ops->round_rate가 없는 gate 타입의 클럭을 사용하는 경우 CLK_SET_RATE_PARENT 플래그가 사용되지 않을 때까지 상위 클럭으로 이동한다.
  • 코드 라인 54~59에서 mux 타입 클럭에서 rate 변경 요청으로 인해 부모 클럭의 변경이 필요한 상태이며 현재 클럭에 CLK_SET_PARENT_GATE 플래그가 설정된 경우일 때 gate가 열린 상태이면 null을 반환한다.
    • CLK_SET_PARENT_GATE 플래그 옵션을 사용하는 mux인 경우 gate를 닫지 않으면 mux에서 부모 클럭의 변경이 실패한다.
  • 코드 라인 62~69에서 mux 타입 클럭에서 부모 클럭이 2개 이상이면 현재 선택된 부모 인덱스 값을 알아온다. 만일 알아올 수 없으면 null을 반환한다.
  • 코드 라인 71~73에서 클럭에 CLK_SET_RATE_PARENT 플래그가 설정되었고 parent의 rate가 변경된 경우 인수로 부모 클럭과 best_parent_rate 값으로 이 함수를 재귀호출하여 상위 클럭으로 올라가서 rate가 변경될 상위 부모 클럭을 알아온다.
  • 코드 라인 76에서 이 함수가 재귀 호출된 경우 rate가 변경될 상위 부모 클럭부터 시작하여 연결된 모든 자식 클럭 방향으로 rate를 재계산하게 한다.

 

다음 그림은 클럭 F에서 rate를 바꾸고자 계산하는 경우 클럭 F->D까지 rate를 결정하고 다시 클럭 D->G까지 재계산하는 과정을 보여준다.

 

clk_core_get_boundaries()

drivers/clk/clk.c

static void clk_core_get_boundaries(struct clk_core *clk,
                                    unsigned long *min_rate,
                                    unsigned long *max_rate)
{
        struct clk *clk_user;

        *min_rate = 0;
        *max_rate = ULONG_MAX;

        hlist_for_each_entry(clk_user, &clk->clks, child_node)
                *min_rate = max(*min_rate, clk_user->min_rate);

        hlist_for_each_entry(clk_user, &clk->clks, child_node)
                *max_rate = min(*max_rate, clk_user->max_rate);
}

자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.

  • min_rate
    • 자식 클럭들의 min_rate 값들 중 최대 min_rate
  • max_rate
    • 자식 클럭들의 max_rate 값들 중 최소 max_rate

 

다음 그림은 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아오는 것을 보여준다. (min_rate가 max_rate 보다 큰 숫자임을 주의한다)

 

clk_fetch_parent_index()

drivers/clk/clk.c

static int clk_fetch_parent_index(struct clk_core *clk,
                                  struct clk_core *parent)
{
        int i;

        if (!clk->parents) {
                clk->parents = kcalloc(clk->num_parents,
                                        sizeof(struct clk *), GFP_KERNEL);
                if (!clk->parents)
                        return -ENOMEM;
        }

        /*
         * find index of new parent clock using cached parent ptrs,
         * or if not yet cached, use string name comparison and cache
         * them now to avoid future calls to clk_core_lookup.
         */
        for (i = 0; i < clk->num_parents; i++) {
                if (clk->parents[i] == parent)
                        return i;

                if (clk->parents[i])
                        continue;

                if (!strcmp(clk->parent_names[i], parent->name)) {
                        clk->parents[i] = clk_core_lookup(parent->name);
                        return i;
                }
        }

        return -EINVAL;
}

현재 클럭에 연결된 인수 parent 클럭으로 인덱스 값을 알아온다. 실패하는 경우 음수 에러가 반환된다.

  • 코드 라인 6~11에서 parents 멤버에 할당된 배열이 없는 경우 부모 클럭 수 만큼 포인터 배열을 할당한다.
  • 코드 라인 18~20에서 부모 클럭 수만큼 인덱스를 증가시키며 요청한 부모 클럭인 경우 그 인덱스 값을 반환한다.
  • 코드 라인 22~23에서 인덱스에 해당하는 어떤 클럭이 있는 경우 skip 한다.
  • 코드 라인 25~28에서 인덱스에 해당하는 클럭이 없는 경우 인수 parent 클럭의 이름으로 검색하여 연결해 놓는다.

 

동적 rate 설정 -3- (recalc)

clk_calc_subtree()

drivers/clk/clk.c

static void clk_calc_subtree(struct clk_core *clk, unsigned long new_rate,
                             struct clk_core *new_parent, u8 p_index)
{
        struct clk_core *child;

        clk->new_rate = new_rate;
        clk->new_parent = new_parent;
        clk->new_parent_index = p_index;
        /* include clk in new parent's PRE_RATE_CHANGE notifications */
        clk->new_child = NULL;
        if (new_parent && new_parent != clk->parent)
                new_parent->new_child = clk;

        hlist_for_each_entry(child, &clk->children, child_node) {
                child->new_rate = clk_recalc(child, new_rate);
                clk_calc_subtree(child, child->new_rate, NULL, 0);
        }
}

현재 클럭 및 모든 연결된 하위 클럭들에 대해 새 rate, 새 부모, 새 부모 인덱스 값 등을 갱신하게 한다.

  • 코드 라인 6~8에서 현재 클럭의 new_rate, new_parent, new_parent_index 값을 갱신한다.
  • 코드 라인 10~12에서 부모가 변경된 경우 new_child에 현재 클럭을 대입한다. 그렇지 않은 경우 null을 대입한다.
  • 코드 라인 14~15에서 자식 클럭들 수 만큼 루프를 돌며 new_rate로 재계산하도록 한다.
  • 코드 라인 16에서 자식 노드에 대해 이 함수를 재귀 호출하여 계산하게 한다.

 

clk_recalc()

drivers/clk/clk.c

static unsigned long clk_recalc(struct clk_core *clk,
                                unsigned long parent_rate)
{
        if (clk->ops->recalc_rate)
                return clk->ops->recalc_rate(clk->hw, parent_rate);
        return parent_rate;
}

클럭 h/w 디바이스 드라이버를 통해 parent_rate 값을 사용하여 현재 클럭의 rate를 재계산해온다.

 

__clk_recalc_rates()

drivers/clk/clk.c

/**
 * __clk_recalc_rates
 * @clk: first clk in the subtree
 * @msg: notification type (see include/linux/clk.h)
 *
 * Walks the subtree of clks starting with clk and recalculates rates as it
 * goes.  Note that if a clk does not implement the .recalc_rate callback then
 * it is assumed that the clock will take on the rate of its parent.
 *
 * clk_recalc_rates also propagates the POST_RATE_CHANGE notification,
 * if necessary.
 *
 * Caller must hold prepare_lock.
 */
static void __clk_recalc_rates(struct clk_core *clk, unsigned long msg)
{
        unsigned long old_rate;
        unsigned long parent_rate = 0;
        struct clk_core *child;

        old_rate = clk->rate;

        if (clk->parent)
                parent_rate = clk->parent->rate;

        clk->rate = clk_recalc(clk, parent_rate);

        /*
         * ignore NOTIFY_STOP and NOTIFY_BAD return values for POST_RATE_CHANGE
         * & ABORT_RATE_CHANGE notifiers
         */
        if (clk->notifier_count && msg)
                __clk_notify(clk, msg, old_rate, clk->rate);

        hlist_for_each_entry(child, &clk->children, child_node)
                __clk_recalc_rates(child, msg);
}

 

 

 

rate 설정 -4- (mux 타입 rate 설정)

__clk_mux_determine_rate()

drivers/clk/clk.c

/*
 * Helper for finding best parent to provide a given frequency. This can be used
 * directly as a determine_rate callback (e.g. for a mux), or from a more
 * complex clock that may combine a mux with other operations.
 */
long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
                              unsigned long min_rate,
                              unsigned long max_rate,
                              unsigned long *best_parent_rate,
                              struct clk_hw **best_parent_p)
{
        return clk_mux_determine_rate_flags(hw, rate, min_rate, max_rate,
                                            best_parent_rate,
                                            best_parent_p, 0);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);

mux 타입 클럭 디바이스 드라이버의 ops->determine_rate에서 호출되는 콜백함수로도 사용된다. 요청한 rate 이하 값으로 가장 가까운 rate를 구한다. 출력 인수 best_parent_rate에 산출한 최적의 rate가 담기고 best_parent_p는 산출된 최적의 부모 클럭을 가리킨다. 단 min_rate ~ max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 0을 사용한다.

 

long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
                              unsigned long min_rate,
                              unsigned long max_rate,
                              unsigned long *best_parent_rate,
                              struct clk_hw **best_parent_p)
{
        return clk_mux_determine_rate_flags(hw, rate, min_rate, max_rate,
                                            best_parent_rate,
                                            best_parent_p,
                                            CLK_MUX_ROUND_CLOSEST);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);

요청한 rate에 가장 가까운 rate를 구한다. 출력 인수 best_parent_rate에 산출한 최적의 rate가 담기고 best_parent_p는 산출된 최적의 부모 클럭을 가리킨다. 단 min_rate ~ max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 0을 사용한다.

 

clk_mux_determine_rate_flags()

drivers/clk/clk.c

static long
clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long rate,
                             unsigned long min_rate,
                             unsigned long max_rate,
                             unsigned long *best_parent_rate,
                             struct clk_hw **best_parent_p,
                             unsigned long flags)
{
        struct clk_core *core = hw->core, *parent, *best_parent = NULL;
        int i, num_parents;
        unsigned long parent_rate, best = 0;

        /* if NO_REPARENT flag set, pass through to current parent */
        if (core->flags & CLK_SET_RATE_NO_REPARENT) {
                parent = core->parent;
                if (core->flags & CLK_SET_RATE_PARENT)
                        best = __clk_determine_rate(parent ? parent->hw : NULL,
                                                    rate, min_rate, max_rate);
                else if (parent)
                        best = clk_core_get_rate_nolock(parent);
                else
                        best = clk_core_get_rate_nolock(core);
                goto out;
        }

        /* find the parent that can provide the fastest rate <= rate */
        num_parents = core->num_parents;
        for (i = 0; i < num_parents; i++) {
                parent = clk_core_get_parent_by_index(core, i);
                if (!parent)
                        continue;
                if (core->flags & CLK_SET_RATE_PARENT)
                        parent_rate = __clk_determine_rate(parent->hw, rate,
                                                           min_rate,
                                                           max_rate);
                else
                        parent_rate = clk_core_get_rate_nolock(parent);
                if (mux_is_better_rate(rate, parent_rate, best, flags)) {
                        best_parent = parent;
                        best = parent_rate;
                }
        }

out:
        if (best_parent)
                *best_parent_p = best_parent->hw;
        *best_parent_rate = best;

        return best;
}

요청한 rate에 가장 적절한 rate를 구한다. 출력 인수 best_parent_rate에 산출한 최적의 rate가 담기고 best_parent_p는 산출된 최적의 부모 클럭을 가리킨다. 단 min_rate ~ max_rate 범위를 벗어나는 경우 0을 반환한다.

  • 코드 라인 14~24에서 현재 mux 클럭에서 CLK_SET_RATE_NO_REPARENT 플래그가 주어진 경우 현재 클럭에 대해 다른 부모 클럭을 선택하지 못하게된다. 즉 이미 선택된 부모 클럭은 변경하지 않고 고정한다. 이렇게 부모 클럭을 고정시킨 후 rate 값을 알아오는데 다음 3가지 중 하나로 결정하여 best 값으로 대입하고 out 레이블로 이동한다.
    • 코드 라인 16~18에서 현재 클럭에서 CLK_SET_RATE_PARENT 플래그가 사용되면 상위 부모 클럭으로 이동하여 최적의 rate 값을 알아온다.
    • 코드 라인 19~20에서 지정된 부모 클럭의 rate 값을 알아온다.
    • 코드 라인 21~22에서 현재 클럭의 rate 값을 알아온다.
  • 코드 라인 27~42에서 부모 클럭 수 만큼 루프를 돌며 최적의 rate가 산출될 수 있는 부모 클럭을 선택하려 한다. 연결되지 않은 부모 클럭은 skip하고 다음 2가지 중 하나의 상황으로 계속 루프를 돌며 산출된 부모 rate 값이 최적 rate 값인 경우 갱신한다.
    • 코드 라인 32~35에서 현재 클럭에 CLK_SET_RATE_PARENT 플래그가 사용되면 현재 인덱스의 상위 부모 클럭으로 이동하여 최적의 rate 값을 알아온다.
    • 코드 라인 36~37에서 현재 인덱스의 부모 클럭 rate 값을 알아온다.
  • 코드 라인 45~46에서 부모 클럭을 선택할 수 있고 최적의 부모가 산출된 경우 출력 인수 best_parent_p가 선택된 부모 클럭을 가리키게 한다.
  • 코드 라인 47에서 출력 인수 best_parent_rate에 산출된 best 값을 대입한다.

 

mux_is_better_rate()

drivers/clk/clk.c

static bool mux_is_better_rate(unsigned long rate, unsigned long now,
                           unsigned long best, unsigned long flags)
{
        if (flags & CLK_MUX_ROUND_CLOSEST)
                return abs(now - rate) < abs(best - rate);

        return now <= rate && now > best;
}

mux가 설정하고자 하는 rate 값에 now 값이 best 값보다 더 적절한 경우 true(1)를 반환한다. CLK_MUX_ROUND_CLOSEST 플래그의 사용 여부에 따라 적절한 값의 여부 판단이 바뀐다.

  • 사용하지 않는 경우 rate 이하 범위에서 now 값이 best 값 보다 더 가까운 경우 true(1)
  • 사용하는 경우 요청 rate에 now 값이 best 보다 더 가까운 경우 true(1)

 

 

__clk_determine_rate()

drivers/clk/clk.c

/**
 * __clk_determine_rate - get the closest rate actually supported by a clock
 * @hw: determine the rate of this clock
 * @rate: target rate
 * @min_rate: returned rate must be greater than this rate
 * @max_rate: returned rate must be less than this rate
 *
 * Caller must hold prepare_lock.  Useful for clk_ops such as .set_rate and
 * .determine_rate.
 */
unsigned long __clk_determine_rate(struct clk_hw *hw,
                                   unsigned long rate,
                                   unsigned long min_rate,
                                   unsigned long max_rate)
{
        if (!hw)
                return 0;

        return clk_core_round_rate_nolock(hw->core, rate, min_rate, max_rate);
}
EXPORT_SYMBOL_GPL(__clk_determine_rate);

클럭 디바이스 드라이버가 실제 지원하는 rate에 가장 가까운 값을 알아온다. min_rate ~ max_rate 범위를 벗어나는 경우 0을 반환한다.

 

rate 설정 -5- (divider, fractional, … 타입 rate 설정)

clk_round_rate()

drivers/clk/clk.c

/**
 * clk_round_rate - round the given rate for a clk
 * @clk: the clk for which we are rounding a rate
 * @rate: the rate which is to be rounded
 *
 * Takes in a rate as input and rounds it to a rate that the clk can actually
 * use which is then returned.  If clk doesn't support round_rate operation
 * then the parent rate is returned.
 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
        unsigned long ret;

        if (!clk)
                return 0;

        clk_prepare_lock();
        ret = __clk_round_rate(clk, rate);
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_round_rate);

요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다.

 

__clk_round_rate()

drivers/clk/clk.c

/**
 * __clk_round_rate - round the given rate for a clk
 * @clk: round the rate of this clock
 * @rate: the rate which is to be rounded
 *
 * Caller must hold prepare_lock.  Useful for clk_ops such as .set_rate
 */
unsigned long __clk_round_rate(struct clk *clk, unsigned long rate)
{
        unsigned long min_rate;
        unsigned long max_rate;

        if (!clk)
                return 0;

        clk_core_get_boundaries(clk->core, &min_rate, &max_rate);

        return clk_core_round_rate_nolock(clk->core, rate, min_rate, max_rate);
}
EXPORT_SYMBOL_GPL(__clk_round_rate);

요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 호출 시 prepare_lock을 잡아야 한다.

  • 코드 라인 16에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 18에서 요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 또한 min_rate 및 max_rate 값을 초과하는 경우 0을 반환한다.

 

clk_core_round_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_round_rate_nolock(struct clk_core *clk,
                                                unsigned long rate,
                                                unsigned long min_rate,
                                                unsigned long max_rate)
{
        unsigned long parent_rate = 0;
        struct clk_core *parent;
        struct clk_hw *parent_hw;

        if (!clk)
                return 0;

        parent = clk->parent;
        if (parent)
                parent_rate = parent->rate;

        if (clk->ops->determine_rate) {
                parent_hw = parent ? parent->hw : NULL;
                return clk->ops->determine_rate(clk->hw, rate,
                                                min_rate, max_rate,
                                                &parent_rate, &parent_hw);
        } else if (clk->ops->round_rate)
                return clk->ops->round_rate(clk->hw, rate, &parent_rate);
        else if (clk->flags & CLK_SET_RATE_PARENT)
                return clk_core_round_rate_nolock(clk->parent, rate, min_rate,
                                                  max_rate);
        else
                return clk->rate;
}

 

요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 또한 min_rate 및 max_rate 값을 초과하는 경우 0을 반환한다.

  • 코드 라인 13~15에서 부모 클럭이 있는 경우 부모 클럭의 rate 값을 알아온다.
  • 코드 라인 17~21에서 클럭이 ops->determine_rate를 지원하는 mux 타입 클럭인 경우 호출하여 적절한 rate 값을 산출하여 반환한다.
    • 현재 mux 클럭이 선택할 수 있는 부모 클럭을 변경해가며 적절한 rate 값을 알아온다.
  • 코드 라인 22~23에서 클럭이 ops->round_rate를 지원하는 배율 조정 타입 클럭인 경우 호출하여 적절한 rate 값을 산출하여 반환한다.
    • 현재 배율 조정 타입 클럭이 선택할 수 있는 배율을 변경해가며 적절한 rate 값을 알아온다.
  • 코드 라인 24~26에서 클럭이 ops->determine_rate 및 ops_round_rate를 지원하지 않지만 CLK_SET_RATE_PARENT가 설정된 gate 타입인 경우 부모 클럭에서 적절한 rate 값을 산출해온다.
  • 코드 라인 27~28에서 클럭이 ops->determine_rate 및 ops_round_rate를 지원하지 않지만 CLK_SET_RATE_PARENT가 설정되지 않은 gate 타입인 경우 현재 클럭의 rate 값을 반환한다.

 

클럭 준비/해지

clk_prepare()

drivers/clk/clk.c

/**
 * clk_prepare - prepare a clock source
 * @clk: the clk being prepared
 *
 * clk_prepare may sleep, which differentiates it from clk_enable.  In a simple
 * case, clk_prepare can be used instead of clk_enable to ungate a clk if the
 * operation may sleep.  One example is a clk which is accessed over I2c.  In
 * the complex case a clk ungate operation may require a fast and a slow part.
 * It is this reason that clk_prepare and clk_enable are not mutually
 * exclusive.  In fact clk_prepare must be called before clk_enable.
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_prepare(struct clk *clk)
{
        int ret;

        if (!clk)
                return 0;

        clk_prepare_lock();
        ret = clk_core_prepare(clk->core);
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_prepare);

lock을 획득한 상태에서 상위 클럭 소스까지 prepare 시킨다.

 

clk_core_prepare()

drivers/clk/clk.c

static int clk_core_prepare(struct clk_core *clk)
{
        int ret = 0;

        if (!clk)
                return 0;

        if (clk->prepare_count == 0) {
                ret = clk_core_prepare(clk->parent);
                if (ret)
                        return ret;

                if (clk->ops->prepare) {
                        ret = clk->ops->prepare(clk->hw);
                        if (ret) {
                                clk_core_unprepare(clk->parent);
                                return ret;
                        }
                }
        }

        clk->prepare_count++;

        return 0;
}

상위 클럭 소스까지 prepare 시킨다.

  • 코드 라인 5~6에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~11에서 한 번도 prepare 한 적이 없는 경우 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 prepare 한다. 만일 에러가 발생하면 재귀 호출을 빠져나간다.
  • 코드 라인 13~19에서 custom 클럭 디바이스에 구현된 ops->prepare 후크에 연결된 콜백 함수를 수행한다. 만일 에러가 발생한 경우 unprepare 한다.
  • 코드 라인 22~24에서 prepare_count를 1 증가시키고 성공(0)으로 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 증가된다.

 

clk_unprepare()

drivers/clk/clk.c

/**
 * clk_unprepare - undo preparation of a clock source
 * @clk: the clk being unprepared
 *
 * clk_unprepare may sleep, which differentiates it from clk_disable.  In a
 * simple case, clk_unprepare can be used instead of clk_disable to gate a clk
 * if the operation may sleep.  One example is a clk which is accessed over
 * I2c.  In the complex case a clk gate operation may require a fast and a slow
 * part.  It is this reason that clk_unprepare and clk_disable are not mutually
 * exclusive.  In fact clk_disable must be called before clk_unprepare.
 */
void clk_unprepare(struct clk *clk)
{
        if (IS_ERR_OR_NULL(clk))
                return;

        clk_prepare_lock();
        clk_core_unprepare(clk->core);
        clk_prepare_unlock();
}
EXPORT_SYMBOL_GPL(clk_unprepare);

lock을 획득한 상태에서 상위 클럭 소스까지 unprepare 시킨다.

 

clk_core_unprepare()

drivers/clk/clk.c

static void clk_core_unprepare(struct clk_core *clk)
{
        if (!clk)
                return;

        if (WARN_ON(clk->prepare_count == 0))
                return;

        if (--clk->prepare_count > 0)
                return;

        WARN_ON(clk->enable_count > 0);

        if (clk->ops->unprepare)
                clk->ops->unprepare(clk->hw);

        clk_core_unprepare(clk->parent);
}

상위 클럭 소스까지 unprepare 시킨다.

  • 코드 라인 3~4에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 9~10에서 prepare_count를 1 감소시킨 값이 0보다 큰 경우 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 14~15에서 custom 클럭 디바이스에 구현된 ops->unprepare 후크에 연결된 콜백 함수를 수행한다.
  • 코드 라인 17에서 최상위 부모 클럭까지 이 함수를 재귀 호출한다.

 

클럭 게이트 enable & disable

clk_enable()

drivers/clk/clk.c

/**
 * clk_enable - ungate a clock
 * @clk: the clk being ungated
 *
 * clk_enable must not sleep, which differentiates it from clk_prepare.  In a
 * simple case, clk_enable can be used instead of clk_prepare to ungate a clk
 * if the operation will never sleep.  One example is a SoC-internal clk which
 * is controlled via simple register writes.  In the complex case a clk ungate
 * operation may require a fast and a slow part.  It is this reason that
 * clk_enable and clk_prepare are not mutually exclusive.  In fact clk_prepare
 * must be called before clk_enable.  Returns 0 on success, -EERROR
 * otherwise.
 */
int clk_enable(struct clk *clk)
{
        unsigned long flags;
        int ret;

        flags = clk_enable_lock();
        ret = __clk_enable(clk);
        clk_enable_unlock(flags);

        return ret;
}
EXPORT_SYMBOL_GPL(clk_enable);

lock을 획득한 상태에서 상위 클럭 소스까지 enable 시킨다.

 

__clk_enable()

drivers/clk/clk.c

static int __clk_enable(struct clk *clk)
{
        if (!clk)
                return 0;

        return clk_core_enable(clk->core);
}

상위 클럭 소스까지 enable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않으면 0의 결과값으로 함수를 빠져나간다.

 

clk_core_enable()

drivers/clk/clk.c

static int clk_core_enable(struct clk_core *clk)
{
        int ret = 0;

        if (!clk)
                return 0;

        if (WARN_ON(clk->prepare_count == 0))
                return -ESHUTDOWN;

        if (clk->enable_count == 0) {
                ret = clk_core_enable(clk->parent);

                if (ret)
                        return ret;

                if (clk->ops->enable) {
                        ret = clk->ops->enable(clk->hw);
                        if (ret) {
                                clk_core_disable(clk->parent);
                                return ret;
                        }
                }
        }

        clk->enable_count++;
        return 0;
}

상위 클럭 소스까지 enable 시킨다.

  • 코드 라인 5~6에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~9에서 이 클럭이 prepare된 적이 없으면 경고 메시지를 출력하고 -ESHUTDOWN 에러 코드를 반환한다.
  • 코드 라인 11~15에서 한 번도 enable 한 적이 없는 경우 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 enable 한다. 만일 에러가 발생하면 재귀 호출을 빠져나간다.
  • 코드 라인 17~23에서 gate 타입 클럭 디바이스에 구현된 ops->enable 후크에 연결된 콜백 함수를 수행하여 실제 hw의 gate를 연다. 만일 에러가 발생한 경우 disable 한다.
  • 코드 라인 22~24에서 enable_count를 1 증가시키고 성공(0)으로 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 증가된다.

 

clk_disable()

drivers/clk/clk.c

/**
 * clk_disable - gate a clock
 * @clk: the clk being gated
 *
 * clk_disable must not sleep, which differentiates it from clk_unprepare.  In
 * a simple case, clk_disable can be used instead of clk_unprepare to gate a
 * clk if the operation is fast and will never sleep.  One example is a
 * SoC-internal clk which is controlled via simple register writes.  In the
 * complex case a clk gate operation may require a fast and a slow part.  It is
 * this reason that clk_unprepare and clk_disable are not mutually exclusive.
 * In fact clk_disable must be called before clk_unprepare.
 */
void clk_disable(struct clk *clk)
{
        unsigned long flags;

        if (IS_ERR_OR_NULL(clk))
                return;

        flags = clk_enable_lock();
        __clk_disable(clk);
        clk_enable_unlock(flags);
}
EXPORT_SYMBOL_GPL(clk_disable);

lock을 획득한 상태에서 상위 클럭 소스까지 disable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않거나 에러 코드인 경우 함수를 빠져나간다.

 

__clk_disable()

drivers/clk/clk.c

static void __clk_disable(struct clk *clk)
{
        if (!clk)
                return;

        clk_core_disable(clk->core);
}

상위 클럭 소스까지 disable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않으면 함수를 빠져나간다.

 

clk_core_disable()

drivers/clk/clk.c

static void clk_core_disable(struct clk_core *clk)
{
        if (!clk)
                return;

        if (WARN_ON(clk->enable_count == 0))
                return;

        if (--clk->enable_count > 0)
                return;

        if (clk->ops->disable)
                clk->ops->disable(clk->hw);

        clk_core_disable(clk->parent);
}

상위 클럭 소스까지 disable 시킨다.

  • 코드 라인 3~4에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 함수를 빠져나간다.
  • 코드 라인 6~7에서 이 클럭이 enable된 적이 없으면 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 9~10에서 enable_count를 1 감소시킨 값이 0보다 큰 경우 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 14~15에서 gate 타입 클럭 디바이스에 구현된 ops->disable 후크에 연결된 콜백 함수를 수행하여 실제 hw의 gate를 닫는다.
  • 코드 라인 17에서 최상위 부모 클럭까지 이 함수를 재귀 호출한다.

 

mux 타입 클럭에서 입력 클럭 소스(부모 클럭) 선택

clk_set_parent()

drivers/clk/clk.c

/**
 * clk_set_parent - switch the parent of a mux clk
 * @clk: the mux clk whose input we are switching
 * @parent: the new input to clk
 *
 * Re-parent clk to use parent as its new input source.  If clk is in
 * prepared state, the clk will get enabled for the duration of this call. If
 * that's not acceptable for a specific clk (Eg: the consumer can't handle
 * that, the reparenting is glitchy in hardware, etc), use the
 * CLK_SET_PARENT_GATE flag to allow reparenting only when clk is unprepared.
 *
 * After successfully changing clk's parent clk_set_parent will update the
 * clk topology, sysfs topology and propagate rate recalculation via
 * __clk_recalc_rates.
 *
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_set_parent(struct clk *clk, struct clk *parent)
{
        if (!clk)
                return 0;

        return clk_core_set_parent(clk->core, parent ? parent->core : NULL);
}
EXPORT_SYMBOL_GPL(clk_set_parent);

mux 타입 클럭의 입력 클럭 소스(부모 클럭)를 선택하고 연결된 모든 자식 클럭들의 rate는 재계산된다. 성공인 경우 0을 반환한다.

 

clk_core_set_parent()

drivers/clk/clk.c

static int clk_core_set_parent(struct clk_core *clk, struct clk_core *parent)
{
        int ret = 0;
        int p_index = 0;
        unsigned long p_rate = 0;

        if (!clk)
                return 0;

        /* verify ops for for multi-parent clks */
        if ((clk->num_parents > 1) && (!clk->ops->set_parent))
                return -ENOSYS;

        /* prevent racing with updates to the clock topology */
        clk_prepare_lock();

        if (clk->parent == parent)
                goto out;

        /* check that we are allowed to re-parent if the clock is in use */
        if ((clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) {
                ret = -EBUSY;
                goto out;
        }

        /* try finding the new parent index */
        if (parent) {
                p_index = clk_fetch_parent_index(clk, parent);
                p_rate = parent->rate;
                if (p_index < 0) {
                        pr_debug("%s: clk %s can not be parent of clk %s\n",
                                        __func__, parent->name, clk->name);
                        ret = p_index;
                        goto out;
                }
        }

        /* propagate PRE_RATE_CHANGE notifications */
        ret = __clk_speculate_rates(clk, p_rate);

        /* abort if a driver objects */
        if (ret & NOTIFY_STOP_MASK)
                goto out;

        /* do the re-parent */
        ret = __clk_set_parent(clk, parent, p_index);

        /* propagate rate an accuracy recalculation accordingly */
        if (ret) {
                __clk_recalc_rates(clk, ABORT_RATE_CHANGE);
        } else {
                __clk_recalc_rates(clk, POST_RATE_CHANGE);
                __clk_recalc_accuracies(clk);
        }

out:
        clk_prepare_unlock();

        return ret;
}

mux 타입 클럭의 부모 클럭(입력 클럭 소스)을 선택하고 연결된 모든 자식 클럭들의 rate는 재계산된다. 성공인 경우 0을 반환한다.

  • 코드 라인 11~12에서 2개 이상의 부모 클럭(입력 클럭 소스)이 있고 ops->set_parent가 구현된 mux 타입 클럭이 아니면 -ENOSYS 에러를 반환한다.
  • 코드 라인 17~18에서 요청한 부모 클럭(입력 클럭 소스)이 이미 지정되어 있었던 경우 변경할 필요가 없으므로 성공(0) 결과로 함수를 빠져나온다.
  • 코드 라인 21~24에서 클럭에 CLK_SET_PARENT_GATE 플래그가 사용되었고 prepare된 상태인 경우 -EBUSY 에러를 반환한다.
    • unprepare된 상태에서만 입력 클럭 소스(부모 클럭)을 선택할 수 있게 제한한 장치이다.
  • 코드 라인 27~36에서 부모 클럭(입력 클럭 소스)의 인덱스와 rate 값을 알아온다. 만일 알아올 수 없으면 에러를 반환한다.
  • 코드 라인 39~43에서 현재 클럭 이하 연결된 모든 자식 클럭에 대해 PRE_RATE_CHANGE를 통지한다. 만일 결과가 NOTIFY_STOP 또는 NOTIFY_BAD인 경우 함수를 빠져나간다.
  • 코드 라인 46~54에서 부모 클럭(입력 클럭 소스)을 선택한다. 만일 에러가 발생한 경우 ABORT_RATE_CHANGE를 하위 노드에 전파한다. 성공한 경우에는 POST_RATE_CHANGE를 하위 노드에 전파한 후 현재 노드의 모든 하위 노드를 재계산하도록 요청한다.

 

__clk_set_parent()

drivers/clk/clk.c

static int __clk_set_parent(struct clk_core *clk, struct clk_core *parent,
                            u8 p_index)
{
        unsigned long flags;
        int ret = 0;
        struct clk_core *old_parent;

        old_parent = __clk_set_parent_before(clk, parent);

        /* change clock input source */
        if (parent && clk->ops->set_parent)
                ret = clk->ops->set_parent(clk->hw, p_index);

        if (ret) {
                flags = clk_enable_lock();
                clk_reparent(clk, old_parent);
                clk_enable_unlock(flags);

                if (clk->prepare_count) {
                        flags = clk_enable_lock();
                        clk_core_disable(clk);
                        clk_core_disable(parent);
                        clk_enable_unlock(flags);
                        clk_core_unprepare(parent);
                }
                return ret;
        }

        __clk_set_parent_after(clk, parent, old_parent);

        return 0;
}

mux 타입 클럭의 부모 클럭(입력 클럭 소스)을 선택한다. 성공인 경우 0을 반환한다.

  • 코드 라인 8에서 입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.
  • 코드 라인 11~12에서 mux 타입 클럭에서 ops->set_parent에 지정된 콜백 함수를 호출하여 실제 hw로 부모 클럭(입력 클럭 소스)을 선택하게 한다.
  • 코드 라인 14~27에서 에서 처리 시 에러가 발생한 경우 기존 부모 클럭(입력 클럭 소스)로 재 변경하고 현재 클럭을 disable하고 부모 클럭은 disable 및 unprepare를 수행하고 에러를 반환한다.
  • 코드 라인 29에서 입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.

 

__clk_set_parent_before()

drivers/clk/clk.c

static struct clk_core *__clk_set_parent_before(struct clk_core *clk,
                                           struct clk_core *parent)
{
        unsigned long flags;
        struct clk_core *old_parent = clk->parent;

        /*
         * Migrate prepare state between parents and prevent race with
         * clk_enable().
         *
         * If the clock is not prepared, then a race with
         * clk_enable/disable() is impossible since we already have the
         * prepare lock (future calls to clk_enable() need to be preceded by
         * a clk_prepare()).
         *
         * If the clock is prepared, migrate the prepared state to the new
         * parent and also protect against a race with clk_enable() by
         * forcing the clock and the new parent on.  This ensures that all
         * future calls to clk_enable() are practically NOPs with respect to
         * hardware and software states.
         *
         * See also: Comment for clk_set_parent() below.
         */
        if (clk->prepare_count) {
                clk_core_prepare(parent);
                flags = clk_enable_lock();
                clk_core_enable(parent);
                clk_core_enable(clk);
                clk_enable_unlock(flags);
        }

        /* update the clk tree topology */
        flags = clk_enable_lock();
        clk_reparent(clk, parent);
        clk_enable_unlock(flags);

        return old_parent;
}

입력 클럭 소스(부모 클럭)을 선택하기 전에 처리할 일을 수행한다. 결과로 기존 부모 클럭을 반환한다.

  • 코드 라인 24~30에서 클럭이 prepare된 경우 부모 클럭을 prepare 및 enable 시키고 현재 클럭도 enable 한다.
  • 코드 라인 33~35에서 clock tree 토플로지를 갱신한다.

 

__clk_set_parent_after()

drivers/clk/clk.c

static void __clk_set_parent_after(struct clk_core *core,
                                   struct clk_core *parent,
                                   struct clk_core *old_parent)
{
        unsigned long flags;

        /*
         * Finish the migration of prepare state and undo the changes done
         * for preventing a race with clk_enable().
         */
        if (core->prepare_count) {
                flags = clk_enable_lock();
                clk_core_disable(core);
                clk_core_disable(old_parent);
                clk_enable_unlock(flags);
                clk_core_unprepare(old_parent);
        }
}

입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.

  • 코드 라인 11~17에서 클럭이 prepare된 경우 현재 클럭을 disable하고, 부모 클럭도 disable 및 unprepare한다.

 

clk_reparent()

drivers/clk/clk.c

static void clk_reparent(struct clk_core *clk, struct clk_core *new_parent)
{
        hlist_del(&clk->child_node);

        if (new_parent) {
                /* avoid duplicate POST_RATE_CHANGE notifications */
                if (new_parent->new_child == clk)
                        new_parent->new_child = NULL;

                hlist_add_head(&clk->child_node, &new_parent->children);
        } else {
                hlist_add_head(&clk->child_node, &clk_orphan_list);
        }

        clk->parent = new_parent;
}

clock tree 토플로지를 갱신한다.

  • 코드 라인 3에서 부모 클럭(입력 클럭 소스)의 child_node에서 현재 클럭을 제거한다.
  • 코드 라인 5~10에서 새 부모 클럭(입력 클럭 소스)이 지정된 경우 그 부모 클럭의 children으로 추가한다. 만일 새 부모 클럭의 new_child에 현재 클럭이 지정되어 있었던 경우라면 new_child에 null을 대입한다.
  • 코드 라인 11~13에서 새 부모 클럭이 지정되지 않은 경우 고아 리스트에 현재 클럭을 추가한다.

 

클럭 통지(notify) 기능

clk notifier로 등록

clk_notifier_register()

drivers/clk/clk.c

/***        clk rate change notifiers        ***/

/**
 * clk_notifier_register - add a clk rate change notifier
 * @clk: struct clk * to watch
 * @nb: struct notifier_block * with callback info
 *
 * Request notification when clk's rate changes.  This uses an SRCU
 * notifier because we want it to block and notifier unregistrations are
 * uncommon.  The callbacks associated with the notifier must not
 * re-enter into the clk framework by calling any top-level clk APIs;
 * this will cause a nested prepare_lock mutex.
 *
 * In all notification cases cases (pre, post and abort rate change) the
 * original clock rate is passed to the callback via struct
 * clk_notifier_data.old_rate and the new frequency is passed via struct
 * clk_notifier_data.new_rate.
 *
 * clk_notifier_register() must be called from non-atomic context.
 * Returns -EINVAL if called with null arguments, -ENOMEM upon
 * allocation failure; otherwise, passes along the return value of
 * srcu_notifier_chain_register().
 */
int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
{
        struct clk_notifier *cn;
        int ret = -ENOMEM;

        if (!clk || !nb)
                return -EINVAL;

        clk_prepare_lock();

        /* search the list of notifiers for this clk */
        list_for_each_entry(cn, &clk_notifier_list, node)
                if (cn->clk == clk)
                        break;

        /* if clk wasn't in the notifier list, allocate new clk_notifier */
        if (cn->clk != clk) {
                cn = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL);
                if (!cn)
                        goto out;

                cn->clk = clk;
                srcu_init_notifier_head(&cn->notifier_head);

                list_add(&cn->node, &clk_notifier_list);
        }

        ret = srcu_notifier_chain_register(&cn->notifier_head, nb);

        clk->core->notifier_count++;

out:
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_notifier_register);

클럭의 notify chain에 nofitier_block을 등록한다.

  • 코드 라인 33~35에서 clk_notifier_list에 요청한 클럭이 있는지 검색한다.
  • 코드 라인 38~47에서 검색되지 않는 경우 clk_notifier 구조체를 할당하고 클럭 정보를 대입한 후 clk_notifier_list에 등록한다.
  • 코드 라인 49에서 clk_notifier 구조체의 멤버 notifier_head에 요청한 notifier_block을 추가한다.
  • 코드 라인 51에서 클럭의 notifier_count 값을 1 증가시킨다.

 

Rate 변경에 따른 rate 상태 통지

clk_propagate_rate_change()

drivers/clk/clk.c

/*
 * Notify about rate changes in a subtree. Always walk down the whole tree
 * so that in case of an error we can walk down the whole tree again and
 * abort the change.
 */
static struct clk_core *clk_propagate_rate_change(struct clk_core *clk,
                                                  unsigned long event)
{
        struct clk_core *child, *tmp_clk, *fail_clk = NULL;
        int ret = NOTIFY_DONE;

        if (clk->rate == clk->new_rate)
                return NULL;

        if (clk->notifier_count) {
                ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
                if (ret & NOTIFY_STOP_MASK)
                        fail_clk = clk;
        }

        hlist_for_each_entry(child, &clk->children, child_node) {
                /* Skip children who will be reparented to another clock */
                if (child->new_parent && child->new_parent != clk)
                        continue;
                tmp_clk = clk_propagate_rate_change(child, event);
                if (tmp_clk)
                        fail_clk = tmp_clk;
        }

        /* handle the new child who might not be in clk->children yet */
        if (clk->new_child) {
                tmp_clk = clk_propagate_rate_change(clk->new_child, event);
                if (tmp_clk)
                        fail_clk = tmp_clk;
        }

        return fail_clk;
}

현재 클럭에 연결된 모든 하위 트리에 rate 변화를 통지한다. 성공 시 0을 반환한다.

  • 코드 라인 12~13에서 rate의 변화가 없는 경우 null을 반환한다.
  • 코드 라인 15~19에서 현재 클럭에 notifier_count가 있는 경우 notify한다. 만일 결과가 NOTIFY_BAD 또는 NOTIFY_STOP을 갖는 경우 fail_clk에 이 클럭을 대입한다.
  • 코드 라인 21~24에서 자식 클럭 수 만큼 루프를 돌며 요청 클럭이 이 자식 클럭의 새로운 부모 클럭이 아닌 경우 skip 한다.
  • 코드 라인 25~28에서 자식 클럭으로 요청 이벤트를 전파한다.  만일 실패한 경우 fail_clk에 tmp_clk를 담는다.
  • 코드 라인 31~35에서 요청 클럭의 new_child가 있는 경우(곧 children에 들어갈) 그 new_child 클럭에 대해서도 전파한다. 만일 실패한 경우 fail_clk에 tmp_clk를 담는다.

 

__clk_notify()

drivers/clk/clk.c

/**
 * __clk_notify - call clk notifier chain
 * @clk: struct clk * that is changing rate
 * @msg: clk notifier type (see include/linux/clk.h)
 * @old_rate: old clk rate
 * @new_rate: new clk rate
 *
 * Triggers a notifier call chain on the clk rate-change notification
 * for 'clk'.  Passes a pointer to the struct clk and the previous
 * and current rates to the notifier callback.  Intended to be called by
 * internal clock code only.  Returns NOTIFY_DONE from the last driver
 * called if all went well, or NOTIFY_STOP or NOTIFY_BAD immediately if
 * a driver returns that.
 */
static int __clk_notify(struct clk_core *clk, unsigned long msg,
                unsigned long old_rate, unsigned long new_rate)
{
        struct clk_notifier *cn;
        struct clk_notifier_data cnd;
        int ret = NOTIFY_DONE;

        cnd.old_rate = old_rate;
        cnd.new_rate = new_rate;

        list_for_each_entry(cn, &clk_notifier_list, node) {
                if (cn->clk->core == clk) {
                        cnd.clk = cn->clk;
                        ret = srcu_notifier_call_chain(&cn->notifier_head, msg,
                                        &cnd);
                }
        }

        return ret;
}

클럭 통지 리스트에 등록된 현재 클럭의 notifier 체인에 연결된 항목에 대해 srcu를 사용하여 모두 통지한다.

 

참고

 

Common Clock Framework -1- (초기화)

 

Common Clock Framework -1- (초기화)

컴퓨터 하드웨어에는 많은 클럭 장치를 통해 클럭이 공급되고 있다. 시스템 내부에 cpu core에 들어가는 클럭부터 timer, i2c, uart 등 수 ~ 수십 종류의 클럭이 사용된다. 각각의 ARM SoC들은 개개의 클럭 디바이스 드라이버를 통해 클럭 설정을 하는데 하드웨어도 천차만별이고 구현된 드라이버도 기존 코드를 그대로 copy & paste를 통해 사용되고 심지어 clk 구조체 까지도 바꿔서 사용해오고 있다. 이를 해결해 보고자 common clock framework을 준비하였다. CCF는 커널 v3.4에서 처음 소개되었고 커널 v4.0에서 clk 구조체의 대부분을 clk_core 구조체로 옮겼다.

 

drivers/clk/clk.c를 제외하고 다음과 같이 많은 시스템에서 clk 구조체를 변경하여 사용했음을 알 수 있다.

./include/linux/sh_clk.h:37:struct clk {
./drivers/clk/clk.c:80:struct clk {
./arch/c6x/include/asm/clock.h:82:struct clk {
./arch/mips/include/asm/clock.h:20:struct clk {
./arch/mips/include/asm/mach-ar7/ar7.h:147:struct clk {
./arch/mips/ralink/clk.c:19:struct clk {
./arch/mips/jz4740/clock.h:45:struct clk {
./arch/mips/ath79/clock.c:31:struct clk {
./arch/mips/lantiq/clk.h:55:struct clk {
./arch/mips/bcm63xx/clk.c:19:struct clk {
./arch/blackfin/include/asm/clocks.h:61:struct clk {
./arch/blackfin/mach-common/clock.h:14:struct clk {
./arch/arm/mach-lpc32xx/clock.h:22:struct clk {
./arch/arm/mach-w90x900/clock.h:18:struct clk {
./arch/arm/mach-sa1100/clock.c:26:struct clk {
./arch/arm/mach-davinci/clock.h:87:struct clk {
./arch/arm/mach-mmp/clock.h:18:struct clk {
./arch/arm/mach-versatile/include/mach/clkdev.h:6:struct clk {
./arch/arm/mach-pxa/clock.h:11:struct clk {
./arch/arm/mach-omap1/clock.h:141:struct clk {
./arch/arm/mach-ep93xx/clock.c:30:struct clk {
./arch/m68k/include/asm/mcfclk.h:16:struct clk {
./arch/avr32/mach-at32ap/clock.h:20:struct clk {
./arch/unicore32/kernel/clock.c:30:struct clk {

 

drivers/clk 디렉토리를 살펴보면 수 백 개의 클럭 관련 디바이스 파일이 존재한다.

common clock framework에서 사용하는 구조체는 다음과 같다.

  • clk
  • clk_core
  • clk_hw
  • clk_ops
  • clk_init_data

 

다음 그림은 최근 커널의 Common Clk Framework를 보여준다.

 

다음 그림은 Common Clk Framework이 소개되었을 당시의 모습을 보여준다.

 

Basic clock implementations common

가장 많이 사용할 수 있는 공통 기본 클럭 구현이 다음과  같이 준비되어 있다. 공통 기본 클럭 구현만으로 구성 할 수 없는 시스템은 더 확장하여 구성할 수도 있다.

  • Fixed rate clock 구현
    • 고정된 rate와 정밀도(accuracy)로 동작하고 gate는 제어할 수 없다.
    • compatible
      • rate를 s/w로 조절하는 컨트롤 기능이 없는 경우 이 common 드라이버를 사용한다.
      • fixed-clock
  • Gated clock 구현
    • gate를 제어 할 수 있다.
    • rate는 직접 지정할 수 없고 고정(fixed)된 부모 clock으로부터 rate는 상속받아야 한다.
    • compatible
      • 실제 gate를 제어해야하기 때문에 각 클럭 컨틀롤러 제조사의 레지스터를 컨트롤하는 각자의 디바이스 드라이버 필요하다.
      • 예) ti,gate-clock”
  • divider clock 구현
    • 분배기로 rate가 설정되고 gate는 제어할 수 없다.
    • rate = 고정된 부모 clock의 rate / 분배값(div)
    • compatible
      • 실제 분배율을 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      • 예) “ti,divider-clock”
  • Simple multiplexer clock 구현
    • 간단한 멀티플렉서로 동작하여 dynamic하게 부모 클럭을 선택할 수 있고 gate는 제어할 수 없다.
    • compatible
      • 실제 입력 소스를 선택해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      • 예) “ti,mux-clock”
  • Fixed multiplier and divider clock 구현
    • 배율기 및 분배기로 동작하여 rate를 설정하고 gate는 제어할 수 없다.
    • rate  = 고정된 부모 clock의 rate / 분배값(div) * 배율값(mult)
    • compatible
      • 배율을 s/w로 조절하는 컨트롤 기능이 없는 경우 이 common 드라이버를 사용한다.
      • fixed-factor-clock
  • Fractional divider clock 구현
    • 분수 분배기로 rate를 설정하고 gate는 제어할 수 없다.
    • rate = 고정된 부모 clock의 rate * 분수(fractional)
    • compatible
      • 실제 분수 분배율을 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
  • Composite clock 구현
    • fixed rate clock, mux(multiplexer) clock 및 gate clock까지 동시에 2 개 이상 복합 구성하여 동작한다.
    • compatible
      • 복합 동작에 따른 조작을 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      • 예)  “ti,composite-clock”
  • Gpio gated clock 구현
    • gpio를 사용하여 gate를 제어한다.
    • rate는 직접 지정할 수 없고 고정(fixed)된 부모로부터 rate는 상속받아야 한다.
    •  compatible
      • gate의 조작을 common gpio 드라이버를 통해서 하므로 이 common 드라이버를 사용한다.
      • 실제 하드웨어 조작을 하지만 common gpio 디바이스 드라이버를 통해 컨트롤 하므로 별도의 클럭 디바이스 드라이버를 사용하지 않아도 된다.
      • gpio-gate-clock

 

다음 그림은 구현된 공통 클럭 디바이스 드라이버들의 사용예를 각각 보여준다.

  • 8개의 common 클럭 디바이스 드라이버가 준비되어 있다.
    • 그 중 3개의 클럭 디바이스 드라이버는 device tree로 설정 가능하다. (커널 v4.10 기준)
    • 나머지 5개는 third party 클럭 디바이스 드라이버를 통해 설정 가능하다 (ti, samsung, etc…)
  • 클럭 하드웨어 도면은 아래 사이트를 참고한다.

 

다음 그림은 common 클럭 타입별 커스텀 드라이버가 제어를 해야할 레지스터 부분을 보여준다.

 

다음 그림은 common 클럭 타입별 사용되는 ops 콜백 함수를 보여준다.

 

Device Tree 지원

현재에는 클럭 디바이스 드라이버도 머신 형태의 각 개별 드라이버를 사용하지 않고 점차 Device Tree를 지원하는 형태로 바뀌어 가고 있다. 즉 런타임에 Device Tree Blob 내용을 파싱하여 시스템에 맞게 클럭을 설정하는 형태이므로 모든 설정방법이 Device Tree Script에 표현되어 있어야 한다.

다음의 추가 구조체를 사용한다.

  • clock_provider
  • of_clk_provider

 

 

DEBUGFS 관리

Device Tree로 부팅한 rpi2에서 등록된 7개의 클럭을 다음의 디렉토리를 통해 살펴볼 수 있다.

# cd /sys/kernel/debug/clk
# ls -l
total 0
drwxr-xr-x 2 root root 0 Jan  1  1970 apb_pclk
-r--r--r-- 1 root root 0 Jan  1  1970 clk_dump
-r--r--r-- 1 root root 0 Jan  1  1970 clk_orphan_dump
-r--r--r-- 1 root root 0 Jan  1  1970 clk_orphan_summary
-r--r--r-- 1 root root 0 Jan  1  1970 clk_summary
drwxr-xr-x 2 root root 0 Jan  1  1970 clock
drwxr-xr-x 2 root root 0 Jan  1  1970 core
drwxr-xr-x 2 root root 0 Jan  1  1970 mmc
drwxr-xr-x 2 root root 0 Jan  1  1970 osc
drwxr-xr-x 2 root root 0 Jan  1  1970 pwm
drwxr-xr-x 2 root root 0 Jan  1  1970 uart0_pclk

# cat clk_summary
   clock                         enable_cnt  prepare_cnt        rate   accuracy   phase
----------------------------------------------------------------------------------------
 osc                                      0            0    19200000          0 0
 pwm                                      0            0   100000000          0 0
 apb_pclk                                 1            1   126000000          0 0
 uart0_pclk                               1            1    48000000          0 0
 mmc                                      0            0   250000000          0 0
 core                                     0            0   400000000          0 0
    clock                                 0            0   800000000          0 0

 

Device Tree로 Clk 초기화

of_clk_init()

drivers/clk/clk.c

/**     
 * of_clk_init() - Scan and init clock providers from the DT
 * @matches: array of compatible values and init functions for providers.
 *
 * This function scans the device tree for matching clock providers
 * and calls their initialization functions. It also does it by trying
 * to follow the dependencies.
 */
void __init of_clk_init(const struct of_device_id *matches)
{
        const struct of_device_id *match;
        struct device_node *np;
        struct clock_provider *clk_provider, *next;
        bool is_init_done;
        bool force = false;

        if (!matches)
                matches = &__clk_of_table;

        /* First prepare the list of the clocks providers */
        for_each_matching_node_and_match(np, matches, &match) {
                struct clock_provider *parent =
                        kzalloc(sizeof(struct clock_provider),  GFP_KERNEL);

                parent->clk_init_cb = match->data; 
                parent->np = np;
                list_add_tail(&parent->node, &clk_provider_list);
        }

        while (!list_empty(&clk_provider_list)) {
                is_init_done = false;
                list_for_each_entry_safe(clk_provider, next,
                                        &clk_provider_list, node) {
                        if (force || parent_ready(clk_provider->np)) {

                                clk_provider->clk_init_cb(clk_provider->np);
                                of_clk_set_defaults(clk_provider->np, true);

                                list_del(&clk_provider->node);
                                kfree(clk_provider);
                                is_init_done = true;
                        }
                }

                /*
                 * We didn't manage to initialize any of the
                 * remaining providers during the last loop, so now we
                 * initialize all the remaining ones unconditionally
                 * in case the clock parent was not mandatory
                 */
                if (!is_init_done)
                        force = true;
        }
}

Device Tree에서 clock provider를 스캔하고 초기화한다.

  • 코드 라인 17~18에서 matches 값으로 null이 지정된 경우 컴파일 타임에 CLK_OF_DECLARE() 매크로로 만들어진 __clk_of_table 섹션에 있는 모든 클럭 엔트리를 대상으로 한다.
  • 코드 라인 21~28에서 Device Tree에서 matches와 엔트리와 비교하여 일치하는 항목들에 대해 루프를 돌며 clock_provider를 할당받아 구성하고 clk_provider_list에 추가한다.
    • clk_init_cb에는 클럭 초기화 함수가 대입된다.
    • np에는 device_node가 대입된다.
  • 코드 라인 30~31에서 clk_provider_list의 엔트리가 비워질때 까지 루프를 돈다. is_init_done을 false로 하여 다음 루프에서 초기화함수를 한 번도 호출하지 않은 경우 다음에 루프를 돌면 강제로 호출하게 만든다.
  • 코드 라인 32~33에서 clk_provide_list의 엔트리 수 만큼 루프를 돈다.
  • 코드 라인 34~42에서 이전 루프에서 한 번도 초기화 함수를 호출하지 않은 경우와 부모 클럭이 없거나 모두 초기화된 경우 해당 클럭의 초기화 함수를 호출하고 이 클럭을 default로 설정한다. 그런 후 clk_provider_list에서 제거하고 루프내에서 한 번이라도 초기화되었음을 알리도록 is_int_done을 true로 설정한다.
  • 코드 라인 51~52에서 이 전 루프를 돌 때 한 번도 초기화된 적 없으면 force를 true로 하여 다시 한 번 루프를 돌 때 남은 나머지 클럭을 무조건 초기화 처리하도록 한다.

 

다음 그림에서와 같이 rpi2는 clock@0 ~ clock@6 까지 총 7개의 clock을 사용하며 이에 대한 각각의 초기화 함수가 호출되는 것을 보여준다.

  • clock@0~4, 6의 6개의 클럭은 “fixed-clock” 디바이스 드라이버에서 구현된 fixed rate clock 타입으로 of_fixed_clk_setup() 함수를 호출한다.
  • clock@5를 사용하는 1 개의 uart 클럭은 “fixed-factor-clock” 디바이스 드라이버에서 구현된 Fixed multiplier and divider clock 타입으로 of_fixed_factor_clk_setup() 함수를 호출한다.
    • 이 클럭의 부모 클럭인 core clock에서 사용하는 rate 값인 250Mhz를 2배 곱하여(multiplex) 500Mhz로 uart clock의 rate로 동작시킨다.

 

다음 그림은 A~I 까지의 클럭 디바이스들에 대해 부모 클럭 디바이스부터 초기화되는 과정을 보여준다.

 

parent_ready()

drivers/clk/clk.c

/*
 * This function looks for a parent clock. If there is one, then it
 * checks that the provider for this parent clock was initialized, in
 * this case the parent clock will be ready.
 */
static int parent_ready(struct device_node *np)
{
        int i = 0;

        while (true) {
                struct clk *clk = of_clk_get(np, i);

                /* this parent is ready we can check the next one */
                if (!IS_ERR(clk)) {
                        clk_put(clk);
                        i++;
                        continue;
                }

                /* at least one parent is not ready, we exit now */
                if (PTR_ERR(clk) == -EPROBE_DEFER)
                        return 0;

                /*
                 * Here we make assumption that the device tree is
                 * written correctly. So an error means that there is
                 * no more parent. As we didn't exit yet, then the
                 * previous parent are ready. If there is no clock
                 * parent, no need to wait for them, then we can
                 * consider their absence as being ready
                 */           
                return 1;
        }
}

요청한 클럭 노드의 부모 클럭 노드들 모두가 초기화 되었는지 여부를 알아온다. 1=부모 클럭 노드가 없거나 모두 초기화 된 경우, 0=부모 클럭 노드들 중 하나라도 초기화 되지 않았을 경우

  • 코드 라인 10~11에서 클럭의 부모 노드가 여러 개일 수 있으므로 루프를 반복하고 지정한 인덱스의 부모 클럭을 알아온다.
  • 코드 라인 14~18에서 지정된 인덱스의 부모 클럭이 이미 초기화된 경우 인덱스를 증가시키고 skip 한다.
  • 코드 라인 21~22에서 부모 클럭 노드가 아직 초기화되지 않은 경우 0을 반환한다.
  • 코드 라인 32에서 부모가 없는 경우 1을 반환한다.

 

clk 검색 -1-

clk_core_lookup()

drivers/clk/clk.c

static struct clk_core *clk_core_lookup(const char *name)
{
        struct clk_core *root_clk;
        struct clk_core *ret;

        if (!name)
                return NULL;    

        /* search the 'proper' clk tree first */
        hlist_for_each_entry(root_clk, &clk_root_list, child_node) {
                ret = __clk_lookup_subtree(name, root_clk);
                if (ret)
                        return ret;
        }

        /* if not found, then search the orphan tree */
        hlist_for_each_entry(root_clk, &clk_orphan_list, child_node) {
                ret = __clk_lookup_subtree(name, root_clk);
                if (ret)
                        return ret;
        }

        return NULL;
}

루트 클럭 리스트와 고아 클럭 리스트에 등록된 모든 하위 클럭들을 포함하여 요청한 이름의 클럭(clk_core)을 검색한다. 검색되지 않는 경우 null을 반환한다.

  • 코드 라인 10에서 clk_root_list에 등록된 루트 클럭들에 대해 루프를 돈다.
  • 코드 라인 11~13에서 루트 클럭을 포함해서 하위 트리에서 요청한 이름의 클럭(clk_core)을 찾아 반환한다.
  • 코드 라인 17~21에서 clk_orphan_list에 등록된 고아 클럭들에 대해 루프를 돌며 고아 클럭을 포함하여 하위 트리에서 요청한 이름의 클럭(clk_core)을 찾아 반환한다.

 

다음 그림은 “F”라는 이름의 클럭을 검색시 child 클럭을 검색하는 순서를 보여준다.

__clk_lookup_subtree()

drivers/clk/clk.c

static struct clk_core *__clk_lookup_subtree(const char *name,
                                             struct clk_core *clk)
{
        struct clk_core *child;
        struct clk_core *ret;

        if (!strcmp(clk->name, name))
                return clk;

        hlist_for_each_entry(child, &clk->children, child_node) {
                ret = __clk_lookup_subtree(name, child);
                if (ret)
                        return ret;
        }

        return NULL;
}

현재 클럭 및 그 자식 클럭에서 요청한 이름의 클럭(clk_core)을 검색한다. 검색되지 않는 경우 null을 반환한다.

  • 코드 라인 7~8에서 요청한 이름의 클럭인 경우 그 클럭을 반환한다.
  • 코드 라인 10에서 자식 클럭이 있는 경우 그 수 만큼 루프를 돈다.
  • 코드 라인 11에서 자식 클럭과 그 이하 서브 트리를 재귀 검색한다.
  • 코드 라인 12~13에서 클럭이 검색된 경우 반환한다.

 

clk 검색 -2- (부모 인덱스 사용)

 

static struct clk_core *clk_core_get_parent_by_index(struct clk_core *clk,
                                                         u8 index)
{
        if (!clk || index >= clk->num_parents)
                return NULL;
        else if (!clk->parents)
                return clk_core_lookup(clk->parent_names[index]);
        else if (!clk->parents[index])
                return clk->parents[index] =
                        clk_core_lookup(clk->parent_names[index]);
        else
                return clk->parents[index];
}

부모 인덱스 값으로 부모 클럭을 찾아 반환한다.

  • 코드 라인 4~5에서 인덱스값이 num_parents를 초과하는 경우 null을 반환한다.
  • 코드 라인 6~7에서 부모 클럭을 가리키는 포인터 어레이가 준비되지 않은 경우 그냥 부모 이름으로 검색하여 반환한다.
  • 코드 라인 8~10에서 부모 인덱스에 해당하는 부모 클럭이 설정되지 않은 경우 부모 이름으로 검색하여 설정하고 반환한다.
  • 코드 라인 11~12에서 부모 인덱스에 해당하는 부모 클럭을 반환한다.

 

Device Tree로 클럭 셋업

Fixed Rate 타입 Clk 디바이스 셋업

of_fixed_clk_setup()

/**
 * of_fixed_clk_setup() - Setup function for simple fixed rate clock
 */
void of_fixed_clk_setup(struct device_node *node)
{
        struct clk *clk;
        const char *clk_name = node->name;
        u32 rate;
        u32 accuracy = 0;

        if (of_property_read_u32(node, "clock-frequency", &rate))
                return;

        of_property_read_u32(node, "clock-accuracy", &accuracy);

        of_property_read_string(node, "clock-output-names", &clk_name);

        clk = clk_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,
                                                    CLK_IS_ROOT, rate,
                                                    accuracy);
        if (!IS_ERR(clk))
                of_clk_add_provider(node, of_clk_src_simple_get, clk);
}
EXPORT_SYMBOL_GPL(of_fixed_clk_setup);
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);

Device Tree의 요청 클럭 노드 정보로 fixed rate 타입의 클럭을 설정한다.

  • 코드 라인 11~12에서 요청한 클럭 노드에서 “clock-frequency” 속성 값을 읽어 rate에 대입한다.
  • 코드 라인 14에서 “clock-accuracy” 값을 읽어 accuracy에 대입한다. 속성이 없는 경우 accuracy=0 이다.
  • 코드 라인 16에서 “clock-output-names” 속성 값(문자열)을 읽어 clk_name에 대입한다. 속성이 없는 경우 노드명을 사용한다.
  • 코드 라인 18~20에서 읽어들인 rate, accuracy 및 clk_name으로 fixed rate 타입의 루트 클럭으로 등록한다.
  • 코드 라인 21~22에서 등록이 성공된 경우 클럭 provider에 추가한다.

 

다음 그림은 rpi2의 Device Tree Script (커널 v4.10 기준)에서 각 clock에 대한 연관도를 보여준다.

  • /soc/cprman은 플랫폼 클럭 장치로 7개의 클럭을 제공하고 4개의 클럭 입력을 받는다.
  • /soc/aux 디바이스는 3 개의 gate clock 장치이다. (0번은 aux_uart용으로 spi1 및 spi2 장치와 인터럽트가 공유된다.)
  • /soc/dsi1 디바이스는 GPU용 DSI 장치이다.

 

Fixed Factor 타입 Clk 디바이스 셋업

clk_register_fixed_factor_clk_setup()

drivers/clk/clk-fixed-factor.c

/**
 * of_fixed_factor_clk_setup() - Setup function for simple fixed factor clock
 */
void __init of_fixed_factor_clk_setup(struct device_node *node)
{
        struct clk *clk;
        const char *clk_name = node->name;
        const char *parent_name;
        u32 div, mult;

        if (of_property_read_u32(node, "clock-div", &div)) {
                pr_err("%s Fixed factor clock <%s> must have a clock-div property\n",
                        __func__, node->name);
                return;
        }

        if (of_property_read_u32(node, "clock-mult", &mult)) {
                pr_err("%s Fixed factor clock <%s> must have a clock-mult property\n",
                        __func__, node->name);
                return;
        }

        of_property_read_string(node, "clock-output-names", &clk_name);
        parent_name = of_clk_get_parent_name(node, 0);

        clk = clk_register_fixed_factor(NULL, clk_name, parent_name, 0,
                                        mult, div);
        if (!IS_ERR(clk))
                of_clk_add_provider(node, of_clk_src_simple_get, clk);
}
EXPORT_SYMBOL_GPL(of_fixed_factor_clk_setup);
CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock",
                of_fixed_factor_clk_setup);

Device Tree의 요청 클럭 노드 정보로 fixed factor 타입의 클럭을 설정한다.

  • 코드 라인 11~15에서 요청한 클럭 노드에서 “clock-div” 속성 값을 읽어 div에 대입한다.
  • 코드 라인 17~21에서 “clock-mult” 값을 읽어 multi에 대입한다.
  • 코드 라인 23에서 “clock-output-names” 속성 값(문자열)을 읽어 clk_name에 대입한다. 속성이 없는 경우 노드명을 사용한다.
  • 코드 라인 24에서 부모 클럭 이름을 알아와서 parent_name에 대입한다.
  • 코드 라인 26에서 읽어들인 div, multi, parent_name 및 clk_name으로 fixed factor 타입의 클럭으로 등록한다.
  • 코드 라인 28~29에서 등록이 성공된 경우 클럭 provider에 추가한다.

 

타입별 클럭 등록

Fixed Rate 타입 Clk 등록

clk_register_fixed_rate()

drivers/clk/clk-fixed-rate.c

/**
 * clk_register_fixed_rate - register fixed-rate clock with the clock framework
 * @dev: device that is registering this clock
 * @name: name of this clock
 * @parent_name: name of clock's parent
 * @flags: framework-specific flags
 * @fixed_rate: non-adjustable clock rate
 */
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned long fixed_rate)
{
        return clk_register_fixed_rate_with_accuracy(dev, name, parent_name,
                                                     flags, fixed_rate, 0);
}
EXPORT_SYMBOL_GPL(clk_register_fixed_rate);

클럭 디바이스, name, parent_name, flags, fixed_rate 정보를 인수로 accuracy가 0인 fixed rate 타입의 클럭을 등록한다.

 

clk_register_fixed_rate_with_accuracy()

drivers/clk/clk-fixed-rate.c

/**
 * clk_register_fixed_rate_with_accuracy - register fixed-rate clock with the
 *                                         clock framework
 * @dev: device that is registering this clock
 * @name: name of this clock
 * @parent_name: name of clock's parent
 * @flags: framework-specific flags
 * @fixed_rate: non-adjustable clock rate
 * @fixed_accuracy: non-adjustable clock rate
 */
struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                unsigned long fixed_rate, unsigned long fixed_accuracy)
{
        struct clk_fixed_rate *fixed;
        struct clk *clk;
        struct clk_init_data init;

        /* allocate fixed-rate clock */
        fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL);
        if (!fixed) {
                pr_err("%s: could not allocate fixed clk\n", __func__);
                return ERR_PTR(-ENOMEM);
        }

        init.name = name;
        init.ops = &clk_fixed_rate_ops;
        init.flags = flags | CLK_IS_BASIC;
        init.parent_names = (parent_name ? &parent_name: NULL);
        init.num_parents = (parent_name ? 1 : 0);

        /* struct clk_fixed_rate assignments */
        fixed->fixed_rate = fixed_rate;
        fixed->fixed_accuracy = fixed_accuracy;
        fixed->hw.init = &init;

        /* register the clock */
        clk = clk_register(dev, &fixed->hw);
        if (IS_ERR(clk))
                kfree(fixed);

        return clk;
}
EXPORT_SYMBOL_GPL(clk_register_fixed_rate_with_accuracy);

클럭 디바이스, name, parent_name, flags, fixed_rate, fixed_accuracy 정보를 인수로 받아 fixed rate 타입의 클럭을 등록한다.

  • 코드 라인 20~24에서 clk_fixed_rate 구조체를 할당받아온다.
  • 코드 라인 26~30에서 임시 구조체인 clk_init_data에 초기화 정보를 구성한다.
  • 코드 라인 33~35에서 clk_fixed_rate 구조체에서 fixed_rate, fixed_accuracy 및 임시 구조체 clk_init_data를 지정한다.
  • 코드 라인 38에서 클럭을 등록한다.

 

다음 그림은 fixed rate 타입의 클럭을 등록하는 모습을 보여준다.

 

Fixed Factor 타입 Clk 등록

clk_register_fixed_factor()

drivers/clk/clk-fixed-factor.c

struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned int mult, unsigned int div)
{
        struct clk_fixed_factor *fix;
        struct clk_init_data init;
        struct clk *clk;

        fix = kmalloc(sizeof(*fix), GFP_KERNEL);
        if (!fix) {
                pr_err("%s: could not allocate fixed factor clk\n", __func__);
                return ERR_PTR(-ENOMEM);
        }

        /* struct clk_fixed_factor assignments */
        fix->mult = mult;
        fix->div = div;
        fix->hw.init = &init;

        init.name = name;
        init.ops = &clk_fixed_factor_ops;
        init.flags = flags | CLK_IS_BASIC;
        init.parent_names = &parent_name;
        init.num_parents = 1;

        clk = clk_register(dev, &fix->hw);

        if (IS_ERR(clk))
                kfree(fix);

        return clk;
}
EXPORT_SYMBOL_GPL(clk_register_fixed_factor);

클럭 디바이스, name, parent_name, flags, multi, div 정보를 인수로 받아 fixed factor 타입의 클럭을 등록한다.

  • 코드 라인 9~18에서 clk_fixed_facotr 구조체를 할당받고 multi 값과 div 값 등을 구성한다.
  • 코드 라인 20~24에서 임시 구조체인 clk_init_data에 초기화 정보를 구성한다.
  • 코드 라인 26~29에서 클럭을 등록한다

 

Clk 등록

clk_register()

drivers/clk/clk.c

/**
 * clk_register - allocate a new clock, register it and return an opaque cookie
 * @dev: device that is registering this clock
 * @hw: link to hardware-specific clock data
 *
 * clk_register is the primary interface for populating the clock tree with new
 * clock nodes.  It returns a pointer to the newly allocated struct clk which
 * cannot be dereferenced by driver code but may be used in conjuction with the
 * rest of the clock API.  In the event of an error clk_register will return an
 * error code; drivers must test for an error code after calling clk_register.
 */
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
        int i, ret;
        struct clk_core *clk;

        clk = kzalloc(sizeof(*clk), GFP_KERNEL);
        if (!clk) {     
                pr_err("%s: could not allocate clk\n", __func__);
                ret = -ENOMEM;
                goto fail_out;
        }

        clk->name = kstrdup_const(hw->init->name, GFP_KERNEL);
        if (!clk->name) {
                pr_err("%s: could not allocate clk->name\n", __func__);
                ret = -ENOMEM;
                goto fail_name;
        }
        clk->ops = hw->init->ops;
        if (dev && dev->driver)
                clk->owner = dev->driver->owner;
        clk->hw = hw;
        clk->flags = hw->init->flags;
        clk->num_parents = hw->init->num_parents;
        hw->core = clk;

        /* allocate local copy in case parent_names is __initdata */
        clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),
                                        GFP_KERNEL);

        if (!clk->parent_names) {
                pr_err("%s: could not allocate clk->parent_names\n", __func__);
                ret = -ENOMEM;
                goto fail_parent_names;
        }


        /* copy each string name in case parent_names is __initdata */
        for (i = 0; i < clk->num_parents; i++) {
                clk->parent_names[i] = kstrdup_const(hw->init->parent_names[i],
                                                GFP_KERNEL);
                if (!clk->parent_names[i]) {
                        pr_err("%s: could not copy parent_names\n", __func__);
                        ret = -ENOMEM;
                        goto fail_parent_names_copy;
                }
        }

        INIT_HLIST_HEAD(&clk->clks);

        hw->clk = __clk_create_clk(hw, NULL, NULL);
        if (IS_ERR(hw->clk)) {
                pr_err("%s: could not allocate per-user clk\n", __func__);
                ret = PTR_ERR(hw->clk);
                goto fail_parent_names_copy;
        }

        ret = __clk_init(dev, hw->clk);
        if (!ret)
                return hw->clk;

        __clk_free_clk(hw->clk);
        hw->clk = NULL;

fail_parent_names_copy:
        while (--i >= 0)
                kfree_const(clk->parent_names[i]);
        kfree(clk->parent_names);
fail_parent_names:
        kfree_const(clk->name);
fail_name:
        kfree(clk);
fail_out:
        return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(clk_register);

요청한 디바이스와 clk_hw 정보로 clk_core와 clk를 구성하여 등록하고 최상위 노드인 경우 초기화한다.

  • 코드 라인 17~22에서 clk_core 구조체를 할당받아 core에 대입한다.
  • 코드 라인 24~29에서 core->name에 hw->init->name의 복사본 문자열을 대입한다.
    • kstrdup_const()
      • 문자열이 rodata(읽기 전용 커널 데이터) 섹션에 위치한 경우 복제하지 않고 const 타입으로 그냥 사용한다.
  • 코드 라인 30~36에서 clk_core 구조체를 구성한다.
  • 코드 라인 39~58에서 hw->init->parent_names[]를 core->parent_names[]에 복사한다.
  • 코드 라인 60에 core_clks 리스트를 초기화한다.
  • 코드 라인 62~67에서 clk를 할당받아 구성한다.
  • 코드 라인 69~71에서 클럭에 대해 부모 관계와 rate, accuracy 등을 설정한다.성공 시 함수를 빠져나간다.
  • 코드 라인 73~85까지 실패하는 경우 생성했던 모든 객체들을 free 시키고 에러를 반환한다.

 

다음 그림은 부모 노드를 가진 클럭을 등록하는 모습을 보여준다.

 

__clk_create_clk()

drivers/clk/clk.c

struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
                             const char *con_id)
{
        struct clk *clk;

        /* This is to allow this function to be chained to others */
        if (!hw || IS_ERR(hw))
                return (struct clk *) hw;

        clk = kzalloc(sizeof(*clk), GFP_KERNEL);
        if (!clk)
                return ERR_PTR(-ENOMEM);

        clk->core = hw->core;
        clk->dev_id = dev_id;
        clk->con_id = con_id;
        clk->max_rate = ULONG_MAX;

        clk_prepare_lock();
        hlist_add_head(&clk->child_node, &hw->core->clks);
        clk_prepare_unlock();

        return clk;
}

clk를 할당하고 구성한 후 hw->core->clks 리스트에 추가하고 clk를 반환한다.

  • 코드 라인 10~17에서 clk를 할당받은 후 구성한다.
  • 코드 라인 20에서 hw->core->clks 리스트에 할당받고 구성한 clk를 등록한다.

 

다음 그림은 clk_core의 child_node에 clk가 등록된 것을 보여준다.

 

__clk_init()

drivers/clk/clk.c

/**
 * __clk_init - initialize the data structures in a struct clk
 * @dev:        device initializing this clk, placeholder for now
 * @clk:        clk being initialized
 *
 * Initializes the lists in struct clk_core, queries the hardware for the
 * parent and rate and sets them both.
 */
static int __clk_init(struct device *dev, struct clk *clk_user)
{
        int i, ret = 0;
        struct clk_core *orphan;
        struct hlist_node *tmp2;
        struct clk_core *clk;
        unsigned long rate;

        if (!clk_user)
                return -EINVAL;

        clk = clk_user->core;

        clk_prepare_lock();

        /* check to see if a clock with this name is already registered */
        if (clk_core_lookup(clk->name)) {
                pr_debug("%s: clk %s already initialized\n",
                                __func__, clk->name);
                ret = -EEXIST;
                goto out;
        }

        /* check that clk_ops are sane.  See Documentation/clk.txt */
        if (clk->ops->set_rate &&
            !((clk->ops->round_rate || clk->ops->determine_rate) &&
              clk->ops->recalc_rate)) {
                pr_warning("%s: %s must implement .round_rate or .determine_rate in addition to .recalc_rate\n",
                                __func__, clk->name);
                ret = -EINVAL;
                goto out;
        }

        if (clk->ops->set_parent && !clk->ops->get_parent) {
                pr_warning("%s: %s must implement .get_parent & .set_parent\n",
                                __func__, clk->name);
                ret = -EINVAL;
                goto out;
        }

        if (clk->ops->set_rate_and_parent &&
                        !(clk->ops->set_parent && clk->ops->set_rate)) {
                pr_warn("%s: %s must implement .set_parent & .set_rate\n",
                                __func__, clk->name);
                ret = -EINVAL;
                goto out;
        }

        /* throw a WARN if any entries in parent_names are NULL */
        for (i = 0; i < clk->num_parents; i++)
                WARN(!clk->parent_names[i],
                                "%s: invalid NULL in %s's .parent_names\n",
                                __func__, clk->name);

클럭에 대해 부모 관계와 rate, accuracy 등을 설정한다.

  • 코드 라인 25~30에서 이름으로 클럭을 검색하여 이미 존재하는 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 33~40에서 set_rate와 recalc_rate를 위해 round_rate 또는 determine_rate가 등록이 되어 있어야하는데 만일 그렇지 않은 경우 경고 메시지를 출력하고 -EINVAL을 반환한다.
  • 코드 라인 42~47에서 set_parent를 위해 get_parent가 필요한데 없는 경우 경고 메시지를 출력하고 -EINVAL을 반환한다.
  • 코드 라인 49~55에서 num_parents 만큼 루프를 돌며 부모 클럭 명이 지정되지 않고 null로 되어 잇는 경우 경고 메시지를 출력한다.

 

        /*
         * Allocate an array of struct clk *'s to avoid unnecessary string
         * look-ups of clk's possible parents.  This can fail for clocks passed
         * in to clk_init during early boot; thus any access to clk->parents[]
         * must always check for a NULL pointer and try to populate it if
         * necessary.
         *
         * If clk->parents is not NULL we skip this entire block.  This allows
         * for clock drivers to statically initialize clk->parents.
         */
        if (clk->num_parents > 1 && !clk->parents) {
                clk->parents = kcalloc(clk->num_parents, sizeof(struct clk *),
                                        GFP_KERNEL);
                /*
                 * clk_core_lookup returns NULL for parents that have not been
                 * clk_init'd; thus any access to clk->parents[] must check
                 * for a NULL pointer.  We can always perform lazy lookups for
                 * missing parents later on.
                 */
                if (clk->parents)
                        for (i = 0; i < clk->num_parents; i++)
                                clk->parents[i] =
                                        clk_core_lookup(clk->parent_names[i]);
        }

        clk->parent = __clk_init_parent(clk);

        /*
         * Populate clk->parent if parent has already been __clk_init'd.  If
         * parent has not yet been __clk_init'd then place clk in the orphan
         * list.  If clk has set the CLK_IS_ROOT flag then place it in the root
         * clk list.
         *
         * Every time a new clk is clk_init'd then we walk the list of orphan
         * clocks and re-parent any that are children of the clock currently
         * being clk_init'd.
         */
        if (clk->parent)
                hlist_add_head(&clk->child_node,
                                &clk->parent->children);
        else if (clk->flags & CLK_IS_ROOT)
                hlist_add_head(&clk->child_node, &clk_root_list);
        else
                hlist_add_head(&clk->child_node, &clk_orphan_list);

        /*
         * Set clk's accuracy.  The preferred method is to use
         * .recalc_accuracy. For simple clocks and lazy developers the default
         * fallback is to use the parent's accuracy.  If a clock doesn't have a
         * parent (or is orphaned) then accuracy is set to zero (perfect
         * clock).
         */
        if (clk->ops->recalc_accuracy)
                clk->accuracy = clk->ops->recalc_accuracy(clk->hw,
                                        __clk_get_accuracy(clk->parent));
        else if (clk->parent)
                clk->accuracy = clk->parent->accuracy;
        else
                clk->accuracy = 0;
  • 코드 라인 11~24에서 부모 클럭이 2개 이상이면서 parents가 null인 경우 부모 클럭 수 만큼 포인터 배열을 할당받고 부모 클럭 이름으로 검색하여 구성한다.
  • 코드 라인 26에서 최초 연결될 부모 클럭을 대입한다.
  • 코드 라인 38~40에서 부모가 지정된 경우 부모 클럭의 children 리스트에 등록한다.
  • 코드 라인 41~42에서 ROOT 플래그가 설정된 경우 루트 클럭으로 인식하여 전역 clk_root_list에 등록한다.
  • 코드 라인 43~44에서 그외의 클럭은 고아 클럭으로 인식하여 전역 clk_orphan_list에 등록한다.
  • 코드 라인 53~55에서 ops->recalc_accuracy 콜백 함수가 지원되는 경우 부모의 정확도를 알아와서 이 콜백 함수의 결과를 가져와서 accuracy로 설정한다.
  • 코드 라인 56~59에서 부모가 있는 경우 부모의 정확도를 상속받아 설정하고 그렇지 않으면 0으로 설정한다.

 

        /*
         * Set clk's phase.
         * Since a phase is by definition relative to its parent, just
         * query the current clock phase, or just assume it's in phase.
         */
        if (clk->ops->get_phase)
                clk->phase = clk->ops->get_phase(clk->hw);
        else
                clk->phase = 0;

        /*
         * Set clk's rate.  The preferred method is to use .recalc_rate.  For
         * simple clocks and lazy developers the default fallback is to use the
         * parent's rate.  If a clock doesn't have a parent (or is orphaned)
         * then rate is set to zero.
         */
        if (clk->ops->recalc_rate)
                rate = clk->ops->recalc_rate(clk->hw,
                                clk_core_get_rate_nolock(clk->parent));
        else if (clk->parent)
                rate = clk->parent->rate;
        else
                rate = 0;
        clk->rate = clk->req_rate = rate;

        /*
         * walk the list of orphan clocks and reparent any that are children of
         * this clock
         */
        hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
                if (orphan->num_parents && orphan->ops->get_parent) {
                        i = orphan->ops->get_parent(orphan->hw);
                        if (!strcmp(clk->name, orphan->parent_names[i]))
                                clk_core_reparent(orphan, clk);
                        continue;
                }

                for (i = 0; i < orphan->num_parents; i++)
                        if (!strcmp(clk->name, orphan->parent_names[i])) {
                                clk_core_reparent(orphan, clk);
                                break;
                        }
         }

        /*
         * optional platform-specific magic
         *
         * The .init callback is not used by any of the basic clock types, but
         * exists for weird hardware that must perform initialization magic.
         * Please consider other ways of solving initialization problems before
         * using this callback, as its use is discouraged.
         */
        if (clk->ops->init)
                clk->ops->init(clk->hw);

        kref_init(&clk->ref);
out:
        clk_prepare_unlock();

        if (!ret)
                clk_debug_register(clk);

        return ret;
}
  • 코드 라인 6~9에서 ops->get_phase 콜백 함수가 지원되는 경우 호출하여 phase를 설정하고 그렇지 않은 경우 0으로 설정한다.
  • 코드 라인 17~19에서 ops->recalc_rate 콜백 함수가 지원되는 경우 부모의 클럭 rate로 이 콜백 함수를 호출하여 rate를 산출해온다.
  • 코드 라인 20~23에서 부모 클럭이 있는 경우 부모 클럭의 rate를 상속받아 설정하고 그렇지 않은 경우 0으로 설정한다.
  • 코드 라인 30에서 고아 클럭 리스트인 clk_orphan_list에 등록된 클럭들에 루프를 돈다.
  • 코드 라인 31~36에서 부모가 있는 경우 ops->get_parent 콜백 함수를 호출하여 부모 인덱스 값을 알아와서 부모를 다시 재설정하게 한다.
  • 코드 라인 38~42에서 부모 수 만큼 루프를 돌며 부모를 다시 재설정하게 한다.
  • 코드 라인 53~54에서 ops->init 콜백 함수가 지원되면 호출한다.

 

다음 그림은 clock 루트 리스트에 포함되는 클럭과 clock 고아 리스트에 포함되는 클럭의 속성 차이를 보여준다.

 

__clk_init_parent()

drivers/clk/clk.c

/*
 * .get_parent is mandatory for clocks with multiple possible parents.  It is
 * optional for single-parent clocks.  Always call .get_parent if it is
 * available and WARN if it is missing for multi-parent clocks.
 *
 * For single-parent clocks without .get_parent, first check to see if the
 * .parents array exists, and if so use it to avoid an expensive tree
 * traversal.  If .parents does not exist then walk the tree.
 */
static struct clk_core *__clk_init_parent(struct clk_core *clk)
{
        struct clk_core *ret = NULL;
        u8 index;

        /* handle the trivial cases */

        if (!clk->num_parents)
                goto out;

        if (clk->num_parents == 1) {
                if (IS_ERR_OR_NULL(clk->parent))
                        clk->parent = clk_core_lookup(clk->parent_names[0]);
                ret = clk->parent;
                goto out;
        }

        if (!clk->ops->get_parent) {
                WARN(!clk->ops->get_parent,
                        "%s: multi-parent clocks must implement .get_parent\n",
                        __func__);
                goto out;
        };

        /*
         * Do our best to cache parent clocks in clk->parents.  This prevents
         * unnecessary and expensive lookups.  We don't set clk->parent here;
         * that is done by the calling function.
         */

        index = clk->ops->get_parent(clk->hw);

        if (!clk->parents)
                clk->parents =
                        kcalloc(clk->num_parents, sizeof(struct clk *),
                                        GFP_KERNEL);

        ret = clk_core_get_parent_by_index(clk, index);

out:
        return ret;
}

현재 클럭에서 처음 설정할 부모 클럭을 알아온다.

  • 코드 라인 17~18에서 부모가 없는 경우 함수를 빠져나온다.
  • 코드 라인 20~25에서 부모가 하나 인 경우 이름으로 검색하여 반환한다.
  • 코드 라인 27~32에서 ops->get_parent 콜백 함수가 지원되지 않는 경우 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 40에서 ops->get_parent 콜백 함수를 호출하여 사용할 부모 클럭 인덱스를 알아온다.
  • 코드 라인 42~45에서 parents가 null인 경우 num_parents 수 만큼 포인터 배열을 할당한다.
  • 코드 라인 47에서 부모 인덱스로 부모 클럭을 알아와 반환한다.

 

클럭 Provider

provider에 Clk 추가

of_clk_add_provider()

drivers/clk/clk.c

/**
 * of_clk_add_provider() - Register a clock provider for a node
 * @np: Device node pointer associated with clock provider
 * @clk_src_get: callback for decoding clock
 * @data: context pointer for @clk_src_get callback.
 */
int of_clk_add_provider(struct device_node *np,
                        struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
                                                   void *data),
                        void *data)
{
        struct of_clk_provider *cp;
        int ret;

        cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);
        if (!cp)
                return -ENOMEM;

        cp->node = of_node_get(np);
        cp->data = data;
        cp->get = clk_src_get;

        mutex_lock(&of_clk_mutex);
        list_add(&cp->link, &of_clk_providers);
        mutex_unlock(&of_clk_mutex);
        pr_debug("Added clock from %s\n", np->full_name);

        ret = of_clk_set_defaults(np, true);
        if (ret < 0)
                of_clk_del_provider(np);

        return ret;
}
EXPORT_SYMBOL_GPL(of_clk_add_provider);

클럭을 provider 리스트에 추가한다.

  • 코드 라인 15~21에서 of_clk_provider 구조체를 할당받아 구성한다.
  • 코드 라인 23~25에서 of_clk_providers에 추가한다.
  • 코드 라인 28~30에서 default 부모 클럭과 rate를 설정한다. 만일 실패하는 경우 provider 리스트에서 제거한다.

 

Provider로부터 클럭 사용

of_clk_get()

drivers/clk/clkdev.c

struct clk *of_clk_get(struct device_node *np, int index)
{
        return __of_clk_get(np, index, np->full_name, NULL);
}
EXPORT_SYMBOL(of_clk_get);

지정한 디바이스 노드에서 index로 지정한 부모 클럭 노드를 알아온다.

  • np가 클럭 소스인 경우
    • “clocks=” 속성이 주어진 경우 인덱스가 지정하는 부모 클럭을 알아온다.
  • np가 클럭을 사용할 디바이스인 경우
    • “clocks=” 속성이 주어진 경우 인덱스가 지정하는 클럭을 알아온다. 없는 경우 에러

 

다음 그림은 부모 인덱스 값으로 부모 클럭을 알아오는 모습을 보여준다.

 

__of_clk_get()

drivers/clk/clkdev.c

static struct clk *__of_clk_get(struct device_node *np, int index,
                               const char *dev_id, const char *con_id)
{
        struct of_phandle_args clkspec;
        struct clk *clk;
        int rc;

        if (index < 0)
                return ERR_PTR(-EINVAL);

        rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
                                        &clkspec);
        if (rc)
                return ERR_PTR(rc);

        clk = __of_clk_get_by_clkspec(&clkspec, dev_id, con_id);
        of_node_put(clkspec.np);

        return clk;
}

요청한 디바이스 노드에서 index 값으로 부모 클럭을 알아온다.

  • 코드 라인 11~14에서 요청 노드의 “clocks” 속성 값에서 index번째 phandle이 가리키는 노드에서 “#clock-cells” 속성값을 읽어 clkspec에 저장한다. 만일 가져올 수 없는 경우 에러를 반환한다.
    • rpi2 예) clk_uart1이 가리키는 부모 클럭 clk_core의 #clock-cells 속성 값 0을 알아온다.
  • 코드 라인 16~17에서 clkspec으로 clk를 반환한다.

 

of_clk_get_by_clkspec()

drivers/clk/clkdev.c

/**
 * of_clk_get_by_clkspec() - Lookup a clock form a clock provider
 * @clkspec: pointer to a clock specifier data structure
 *
 * This function looks up a struct clk from the registered list of clock
 * providers, an input is a clock specifier data structure as returned
 * from the of_parse_phandle_with_args() function call.
 */
struct clk *of_clk_get_by_clkspec(struct of_phandle_args *clkspec)
{
        return __of_clk_get_by_clkspec(clkspec, NULL, __func__);
}

클럭 providers 리스트에서 of_phandle_args 값으로 클럭을 검색하여 반환한다.

 

__of_clk_get_by_clkspec()

drivers/clk/clkdev.c

static struct clk *__of_clk_get_by_clkspec(struct of_phandle_args *clkspec,
                                         const char *dev_id, const char *con_id)
{
        struct clk *clk;

        if (!clkspec)
                return ERR_PTR(-EINVAL);

        of_clk_lock();
        clk = __of_clk_get_from_provider(clkspec, dev_id, con_id);
        of_clk_unlock();
        return clk;
}

클럭 providers 리스트에서 of_phandle_args 값으로 클럭을 검색하여 반환한다.

 

of_clk_get_from_provider()

drivers/clk/clk.c

struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
        struct clk *clk;

        mutex_lock(&of_clk_mutex);
        clk = __of_clk_get_from_provider(clkspec, NULL, __func__);
        mutex_unlock(&of_clk_mutex);

        return clk;
}

 

클럭 providers 리스트에서 of_phandle_args 값으로 클럭을 검색하여 반환한다.

 

다음 그림은 uart 디바이스에 사용하는 클럭을 알아오는 모습을 보여준다.

 

__of_clk_get_from_provider()

drivers/clk/clk.c

struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
                                       const char *dev_id, const char *con_id)
{
        struct of_clk_provider *provider;
        struct clk *clk = ERR_PTR(-EPROBE_DEFER);

        /* Check if we have such a provider in our array */
        list_for_each_entry(provider, &of_clk_providers, link) {
                if (provider->node == clkspec->np) 
                        clk = provider->get(clkspec, provider->data);
                if (!IS_ERR(clk)) {
                        clk = __clk_create_clk(__clk_get_hw(clk), dev_id,
                                               con_id);
 
                        if (!IS_ERR(clk) && !__clk_get(clk)) {
                                __clk_free_clk(clk);
                                clk = ERR_PTR(-ENOENT);
                        }

                        break;
                }
        }

        return clk;       
}

of_clk_providers 리스트에서 루프를 돌며 요청 device_node를 찾은 후 등록된 get 후크 함수를 호출하여 clk을 찾게되면 clk를 새로 할당받아 구성한 후 반환한다.

  • 코드 라인 8~10에서 of_clk_providers 리스트를 루프를 돌며 등록된 of_clk_provider 엔트리와 요청한 노드가 동일한 경우
  • 코드 라인 11~21에서 clk를 새로 할당하고 알아온 clk->core->hw 정보와 인수로 받은 dev_id, con_id 등으로 구성하여 반환한다.

 

 

clk 관련 기타 of_api 들

of_clk_set_defaults()

drivers/clk/clk-conf.c

/**
 * of_clk_set_defaults() - parse and set assigned clocks configuration
 * @node: device node to apply clock settings for
 * @clk_supplier: true if clocks supplied by @node should also be considered
 *
 * This function parses 'assigned-{clocks/clock-parents/clock-rates}' properties
 * and sets any specified clock parents and rates. The @clk_supplier argument
 * should be set to true if @node may be also a clock supplier of any clock
 * listed in its 'assigned-clocks' or 'assigned-clock-parents' properties.
 * If @clk_supplier is false the function exits returnning 0 as soon as it
 * determines the @node is also a supplier of any of the clocks.
 */
int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
{
        int rc;

        if (!node)
                return 0;

        rc = __set_clk_parents(node, clk_supplier);
        if (rc < 0)
                return rc;

        return __set_clk_rates(node, clk_supplier);
}
EXPORT_SYMBOL_GPL(of_clk_set_defaults);

요청한 클럭 디바이스의 부모 클럭을 설정하고 rate를 설정한다.

 

__set_clk_parents()

drivers/clk/clk-conf.c

static int __set_clk_parents(struct device_node *node, bool clk_supplier)
{
        struct of_phandle_args clkspec;
        int index, rc, num_parents;
        struct clk *clk, *pclk;

        num_parents = of_count_phandle_with_args(node, "assigned-clock-parents",
                                                 "#clock-cells");
        if (num_parents == -EINVAL)
                pr_err("clk: invalid value of clock-parents property at %s\n",
                       node->full_name);

        for (index = 0; index < num_parents; index++) {
                rc = of_parse_phandle_with_args(node, "assigned-clock-parents",
                                        "#clock-cells", index, &clkspec);
                if (rc < 0) {
                        /* skip empty (null) phandles */
                        if (rc == -ENOENT)
                                continue;
                        else
                                return rc;
                }
                if (clkspec.np == node && !clk_supplier)
                        return 0;
                pclk = of_clk_get_by_clkspec(&clkspec);
                if (IS_ERR(pclk)) {
                        pr_warn("clk: couldn't get parent clock %d for %s\n",
                                index, node->full_name);
                        return PTR_ERR(pclk);
                }

                rc = of_parse_phandle_with_args(node, "assigned-clocks",
                                        "#clock-cells", index, &clkspec);
                if (rc < 0)
                        goto err;
                if (clkspec.np == node && !clk_supplier) {
                        rc = 0;
                        goto err;
                }
                clk = of_clk_get_by_clkspec(&clkspec);
                if (IS_ERR(clk)) {
                        pr_warn("clk: couldn't get parent clock %d for %s\n",
                                index, node->full_name);
                        rc = PTR_ERR(clk);
                        goto err;
                }

                rc = clk_set_parent(clk, pclk);
                if (rc < 0)
                        pr_err("clk: failed to reparent %s to %s: %d\n",
                               __clk_get_name(clk), __clk_get_name(pclk), rc);
                clk_put(clk);
                clk_put(pclk);
        }
        return 0;
err:
        clk_put(pclk);
        return rc;
}

요청한 클럭 디바이스의 부모 클럭을 설정한다.

  • 코드 라인 7~11에서 “#clock-cells” 속성값을 사용하여 “assigned-clock-parents” 속성에서 phandle의 수를 알아온다.  읽어올 수 없는 경우 에러 메시지를 출력한다.
    • 예) #clock-cells = <1>; assigned-clocks = <&cru PLL_GPLL>, <&cru PLL_CPLL>; -> 2 개
  • 코드 라인 13~15에서 부모 수만큼 루프를 돌며 “assigned-clock-parents” 속성에서 요청한 index의 phandle 값을 알아온다.
  • 코드 라인 16~22에서 알아온 phandle 값이 null이면 skip하고 에러인 경우 함수를 빠져나간다.
  • 코드 라인 23~24에서 clk_supplier 값이 0 이면서 알아온 부모 노드가 요청 노드와 동일한 경우 성공(0)리에 함수를 빠져나간다.
  • 코드 라인 25~30에서 clkspec으로 클럭을 알아온다.
  • 코드 라인 32~35에서 “assigned-clocks” 속성에서 부모 index로 clkspec 값을 알아온다.
  • 코드 라인 36~39에서 clk_supplier 값이 0 이면서 알아온 부모 노드가 요청 노드와 동일한 경우 성공(0)리에 함수를 빠져나간다.
  • 코드 라인 40~46에서 clkspec 값으로 클럭을 알아온다. 검색이 실패하는 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 48~51에서 부모 클럭으로 pclk를 지정한다.

 

__set_clk_rates()

drivers/clk/clk-conf.c

static int __set_clk_rates(struct device_node *node, bool clk_supplier)
{
        struct of_phandle_args clkspec;
        struct property *prop;
        const __be32 *cur;
        int rc, index = 0;
        struct clk *clk;
        u32 rate;

        of_property_for_each_u32(node, "assigned-clock-rates", prop, cur, rate) {
                if (rate) {
                        rc = of_parse_phandle_with_args(node, "assigned-clocks",
                                        "#clock-cells", index, &clkspec);
                        if (rc < 0) {
                                /* skip empty (null) phandles */
                                if (rc == -ENOENT)
                                        continue;
                                else
                                        return rc;
                        }
                        if (clkspec.np == node && !clk_supplier)
                                return 0;

                        clk = of_clk_get_by_clkspec(&clkspec);
                        if (IS_ERR(clk)) {
                                pr_warn("clk: couldn't get clock %d for %s\n",
                                        index, node->full_name);
                                return PTR_ERR(clk);
                        }

                        rc = clk_set_rate(clk, rate);
                        if (rc < 0)
                                pr_err("clk: couldn't set %s clock rate: %d\n",
                                       __clk_get_name(clk), rc);
                        clk_put(clk);
                }
                index++;
        }
        return 0;
}

요청한 클럭 디바이스의 rate를 설정한다.

  • 코드 라인 10에서 요청한 노드 이하에서 “assigned-clock-rates” 속성 값들을 대상으로 루프를 돈다.
  • 코드 라인 11~13에서 rate 값이 0보다 큰 경우 “assigned-clocks” 속성에서 읽은 index 번호의 부모 클럭 노드의 “#clock-cells” 값 길이 만큼의 phandle 뒤의 argument를 읽어들여 clkspec에 대입한다.
  • 코드 라인 14~20에서 알아온 phandle 값이 null이면 skip하고 에러인 경우 함수를 빠져나간다.
  • 코드 라인 21~22에서 clk_supplier 값이 0 이면서 알아온 부모 노드가 요청 노드와 동일한 경우 성공(0)리에 함수를 빠져나간다.
  • 코드 라인 24~29에서 clkspec 값으로 클럭을 알아온다. 검색이 실패하는 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 31~34에서 클럭의 rate를 설정한다.

 

다음 스크립트를 보면 pwm 노드에서 사용할 클럭 소스로 clocks BCM2835_CLOCK_PWN을 지정하였고 이 클럭을 10Mhz로 설정하는 것을 알 수 있다.

arch/arm/boot/dts/bcm283x.dtsi – raspberrypi 커널 v4.9.y

                clocks: cprman@7e101000 {
                        compatible = "brcm,bcm2835-cprman";
                        #clock-cells = <1>;
                        reg = <0x7e101000 0x2000>;
                        clocks = <&clk_osc>,
                                <&dsi0 0>, <&dsi0 1>, <&dsi0 2>,
                                <&dsi1 0>, <&dsi1 1>, <&dsi1 2>;
                };

                pwm: pwm@7e20c000 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c000 0x28>;
                        clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clock-rates = <10000000>;
                        #pwm-cells = <2>;
                        status = "disabled";
                };

 

 

참고

 

init_timers()

Lowres 타이머 초기화 – jiffies 기반

init_timers()

kernel/time/timer.c

void __init init_timers(void)
{
        int err;

        /* ensure there are enough low bits for flags in timer->base pointer */
        BUILD_BUG_ON(__alignof__(struct tvec_base) & TIMER_FLAG_MASK);

        err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
                               (void *)(long)smp_processor_id());
        BUG_ON(err != NOTIFY_OK);

        init_timer_stats();
        register_cpu_notifier(&timers_nb);
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

현재 cpu에 대한 타이머 벡터를 초기화하고 타이머 softirq를 준비한다. 또한 다른 cpu들 상태(up, dead, …)가 바뀌는 경우 타이머 설정(init or migrate) 등이 호출되도록 cpu notify chain에 해당 동작 함수를 추가한다.

  • 코드 라인 8~9에서 현재 cpu에 대한 타이머 벡터를 초기화한다.
    • 5개의 타이머 벡터 리스트 등의 초기화
  • 코드 라인 12에서 타이머 통계를 위해 관련 lock을 초기화한다.
  • 코드 라인 13에서 cpu 상태 변화에 따라 notify 되도록 cpu notify chain에 timers_nb를 추가한다.
    • 호출되는 함수: timer_cpu_notify()
  • 코드 라인 14에서 타이머 softirq를 준비한다.
    • 호출되는 함수: run_timer_softirq()

 

cpu notify chain에 추가할 타이머용 notifier block

kernel/time/timer.c

static struct notifier_block timers_nb = {
        .notifier_call  = timer_cpu_notify,
};

 

timer_cpu_notify()

kernel/time/timer.c

static int timer_cpu_notify(struct notifier_block *self,
                                unsigned long action, void *hcpu)
{
        long cpu = (long)hcpu;
        int err;

        switch(action) {
        case CPU_UP_PREPARE:
        case CPU_UP_PREPARE_FROZEN:
                err = init_timers_cpu(cpu);
                if (err < 0)
                        return notifier_from_errno(err);
                break;
#ifdef CONFIG_HOTPLUG_CPU
        case CPU_DEAD:
        case CPU_DEAD_FROZEN:
                migrate_timers(cpu);
                break;
#endif
        default:
                break;
        }
        return NOTIFY_OK;
}

cpu 상태가 변화될 때 타이머를 초기화하거나 migration 한다.

 

init_timers_cpu()

kernel/time/timer.c

static int init_timers_cpu(int cpu)
{
        int j;
        struct tvec_base *base;
        static char tvec_base_done[NR_CPUS];

        if (!tvec_base_done[cpu]) {
                static char boot_done;

                if (boot_done) {
                        /*
                         * The APs use this path later in boot
                         */
                        base = kzalloc_node(sizeof(*base), GFP_KERNEL,
                                            cpu_to_node(cpu));
                        if (!base)
                                return -ENOMEM;

                        /* Make sure tvec_base has TIMER_FLAG_MASK bits free */
                        if (WARN_ON(base != tbase_get_base(base))) {
                                kfree(base);
                                return -ENOMEM;
                        }
                        per_cpu(tvec_bases, cpu) = base;
                } else {
                        /*
                         * This is for the boot CPU - we use compile-time
                         * static initialisation because per-cpu memory isn't
                         * ready yet and because the memory allocators are not
                         * initialised either.
                         */
                        boot_done = 1;
                        base = &boot_tvec_bases;
                }
                spin_lock_init(&base->lock);
                tvec_base_done[cpu] = 1;
                base->cpu = cpu;
        } else {
                base = per_cpu(tvec_bases, cpu);
        }


        for (j = 0; j < TVN_SIZE; j++) {
                INIT_LIST_HEAD(base->tv5.vec + j);
                INIT_LIST_HEAD(base->tv4.vec + j);
                INIT_LIST_HEAD(base->tv3.vec + j);
                INIT_LIST_HEAD(base->tv2.vec + j);
        }
        for (j = 0; j < TVR_SIZE; j++)
                INIT_LIST_HEAD(base->tv1.vec + j);

        base->timer_jiffies = jiffies;
        base->next_timer = base->timer_jiffies;
        base->active_timers = 0;
        base->all_timers = 0;
        return 0;
}

요청 cpu에 대한 타이머 벡터를 초기화한다.

  • 코드 라인 5에서 static 배열로 cpu별 tvec_base의 초기화 여부를 구분한다.
    • 1=초기화 완료, 0=미초기화
  • 코드 라인 7에서 요청 cpu에 대한 tvec_base가 아직 초기화되지 않은 경우
  • 코드 라인 8~24 부트 타임이 아닌 경우 tvec_base를 동적 할당받아 per-cpu tvec_bases의 현재 cpu에 설정한다.
  • 코드 라인 25~34에서 부트 타임의 경우 tvec_base는 컴파일 타임에 빌드된 boot_tvec_bases를 사용한다.
    • 추후 커널에서 boot_tvec_bases가 없어지면서 부트 타임과 이원화된 할당 방식을 사용하지 않고 항상 동적할당한다.
  • 코드 라인 35~37에서 tvec_base가 초기화되었음을 알린다.
  • 코드 라인 38~40에서 이미 tvec_base가 설정된 경우요청 cpu에 대한 tvec_base를 구한다.
  • 코드 라인 43~48에서 tv2 ~ tv5까지의 tvec 리스트 배열을 초기화한다.
  • 코드 라인 49~50에서 tv1의 tvec 리스트 배열을 초기화한다.
    • tv1의 리스트 배열 크기는 tv2 ~ tv5 리스트 배열 크기와 같다.
  • 코드 라인 52~55에서 tvec_base의 나머지 멤버들도 초기화한다.

 

init_timer_stats()

kernel/time/timer_stats.c

void __init init_timer_stats(void)
{
        int cpu;

        for_each_possible_cpu(cpu)
                raw_spin_lock_init(&per_cpu(tstats_lookup_lock, cpu));
}

타이머 통계용으로 사용되는 tstats_lookup_lock을 초기화한다.

 

참고