Common Clock Framework -1- (초기화)

<kernel v5.4>

Common Clock Framework -1- (초기화)

CCF History

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

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

  • ccf가 소개된 이후 개발된 ARM64 아키텍처에서는 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 {

 

다음과 같이 커널 v5.4에서 clk 구조체를 변경하여 사용한 코드가 일부 남아 있다.

./include/linux/sh_clk.h:38:struct clk {
./drivers/clk/clk.c:97:struct clk {
./arch/unicore32/kernel/clock.c:27:struct clk {
./arch/mips/include/asm/mach-ar7/ar7.h:134:struct clk {
./arch/mips/include/asm/clock.h:21:struct clk {
./arch/mips/ralink/clk.c:18:struct clk {
./arch/mips/bcm63xx/clk.c:21:struct clk {
./arch/mips/lantiq/clk.h:58:struct clk {
./arch/m68k/include/asm/mcfclk.h:17:struct clk {
./arch/arm/mach-ep93xx/clock.c:27:struct clk {
./arch/arm/mach-omap1/clock.h:138:struct clk {
./arch/arm/mach-mmp/clock.h:12:struct clk {
./arch/c6x/include/asm/clock.h:79:struct clk {

 

클럭 Diagram

다음 그림은 클럭 다이어그램 샘플을 보여준다.

  • Device A는 onboard 클럭을 10으로 분주(divide)한 클럭 rate를 공급받는다.
  • Devce B~E가 external 클럭을 공급받는 경우 PLL 설정이 필요하다.

 

클럭 Provider & Consumer

클럭을 공급하는 장치인 Clock Provider와 클럭을 가져다 사용하는 Clock Consumer의 특징을 알아본다.

클럭 Provider
  • CCF 기반의 클럭 디바이스 드라이버를 통해 구현된다.
  • common CCF를 사용하면 유사한 기능을 갖는 클럭 디바이스를 간단히 개발할 수 있다.
  • 클럭 rate, 분주 값 및 Multiplexer의 선택은 디바이스 트리를 통해 각각의 보드 상황에 맞게 설정할 수 있다.

 

클럭 Consumer
  • 디바이스 트리를 통해 사용할 클럭을 지정하여 사용할 수 있다.
  • 런타임에 수동으로 멀티플렉서나 Gate의 제어가 필요한 경우 클럭 API들을 통해서 제어할 수 있다.

 

다음 그림은 기본적인 Clock Provider와 Clock Consumer의 관계와 디바이스 트리에서의 표현을 보여준다.

  • OSC가 Clock Provider로 사용되었고, PLL이 Clock Consumer이자 Clock Provider로 사용된다.
  • UART는 Clock Consumer로 사용된다.

 

다음 그림은 Clock Provider와 Clock Consumer들을 보여준다.

  • PLL의 경우 별도의 custom 드라이버를 통해 구현되는데, 여러 가지 CCF 타입이 혼재되어 구현된다.
  • 노란색 클럭들은 상위 Clock에 대한 Clock Consumer이면서 하위 디바이스에 대한 Clock Provider를 수행한다.

 

Assigned-clock

몇가지 플랫폼에서는 다음과 같이 clock provider의 rate 설정 또는 mux(parent) 입력 소스 선택을 지원하는 속성을 제공한다.

  • assigned-clocks =
  • assigned-clock-parents =
  • assigned-clock-rates =

 

다음 그림은 clkcon 0의 입력 소스로 pll 2를 선택하게하고, pll 2의 rate를 460800hz로 지정하도록 하는 모습을 보여준다.

 


CCF 구조

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

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

  • struct clk_core
    • 클럭 제공자(provider)로 고정 클럭을 포함하여 Rate, Mux, Gate 타입 등 여러 종류의 클럭 클럭을 포함한다.
  • struct clk
    • 클럭 사용자(consumer)로 사용할 clk_core에 연결된다.
    • 예외 사항으로 클럭 consumer가 아니지만 clk_core 생성 시에 따라 생성되는 clk도 있다. 이들의 용도는 clk_core가 삭제되지 않도록 참조한다.
    • 처음 소개된 CCF에서는 부모 관계 설정도 하였지만, 이러한 관계 설정은 clk_core로 이전되었다.
  • struct clk_hw
    • 클럭 hw는 클럭 core와 클럭을 연결한다.
  • struct clk_ops
    • 클럭 코어가 동작하는 여러 가지 후크를 가진다.
  • struct clk_init_data
    • 클럭 코어를 초기화할 때 사용하는 데이터가 담긴다.
  • struct clk_parent_data

 

다음 그림에서 provider로 사용된 clk 구조체와 consumer로 사용된 clk 구조체를 비교해본다.

 

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

  • 파란색 글씨는 커널 v4.0으로부터 추가된 항목들이다.

 

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

 


구현된 기본 공통 클럭 타입

Basic clock implementations common

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

 

Rate 클럭들

Fixed rate 및 Fixed factor 타입 클럭을 제외하고 각자 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.

  • Fixed rate clock 구현
    • 고정된 rate와 정밀도(accuracy)로 동작하고 gate는 제어할 수 없다.
    • compatible
      • rate를 조절하는 컨트롤 기능이 없는 경우 이 common 드라이버를 사용한다.
      • fixed-clock
  • Fixed Factor(multiplier and divider) clock 구현
    • 배율기 및 분배기로 동작하여 rate를 설정하고 gate는 제어할 수 없다.
    • rate  = 고정된 부모 clock의 rate / 분배값(div) * 배율값(mult)
    • compatible
      • 배율을 조절하는 컨트롤 기능이 없는 경우 이 common 드라이버를 사용한다.
      • fixed-factor-clock
  • divider clock 구현
    • 분배기로 rate가 설정되고 gate는 제어할 수 없다.
    • rate = 고정된 부모 clock의 rate / 분배값(div)
    • compatible
      • 실제 분배율을 hw로 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터 주소를 등록하여 사용하도록 각사의 디바이스 드라이버를 사용한다.
      • 예) “ti,divider-clock”
  • Fractional divider clock 구현
    • 분수 분배기로 rate를 설정하고 gate는 제어할 수 없다.
    • rate = 고정된 부모 clock의 rate * 분수(fractional)
    • compatible
      • 실제 분수 분배율을 hw로 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
  • Multiplier clock 구현
    • 배율기로 동작한다.
    • rate = 고정된 부모 clock의 rate * mult
    • compatible
      • 동작에 따른 조작을 hw로 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      •  예) “allwinner,sun4i-a10-pll3-clk”
    • 참고: clk: Add a basic multiplier clock (2015, v4.4-rc1)

 

Mux 클럭들

각자 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.

  • Simple multiplexer clock 구현
    • 간단한 멀티플렉서로 동작하여 dynamic하게 부모 클럭을 선택할 수 있고 gate는 제어할 수 없다.
    • compatible
      • 실제 입력 소스를 hw로 선택해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      • 예) “ti,mux-clock”

 

Gate 클럭들

Gpio gated 타입 클럭은 common gpio 디바이스 드라이버를 통해 hw를 제외하므로 별도의 클럭 디바이스 드라이버의 구현이 필요 없고, 그 외의 gated 타입 클럭은 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.

  • Gpio gated clock 구현
    • gpio를 사용하여 gate를 제어한다.
    • rate는 직접 지정할 수 없고 고정(fixed)된 부모로부터 rate는 상속받아야 한다.
    •  compatible
      • gate의 hw 조작을 common gpio 드라이버를 통해서 동작하므로 별도의 custome 디바이스 드라이버 제작없이 이 타입의 디바이스 드라이버를 사용한다.
      • gpio-gate-clock
  • Gated clock 구현
    • gate를 제어 할 수 있다.
    • rate는 직접 지정할 수 없고 고정(fixed)된 부모 clock으로부터 rate는 상속받아야 한다.
    • compatible
      • 실제 gate를 hw 제어해야하기 때문에 각 클럭 컨틀롤러 제조사의 레지스터를 컨트롤하는 각자의 디바이스 드라이버 필요하다.
      • 예) ti,gate-clock”

 

Composite 클럭들
  • Composite clock 구현
    • rate, mux(multiplexer) 및 gate 클럭들의 기능을 동시에 복합 구성하여 동작한다.
    • compatible
      • 복합 동작에 따른 조작을 hw 제어해야 하기 때문에 각 클럭 컨트롤러 제조사의 레지스터를 컨트롤하는 각사의 디바이스 드라이버를 사용한다.
      • 예)  “ti,composite-clock”

 

클럭 하드웨어 도면은 아래 사이트를 참고한다.

 

다음 그림은 9개의 클럭 디바이스 사용 예를 각각 보여준다.

 

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

  • 아래 하늘색 박스와 같이 gpio gate 클럭 드라이버는 common gpio 드라이버를 사용하여 제어한다.
  • 9개의 common 클럭 구현이 준비되어 있다.
    • 3개의 공통 클럭 디바이스 드라이버는 device tree로 설정 가능하며 완성된 형태로 사용된다.
    • 나머지 6개는 custom 클럭 디바이스 드라이버를 통해 쉽게 구현이 가능하다. (ti, samsung, etc…)

 

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

 

Device Tree 지원

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

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

  • clock_provider
  • of_clk_provider

 

심플한 구현

common 클럭들을 사용하는 경우 hw 조작을 위해 관련 레지스터 주소와 함께 클럭을 등록하여 간단하게 디바이스 트리용 클럭 드라이버를 만들 수 있다. 다음 예는 가장 간단히 만들 수 있는 실제 divider 클럭 드라이버 코드를 보여준다.

drivers/clk/h8300/clk-div.c

#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>

static DEFINE_SPINLOCK(clklock);

static void __init h8300_div_clk_setup(struct device_node *node)
{
        unsigned int num_parents;
        struct clk_hw *hw;
        const char *clk_name = node->name;
        const char *parent_name;
        void __iomem *divcr = NULL;
        int width;
        int offset;

        num_parents = of_clk_get_parent_count(node);
        if (!num_parents) {
                pr_err("%s: no parent found\n", clk_name);
                return;
        }

        divcr = of_iomap(node, 0);
        if (divcr == NULL) {
                pr_err("%s: failed to map divide register\n", clk_name);
                goto error;
        }
        offset = (unsigned long)divcr & 3;
        offset = (3 - offset) * 8;
        divcr = (void __iomem *)((unsigned long)divcr & ~3);

        parent_name = of_clk_get_parent_name(node, 0);
        of_property_read_u32(node, "renesas,width", &width);
        hw = clk_hw_register_divider(NULL, clk_name, parent_name,
                                   CLK_SET_RATE_GATE, divcr, offset, width,
                                   CLK_DIVIDER_POWER_OF_TWO, &clklock);
        if (!IS_ERR(hw)) {
                of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
                return;
        }
        pr_err("%s: failed to register %s div clock (%ld)\n",
               __func__, clk_name, PTR_ERR(hw));
error:
        if (divcr)
                iounmap(divcr);
}

CLK_OF_DECLARE(h8300_div_clk, "renesas,h8300-div-clock", h8300_div_clk_setup);

2의 배수 단계로 나누는 divider 클럭이다.

  • n 비트 셀렉터 값
    • (레지스터(reg) 값 >> offset) & width
  • width=2인 경우 2 bit 셀렉터를 사용하여 4단계(1/1, 1/2, 1/4, 1/8)로 클럭 rate를 나눌 수 있다.

 

다음은 위 클럭 디바이스 드라이버를 사용하는 4단계 divier 클럭 디바이스를 정의한 디바이스 트리 설정이다. (core_clk 부분)

arch/h8300/boot/dts/h8300h_sim.dts

...
        xclk: oscillator {
                #clock-cells = <0>;
                compatible = "fixed-clock";
                clock-frequency = <20000000>;
                clock-output-names = "xtal";
        };
        core_clk: core_clk {
                compatible = "renesas,h8300-div-clock";
                clocks = <&xclk>;
                #clock-cells = <0>;
                reg = <0xfee01b 2>;
                renesas,width = <2>;
        };
        fclk: fclk {
                compatible = "fixed-factor-clock";
                clocks = <&core_clk>;
                #clock-cells = <0>;
                clock-div = <1>;
                clock-mult = <1>;
        };
...

20Mhz 고정 클럭 -> 4 단계 divider -> 1/1 Factor 클럭 순으로 클럭이 공급됨을 알 수 있다.

 


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

 

다음은 rpi4(Ubuntu 4.18.03)에서 사용된 클럭들을 보여준다. (커널 v4.19)

$ cd /sys/kernel/debug/clk
rpi4 /sys/kernel/debug/clk$ ls -l
total 0
drwxr-xr-x 2 root root 0 Jan  1  1970 aux_spi1
drwxr-xr-x 2 root root 0 Jan  1  1970 aux_spi2
drwxr-xr-x 2 root root 0 Jan  1  1970 aux_uart
drwxr-xr-x 2 root root 0 Jan  1  1970 aveo
drwxr-xr-x 2 root root 0 Jan  1  1970 cam0
drwxr-xr-x 2 root root 0 Jan  1  1970 cam1
-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 dft
drwxr-xr-x 2 root root 0 Jan  1  1970 dpi
drwxr-xr-x 2 root root 0 Jan  1  1970 dsi0e
drwxr-xr-x 2 root root 0 Jan  1  1970 dsi0p
drwxr-xr-x 2 root root 0 Jan  1  1970 dsi1e
drwxr-xr-x 2 root root 0 Jan  1  1970 dsi1p
drwxr-xr-x 2 root root 0 Jan  1  1970 emmc
drwxr-xr-x 2 root root 0 Jan  1  1970 emmc2
drwxr-xr-x 2 root root 0 Jan  1  1970 gp0
drwxr-xr-x 2 root root 0 Jan  1  1970 gp1
drwxr-xr-x 2 root root 0 Jan  1  1970 gp2
drwxr-xr-x 2 root root 0 Jan  1  1970 h264
drwxr-xr-x 2 root root 0 Jan  1  1970 hsm
drwxr-xr-x 2 root root 0 Jan  1  1970 isp
drwxr-xr-x 2 root root 0 Jan  1  1970 osc
drwxr-xr-x 2 root root 0 Jan  1  1970 otg
drwxr-xr-x 2 root root 0 Jan  1  1970 otp
drwxr-xr-x 2 root root 0 Jan  1  1970 pcm
drwxr-xr-x 2 root root 0 Jan  1  1970 peri_image
drwxr-xr-x 2 root root 0 Jan  1  1970 plla
drwxr-xr-x 2 root root 0 Jan  1  1970 plla_ccp2
drwxr-xr-x 2 root root 0 Jan  1  1970 plla_core
drwxr-xr-x 2 root root 0 Jan  1  1970 plla_dsi0
drwxr-xr-x 2 root root 0 Jan  1  1970 plla_per
drwxr-xr-x 2 root root 0 Jan  1  1970 pllb
drwxr-xr-x 2 root root 0 Jan  1  1970 pllb_arm
drwxr-xr-x 2 root root 0 Jan  1  1970 pllc
drwxr-xr-x 2 root root 0 Jan  1  1970 pllc_core0
drwxr-xr-x 2 root root 0 Jan  1  1970 pllc_core1
drwxr-xr-x 2 root root 0 Jan  1  1970 pllc_core2
drwxr-xr-x 2 root root 0 Jan  1  1970 pllc_per
drwxr-xr-x 2 root root 0 Jan  1  1970 plld
drwxr-xr-x 2 root root 0 Jan  1  1970 plld_core
drwxr-xr-x 2 root root 0 Jan  1  1970 plld_dsi0
drwxr-xr-x 2 root root 0 Jan  1  1970 plld_dsi1
drwxr-xr-x 2 root root 0 Jan  1  1970 plld_per
drwxr-xr-x 2 root root 0 Jan  1  1970 pwm
drwxr-xr-x 2 root root 0 Jan  1  1970 sdram
drwxr-xr-x 2 root root 0 Jan  1  1970 slim
drwxr-xr-x 2 root root 0 Jan  1  1970 smi
drwxr-xr-x 2 root root 0 Jan  1  1970 tec
drwxr-xr-x 2 root root 0 Jan  1  1970 timer
drwxr-xr-x 2 root root 0 Jan  1  1970 tsens
drwxr-xr-x 2 root root 0 Jan  1  1970 uart
drwxr-xr-x 2 root root 0 Jan  1  1970 v3d
drwxr-xr-x 2 root root 0 Jan  1  1970 vec
drwxr-xr-x 2 root root 0 Jan  1  1970 vpu

rpi4 /sys/kernel/debug/clk$ cat clk_summary
                                 enable  prepare  protect                                duty
   clock                          count    count    count        rate   accuracy phase  cycle
---------------------------------------------------------------------------------------------
 otg                                  0        0        0   480000000          0     0  50000
 osc                                  5        5        0    54000000          0     0  50000
    tsens                             1        1        0     3375000          0     0  50000
    otp                               0        0        0    13500000          0     0  50000
    timer                             0        0        0     1000000          0     0  50000
    plld                              5        5        0  2999999988          0     0  50000
       plld_dsi1                      1        1        0    11718750          0     0  50000
       plld_dsi0                      1        1        0    11718750          0     0  50000
       plld_per                       3        3        0   749999997          0     0  50000
          emmc2                       1        1        0    99999999          0     0  50000
          uart                        1        1        0    47999999          0     0  50000
       plld_core                      1        1        0   599999998          0     0  50000
    pllc                              5        5        0  2999999988          0     0  50000
       pllc_per                       1        1        0   599999998          0     0  50000
          emmc                        0        0        0   199999999          0     0  50000
       pllc_core2                     1        1        0    11718750          0     0  50000
       pllc_core1                     1        1        0    11718750          0     0  50000
       pllc_core0                     2        2        0   499999998          0     0  50000
          vpu                         2        2        0   500000000          0     0  50000
             aux_spi2                 0        0        0   500000000          0     0  50000
             aux_spi1                 0        0        0   500000000          0     0  50000
             aux_uart                 0        0        0   500000000          0     0  50000
             peri_image               0        0        0   500000000          0     0  50000
    pllb                              2        2        0  2999999988          0     0  50000
       pllb_arm                       1        1        0  1499999994          0     0  50000
    plla                              2        2        0  2999999988          0     0  50000
       plla_ccp2                      0        0        0    11718750          0     0  50000
       plla_dsi0                      0        0        0    11718750          0     0  50000
       plla_per                       0        0        0    11718750          0     0  50000
       plla_core                      2        2        0   499999998          0     0  50000
          h264                        0        0        0   499999998          0     0  50000
          isp                         0        0        0   499999998          0     0  50000
          v3d                         1        1        0    31257631          0     0  50000
 dsi1p                                0        0        0           0          0     0  50000
 dsi0p                                0        0        0           0          0     0  50000
 dsi1e                                0        0        0           0          0     0  50000
 dsi0e                                0        0        0           0          0     0  50000
 cam1                                 0        0        0           0          0     0  50000
 cam0                                 0        0        0           0          0     0  50000
 dpi                                  0        0        0           0          0     0  50000
 tec                                  0        0        0           0          0     0  50000
 smi                                  0        0        0           0          0     0  50000
 slim                                 0        0        0           0          0     0  50000
 gp2                                  0        0        0           0          0     0  50000
 gp1                                  0        0        0           0          0     0  50000
 gp0                                  0        0        0           0          0     0  50000
 dft                                  0        0        0           0          0     0  50000
 aveo                                 0        0        0           0          0     0  50000
 pcm                                  0        0        0           0          0     0  50000
 pwm                                  0        0        0           0          0     0  50000
 sdram                                0        0        0           0          0     0  50000
 hsm                                  0        0        0           0          0     0  50000
 vec                                  0        0        0           0          0     0  50000

 


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;
        LIST_HEAD(clk_provider_list);

        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;

                if (!of_device_is_available(np))
                        continue;

                parent = kzalloc(sizeof(*parent), GFP_KERNEL);
                if (!parent) {
                        list_for_each_entry_safe(clk_provider, next,
                                                 &clk_provider_list, node) {
                                list_del(&clk_provider->node);
                                of_node_put(clk_provider->np);
                                kfree(clk_provider);
                        }
                        of_node_put(np);
                        return;
                }

                parent->clk_init_cb = match->data;
                parent->np = of_node_get(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)) {

                                /* Don't populate platform devices */
                                of_node_set_flag(clk_provider->np,
                                                 OF_POPULATED);

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

                                list_del(&clk_provider->node);
                                of_node_put(clk_provider->np);
                                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를 스캔하고 초기화한다.

  • 코드 라인 10~11에서 matches 값으로 null이 지정된 경우 컴파일 타임에 CLK_OF_DECLARE() 매크로로 만들어진 __clk_of_table 섹션에 있는 모든 클럭 엔트리를 대상으로 한다.
  • 코드 라인 14~35에서 Device Tree에서 matches와 엔트리와 비교하여 일치하는 항목들에 대해 루프를 돌며 clock_provider를 할당받아 구성하고 clk_provider_list에 추가한다. 메모리 부족 시에는 등록된 모든 clock_provider를 할당 해제하고함수를 빠져나간다.
    • clk_init_cb에는 클럭 초기화 함수가 대입된다.
    • np에는 device_node가 대입된다.
  • 코드 라인 37~38에서 clk_provider_list의 엔트리가 비워질때 까지 루프를 돈다. is_init_done을 false로 하여 다음 루프에서 초기화함수를 한 번도 호출하지 않은 경우 다음에 루프를 돌면 강제로 호출하게 만든다.
  • 코드 라인 39~40에서 clk_provide_list의 엔트리 수 만큼 루프를 돈다.
  • 코드 라인 41~54에서 이전 루프에서 한 번도 초기화 함수를 호출하지 않은 경우와 부모 클럭이 없거나 모두 초기화된 경우 해당 클럭의 초기화 함수를 호출하고 이 클럭을 default로 설정한다. 그런 후 clk_provider_list에서 제거하고 루프내에서 한 번이라도 초기화되었음을 알리도록 is_int_done을 true로 설정한다.
  • 코드 라인 63~64에서 이 전 루프를 돌 때 한 번도 초기화된 적 없으면 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) 클럭 디바이스부터 초기화되는 과정을 보여준다.

  • 리스트에 H, C, D, B, E, A, F, G 클럭 프로바이더들이 등록되어 있다고 가정한다.

 

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=부모 클럭 노드들 중 하나라도 초기화 되지 않았을 경우

  • 코드 라인 5~6에서 클럭의 부모 노드가 여러 개일 수 있으므로 루프를 반복하고 지정한 인덱스의 부모 클럭을 알아온다.
  • 코드 라인 9~13에서 지정된 인덱스의 부모 클럭이 이미 초기화된 경우 인덱스를 증가시키고 skip 한다.
  • 코드 라인 16~17에서 부모 클럭 노드가 아직 초기화되지 않은 경우 0을 반환한다.
  • 코드 라인 27에서 부모가 없는 경우 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;
}

루트 클럭 리스트와 고아(orphan) 클럭 리스트에 등록된 모든 하위 클럭들을 포함하여 요청한 이름의 클럭(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- (부모 인덱스 사용)

clk_core_get_parent_by_index()

drivers/clk/clk.c

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을 반환한다.
  • 코드 라인 7~8에서 인덱스에 해당하는 부모 클럭맵을 설정한다.
  • 코드 라인 11~12에서 인덱스에 해당하는 부모 클럭을 반환한다.

 

clk_core_fill_parent_index()

drivers/clk/clk.c

static void clk_core_fill_parent_index(struct clk_core *core, u8 index)
{
        struct clk_parent_map *entry = &core->parents[index];
        struct clk_core *parent = ERR_PTR(-ENOENT);

        if (entry->hw) {
                parent = entry->hw->core;
                /*
                 * We have a direct reference but it isn't registered yet?
                 * Orphan it and let clk_reparent() update the orphan status
                 * when the parent is registered.
                 */
                if (!parent)
                        parent = ERR_PTR(-EPROBE_DEFER);
        } else {
                parent = clk_core_get(core, index);
                if (IS_ERR(parent) && PTR_ERR(parent) == -ENOENT && entry->name)
                        parent = clk_core_lookup(entry->name);
        }

        /* Only cache it if it's not an error */
        if (!IS_ERR(parent))
                entry->core = parent;
}

@index에 해당하는 부모 클럭맵을 설정한다.

 


Device Tree로 클럭 셋업

Fixed Rate 타입 Clk 디바이스 셋업

of_fixed_clk_setup()

drivers/clk/clk-fixed-rate.c

/**
 * of_fixed_clk_setup() - Setup function for simple fixed rate clock
 */
void __init of_fixed_clk_setup(struct device_node *node)
{
        _of_fixed_clk_setup(node);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);

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

 

_of_fixed_clk_setup()

drivers/clk/clk-fixed-rate.c

static struct clk *_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 ERR_PTR(-EIO);

        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,
                                                    0, rate, accuracy);
        if (IS_ERR(clk))
                return clk;

        ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
        if (ret) {
                clk_unregister(clk);
                return ERR_PTR(ret);
        }

        return clk;
}

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

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

 

Fixed Factor 타입 Clk 디바이스 셋업

of_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)
{
        _of_fixed_factor_clk_setup(node);
}
CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock",
                of_fixed_factor_clk_setup);

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

 

_of_fixed_factor_clk_setup()

drivers/clk/clk-fixed-factor.c

static struct clk_hw *_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;
        int ret;

        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 ERR_PTR(-EIO);
        }

        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 ERR_PTR(-EIO);
        }

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

        if (of_match_node(set_rate_parent_matches, node))
                flags |= CLK_SET_RATE_PARENT;

        hw = __clk_hw_register_fixed_factor(NULL, node, clk_name, NULL, 0,
                                            flags, mult, div);
        if (IS_ERR(hw)) {
                /*
                 * Clear OF_POPULATED flag so that clock registration can be
                 * attempted again from probe function.
                 */
                of_node_clear_flag(node, OF_POPULATED);
                return ERR_CAST(hw);
        }

        ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
        if (ret) {
                clk_hw_unregister_fixed_factor(hw);
                return ERR_PTR(ret);
        }

        return hw;
}

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

  • 코드 라인 9~13에서 요청한 클럭 노드에서 “clock-div” 속성 값을 읽어 div에 대입한다.
  • 코드 라인 15~19에서 “clock-mult” 값을 읽어 multi에 대입한다.
  • 코드 라인 21에서 “clock-output-names” 속성 값(문자열)을 읽어 clk_name에 대입한다. 속성이 없는 경우 노드명을 사용한다.
  • 코드 라인 23~24에서 디바이스 노드가 set_rate_parent_matches[]에 적합한 드라이버인 경우 CLK_SET_RATE_PARENT 플래그를 설정한다.
  • 코드 라인 26~35에서 읽어들인 div, multi, parent_name 및 clk_name으로 fixed factor 타입의 클럭으로 등록한다.
  • 코드 라인 37~43에서 등록이 성공된 경우 클럭 provider에 추가하고 clk_hw를 반환한다.

 

다음 그림은 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 장치이다.

 


타입별 클럭 등록 함수

9개 타입의 클럭에 대해 multiplier 타입을 제외한 등록 함수들이 API로 제공된다. 그리고 fixed rate 및 fixed factor 타입의 경우 디바이스 트리를 사용한 API도 제공된다. 소스 분석은 fixed rate 타입과 fixed factor 타입으로 제한하였다.

  • multiplier 타입의 경우는 아직 composite 타입에 연동하여 사용되므로 등록 함수는 제외된다.

 

다음 그림과 같이 클럭은 3가지 형태로 구분할 수 있으며 총 9개의 타입을 가지고 있으며 각각의 등록 함수를 보여준다.

common 클럭 타입별 ops

composite 타입을 제외한 ops를 알아본다. composite 타입의 경우 별도의 ops를 사용하지 않고, 다른 클럭 타입들 중 rate, mux 및 gate 클럭의 ops를 두 개 이상 지정하여 복합 구성할 수 있다.

 

clk_fixed_rate_ops

/drivers/clk/clk-fixed-rate.c

const struct clk_ops clk_fixed_rate_ops = {
        .recalc_rate = clk_fixed_rate_recalc_rate,
        .recalc_accuracy = clk_fixed_rate_recalc_accuracy,
};
EXPORT_SYMBOL_GPL(clk_fixed_rate_ops);

 

clk_fixed_factor_ops

/drivers/clk/clk-fixed-factor.c

const struct clk_ops clk_fixed_factor_ops = {
        .round_rate = clk_factor_round_rate,
        .set_rate = clk_factor_set_rate,
        .recalc_rate = clk_factor_recalc_rate,
};
EXPORT_SYMBOL_GPL(clk_fixed_factor_ops);

 

clk_divider_ops

/drivers/clk/clk-divider.c

const struct clk_ops clk_divider_ops = {
        .recalc_rate = clk_divider_recalc_rate,
        .round_rate = clk_divider_round_rate,
        .set_rate = clk_divider_set_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ops);

 

clk_divider_ro_ops

/drivers/clk/clk-divider.c

const struct clk_ops clk_divider_ro_ops = {
        .recalc_rate = clk_divider_recalc_rate,
        .round_rate = clk_divider_round_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ro_ops);
  • CLK_DIVIDER_READ_ONLY 플래그를 사용하는 경우 사용되는 ops이다.

 

clk_fractional_divider_ops

/drivers/clk/

const struct clk_ops clk_fractional_divider_ops = {
        .recalc_rate = clk_fd_recalc_rate,
        .round_rate = clk_fd_round_rate,
        .set_rate = clk_fd_set_rate,
};
EXPORT_SYMBOL_GPL(clk_fractional_divider_ops);

 

clk_multiplier_ops

/drivers/clk/clk-fractional-divider.c

const struct clk_ops clk_multiplier_ops = {
        .recalc_rate    = clk_multiplier_recalc_rate,
        .round_rate     = clk_multiplier_round_rate,
        .set_rate       = clk_multiplier_set_rate,
};
EXPORT_SYMBOL_GPL(clk_multiplier_ops);

 

clk_mux_ops

/drivers/clk/clk-mux.c

const struct clk_ops clk_mux_ops = {
        .get_parent = clk_mux_get_parent,
        .set_parent = clk_mux_set_parent,
        .determine_rate = clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_mux_ops);

 

clk_mux_ro_ops

/drivers/clk/clk-mux.c

const struct clk_ops clk_mux_ro_ops = {
        .get_parent = clk_mux_get_parent,
};
EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
  • CLK_MUX_READ_ONLY 플래그를 사용하는 경우 사용되는 ops이다.

 

clk_gpio_gate_ops

/drivers/clk/clk-gpio.c

const struct clk_ops clk_gpio_gate_ops = {
        .enable = clk_gpio_gate_enable,
        .disable = clk_gpio_gate_disable,
        .is_enabled = clk_gpio_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gpio_gate_ops);

 

clk_gate_ops

/drivers/clk/clk-gate.c

const struct clk_ops clk_gate_ops = {
        .enable = clk_gate_enable,
        .disable = clk_gate_disable,
        .is_enabled = clk_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gate_ops);

 

Fixed Rate 타입 Clk 등록

clk_register_fixed_rate()

drivers/clk/clk-fixed-rate.c

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

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_hw *hw;

        hw = clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name,
                        flags, fixed_rate, fixed_accuracy);
        if (IS_ERR(hw))
                return ERR_CAST(hw);
        return hw->clk;
}
EXPORT_SYMBOL_GPL(clk_register_fixed_rate_with_accuracy);

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

 

clk_hw_register_fixed_rate()

drivers/clk/clk-fixed-rate.c

/**
 * clk_hw_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_hw *clk_hw_register_fixed_rate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned long fixed_rate)
{
        return clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name,
                                                     flags, fixed_rate, 0);
}
EXPORT_SYMBOL_GPL(clk_hw_register_fixed_rate);

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

 

clk_hw_register_fixed_rate_with_accuracy()

drivers/clk/clk-fixed-rate.c

/**
 * clk_hw_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_hw *clk_hw_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_hw *hw;
        struct clk_init_data init;
        int ret;

        /* allocate fixed-rate clock */
        fixed = kzalloc(sizeof(*fixed), GFP_KERNEL);
        if (!fixed)
                return ERR_PTR(-ENOMEM);

        init.name = name;
        init.ops = &clk_fixed_rate_ops;
        init.flags = flags;
        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 */
        hw = &fixed->hw;
        ret = clk_hw_register(dev, hw);
        if (ret) {
                kfree(fixed);
                hw = ERR_PTR(ret);
        }

        return hw;
}
EXPORT_SYMBOL_GPL(clk_hw_register_fixed_rate_with_accuracy);

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

  • 코드 라인 11~19에서 clk_fixed_rate 구조체를 할당받아 fixed rate 클럭에대한 기본 정보를 구성한다.
  • 코드 라인 22~24에서 @fixed_rate, @fixed_accuracy  등을 추가하여 구성한다.
  • 코드 라인 27~34에서 클럭을 등록한 후 clk_hw를 반환한다.

 

다음 그림은 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_hw *hw;

        hw = clk_hw_register_fixed_factor(dev, name, parent_name, flags, mult,
                                          div);
        if (IS_ERR(hw))
                return ERR_CAST(hw);
        return hw->clk;
}
EXPORT_SYMBOL_GPL(clk_register_fixed_factor);

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

 

clk_hw_register_fixed_factor()

drivers/clk/clk-fixed-factor.c

struct clk_hw *clk_hw_register_fixed_factor(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                unsigned int mult, unsigned int div)
{
        return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, -1,
                                              flags, mult, div);
}
EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor);

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

 

__clk_hw_register_fixed_factor()

drivers/clk/clk-fixed-factor.c

static struct clk_hw *
__clk_hw_register_fixed_factor(struct device *dev, struct device_node *np,
                const char *name, const char *parent_name, int index,
                unsigned long flags, unsigned int mult, unsigned int div)
{
        struct clk_fixed_factor *fix;
        struct clk_init_data init = { };
        struct clk_parent_data pdata = { .index = index };
        struct clk_hw *hw;
        int ret;

        fix = kmalloc(sizeof(*fix), GFP_KERNEL);
        if (!fix)
                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;
        if (parent_name)
                init.parent_names = &parent_name;
        else
                init.parent_data = &pdata;
        init.num_parents = 1;

        hw = &fix->hw;
        if (dev)
                ret = clk_hw_register(dev, hw);
        else
                ret = of_clk_hw_register(np, hw);
        if (ret) {
                kfree(fix);
                hw = ERR_PTR(ret);
        }

        return hw;
}

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

  • 코드 라인 12~14에서 clk_fixed_facotr 구조체를 할당받고 multi 값과 div 값 등을 구성한다.
  • 코드 라인 17~28에서 할당 받은 구조체를 fixed factor 타입으로 구성한다.
  • 코드 라인 30~40에서 클럭을 등록한다

 


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 *deprecated* interface for populating the clock tree with
 * new clock nodes. Use clk_hw_register() instead.
 *
 * Returns: a pointer to the newly allocated struct clk which
 * cannot be dereferenced by driver code but may be used in conjunction 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)
{
        return __clk_register(dev, dev_of_node(dev), hw);
}
EXPORT_SYMBOL_GPL(clk_register);

요청한 클럭 디바이스를 할당하여 등록한다.

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

 

static struct clk *
__clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
{
        int ret;
        struct clk_core *core;
        const struct clk_init_data *init = hw->init;

        /*
         * The init data is not supposed to be used outside of registration path.
         * Set it to NULL so that provider drivers can't use it either and so that
         * we catch use of hw->init early on in the core.
         */
        hw->init = NULL;

        core = kzalloc(sizeof(*core), GFP_KERNEL);
        if (!core) {
                ret = -ENOMEM;
                goto fail_out;
        }

        core->name = kstrdup_const(init->name, GFP_KERNEL);
        if (!core->name) {
                ret = -ENOMEM;
                goto fail_name;
        }

        if (WARN_ON(!init->ops)) {
                ret = -EINVAL;
                goto fail_ops;
        }
        core->ops = init->ops;

        if (dev && pm_runtime_enabled(dev))
                core->rpm_enabled = true;
        core->dev = dev;
        core->of_node = np;
        if (dev && dev->driver)
                core->owner = dev->driver->owner;
        core->hw = hw;
        core->flags = init->flags;
        core->num_parents = init->num_parents;
        core->min_rate = 0;
        core->max_rate = ULONG_MAX;
        hw->core = core;

        ret = clk_core_populate_parent_map(core, init);
        if (ret)
                goto fail_parents;

        INIT_HLIST_HEAD(&core->clks);

        /*
         * Don't call clk_hw_create_clk() here because that would pin the
         * provider module to itself and prevent it from ever being removed.
         */
        hw->clk = alloc_clk(core, NULL, NULL);
        if (IS_ERR(hw->clk)) {
                ret = PTR_ERR(hw->clk);
                goto fail_create_clk;
        }

        clk_core_link_consumer(hw->core, hw->clk);

        ret = __clk_core_init(core);
        if (!ret)
                return hw->clk;

        clk_prepare_lock();
        clk_core_unlink_consumer(hw->clk);
        clk_prepare_unlock();

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

fail_create_clk:
        clk_core_free_parent_map(core);
fail_parents:
fail_ops:
        kfree_const(core->name);
fail_name:
        kfree(core);
fail_out:
        return ERR_PTR(ret);
}

요청한 클럭 디바이스를 할당하여 등록한다.

  • 코드 라인 6에서 클럭 init 데이터를 클럭 core에 복사할 목적으로 init 포인터에 대입한다.
  • 코드 라인 15~19에서 clk_core 구조체를 할당받아 core에 대입한다.
  • 코드 라인 21~25에서 core->name에 hw->init->name의 복사본 문자열을 대입한다.
    • kstrdup_const()
      • 문자열이 rodata(읽기 전용 커널 데이터) 섹션에 위치한 경우 복제하지 않고 const 타입으로 그냥 사용한다.
  • 코드 라인 27~31에서 클럭 init 데이터로 넘겨 받은 클럭의 ops를 지정한다.
  • 코드 라인 33~44에서 클럭 init 데이터를 사용하여 clk_core 구조체를 구성한다.
  • 코드 라인 46~48에서 부모 클럭 맵을 준비한다.
  • 코드 라인 50에서 core->clks 리스트를 초기화한다.
  • 코드 라인 56~60에서 clk를 할당받아 구성한다.
  • 코드 라인 62에서 클럭 core를 클럭 리스트에 추가한다.
  • 코드 라인 64~66에서 클럭 core를 초기화하고, 성공한 경우 클럭을 반환한다.
  • 코드 라인 68~83에서 클럭 core 등록이 실패한 경우 생성했던 모든 객체들을 free 시키고 에러를 반환한다.

 

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

 

다음 그림은 등록된 클럭의 부모 관계를 보여준다. 클럭을 사용하는 관점에서 Consumer용 클럭도 같이 표현하였다.

 

clk_core_populate_parent_map()

drivers/clk/clk.c

static int clk_core_populate_parent_map(struct clk_core *core,
                                        const struct clk_init_data *init)
{
        u8 num_parents = init->num_parents;
        const char * const *parent_names = init->parent_names;
        const struct clk_hw **parent_hws = init->parent_hws;
        const struct clk_parent_data *parent_data = init->parent_data;
        int i, ret = 0;
        struct clk_parent_map *parents, *parent;

        if (!num_parents)
                return 0;

        /*
         * Avoid unnecessary string look-ups of clk_core's possible parents by
         * having a cache of names/clk_hw pointers to clk_core pointers.
         */
        parents = kcalloc(num_parents, sizeof(*parents), GFP_KERNEL);
        core->parents = parents;
        if (!parents)
                return -ENOMEM;

        /* Copy everything over because it might be __initdata */
        for (i = 0, parent = parents; i < num_parents; i++, parent++) {
                parent->index = -1;
                if (parent_names) {
                        /* throw a WARN if any entries are NULL */
                        WARN(!parent_names[i],
                                "%s: invalid NULL in %s's .parent_names\n",
                                __func__, core->name);
                        ret = clk_cpy_name(&parent->name, parent_names[i],
                                           true);
                } else if (parent_data) {
                        parent->hw = parent_data[i].hw;
                        parent->index = parent_data[i].index;
                        ret = clk_cpy_name(&parent->fw_name,
                                           parent_data[i].fw_name, false);
                        if (!ret)
                                ret = clk_cpy_name(&parent->name,
                                                   parent_data[i].name,
                                                   false);
                } else if (parent_hws) {
                        parent->hw = parent_hws[i];
                } else {
                        ret = -EINVAL;
                        WARN(1, "Must specify parents if num_parents > 0\n");
                }

                if (ret) {
                        do {
                                kfree_const(parents[i].name);
                                kfree_const(parents[i].fw_name);
                        } while (--i >= 0);
                        kfree(parents);

                        return ret;
                }
        }

        return 0;
}

요청한 클럭의 부모들을 연결하는 부모 맵을 구성한다. @init에서 넘겨준 parent_names, parent_data 또는 parent_hws 라는 세 가지 정보들 중 하나로 부모 정보를 구성한다.

  • 코드 라인 11~12에서 부모 클럭이 없는 경우 성공 값으로 0을 반환한다.
  • 코드 라인 18~21에서 부모 수 만큼 clk_parent_data 구조체를 할당받는다.
  • 코드 라인 24~47에서 @init 정보에 포함된 부모 클럭 정보를 가져와서 구성한다. 다음 순서 중 하나를 선택하여 구성한다.
    • init->parent_names[] 정보가 제공된 경우 이 이름 정보만으로 구성한다.
    • init->parent_data[] 정보가 제공된 경우 이 구성 정보를 그대로 복사하여 구성한다.
    • init->parent_hws[] 정보가 제공된 경우 이 hws 정보를 그대로 이용한다.
  • 코드 라인 49~58에서 부모 정보가 제대로 제공되지 않은 경우 할당 했었던 정보를 할당 해제 후 에러를 반환한다.
  • 코드 라인 60에서 에러 없이 모든 부모 정보를 구성한 경우 성공 값으로 0을 반환한다.

 

다음 그림은 해당 클럭의 부모들 연결하는 부모 맵을 구성하는 과정을 보여준다.

 

alloc_clk()

drivers/clk/clk.c

/**
 * alloc_clk - Allocate a clk consumer, but leave it unlinked to the clk_core
 * @core: clk to allocate a consumer for
 * @dev_id: string describing device name
 * @con_id: connection ID string on device
 *
 * Returns: clk consumer left unlinked from the consumer list
 */
static struct clk *alloc_clk(struct clk_core *core, const char *dev_id,
                             const char *con_id)
{
        struct clk *clk;

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

        clk->core = core;
        clk->dev_id = dev_id;
        clk->con_id = kstrdup_const(con_id, GFP_KERNEL);
        clk->max_rate = ULONG_MAX;

        return clk;
}

클럭을 할당하고 구성한 후 반환한다.

 

__clk_core_init()

drivers/clk/clk.c -1/3-

/**
 * __clk_core_init - initialize the data structures in a struct clk_core
 * @core:       clk_core being initialized
 *
 * Initializes the lists in struct clk_core, queries the hardware for the
 * parent and rate and sets them both.
 */
static int __clk_core_init(struct clk_core *core)
{
        int ret;
        struct clk_core *orphan;
        struct hlist_node *tmp2;
        unsigned long rate;

        if (!core)
                return -EINVAL;

        clk_prepare_lock();

        ret = clk_pm_runtime_get(core);
        if (ret)
                goto unlock;

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

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

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

        if (core->num_parents > 1 && !core->ops->get_parent) {
                pr_err("%s: %s must implement .get_parent as it has multi parents\n",
                       __func__, core->name);
                ret = -EINVAL;
                goto out;
        }

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

        core->parent = __clk_init_parent(core);

클럭을 초기화한다. 이 때 부모 클럭 관계와 rate, accuracy 등을 설정한다.

  • 코드 라인 18~23에서 이름으로 클럭을 검색하여 이미 존재하는 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 26~34에서 rate가 고정된 fixed rate 타입을 제외하고 나머지 rate를 설정할 수 있는 클럭들의 경우 (*set_rate) 후크 함수가 주어진다. 이 때 (*recalc_rate)가 준비되어야 하고, (*round_rate) 또는 (*determine_rate) 둘 중 하나의 후크 함수가 필요하므로 이를 체크한다.
    • fixed-factor, divider, multiplier, fractional-divider 클럭 타입등이 해당된다.
  • 코드 라인 36~41에서 mux 클럭의 경우 (*set_parent) 후크가 주어진다. 이 때 (*get_parent)도 필요하므로 체크한다.
  • 코드 라인 43~48에서 2개 이상의 부모를 가진 mux 클럭의 경우 (*get_parent) 후크 함수가 필요한다.
  • 코드 라인 50~56에서 부모의 클럭 rate를 변경할 수 있는 (*set_rate_and_parent) 후크 함수를 가진 클럭인 경우 (*set_parent)와 (*set_rate) 후크 함수를 필요로 한다.
    • 이 기능은 common 클럭 타입에는 없고, 일부 custom 클럭에서 제공되고 있다.
  • 코드 라인 58에서 최초 연결될 부모 클럭맵을 구성하고 부모 클럭을 알아온다.

 

drivers/clk/clk.c -2/3-

.       /*
         * Populate core->parent if parent has already been clk_core_init'd. If
         * parent has not yet been clk_core_init'd then place clk in the orphan
         * list.  If clk doesn't have any parents 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 (core->parent) {
                hlist_add_head(&core->child_node,
                                &core->parent->children);
                core->orphan = core->parent->orphan;
        } else if (!core->num_parents) {
                hlist_add_head(&core->child_node, &clk_root_list);
                core->orphan = false;
        } else {
                hlist_add_head(&core->child_node, &clk_orphan_list);
                core->orphan = true;
        }

        /*
         * 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 (core->ops->init)
                core->ops->init(core->hw);

        /*
         * 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 (core->ops->recalc_accuracy)
                core->accuracy = core->ops->recalc_accuracy(core->hw,
                                        __clk_get_accuracy(core->parent));
        else if (core->parent)
                core->accuracy = core->parent->accuracy;
        else
                core->accuracy = 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 (core->ops->get_phase)
                core->phase = core->ops->get_phase(core->hw);
        else
                core->phase = 0;

        /*
         * Set clk's duty cycle.
         */
        clk_core_update_duty_cycle_nolock(core);

        /*
         * 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 (core->ops->recalc_rate)
                rate = core->ops->recalc_rate(core->hw,
                                clk_core_get_rate_nolock(core->parent));
        else if (core->parent)
                rate = core->parent->rate;
        else
                rate = 0;
        core->rate = core->req_rate = rate;
  • 코드 라인 11~21에서 클럭의 하이라키를 구성한다.
    • 부모 클럭이 지정된 경우 부모 클럭의 children 리스트에 등록한다.
    • 루트 클럭인 경우 clk_root_list에 등록한다.
    • 고아(orphan) 클럭인 경우 clk_orphan_list에 등록한다.
      • clk_root_list에 root 클럭만 추가되듯이,
      • clk_orphan_list에는 고아 클럭들 중 상위 부모만 추가된다.
        • 예) (A 고아) — B  –  C
          • A만 clk_orphan_list에 추가된다.
      • 모든 clock core들은 반드시 다음 3가지 리스트 중 하나에 연결된다.
        • clk_root_list
        • clk_orphan_list
        • 부모의 children 리스트
  • 코드 라인 31~32에서 클럭 초기화를 위해 아키텍처가 지원하는 별도의 (*init) 후크 함수를 수행한다.
    • common 클럭 타입에는 없고, 일부 custom 클럭에서 제공되고 있다.
  • 코드 라인 41~47에서 클럭의 정확도(accuracy)를 설정한다.
    • fixed rate 타입의 클럭인 경우 (*recalc_accuracy) 후크 함수를 수행하여 클럭의 정확도(accuracy)를 설정한다.
    • 그렇지 않은 경우 부모 클럭이 있으면 부모 클럭의 accuracy 값을 사용하고, 없으면 0을 설정한다.
  • 코드 라인 54~57에서 custom 클럭들 중 클럭의 위상(phase)을 지정하는 클럭들이 있다. 이러한 클럭의 위상(phase)을 설정한다.
    • (*get_phase) 후크 함수를 수행하여 phase 값을 알아오고, 해당 후크 함수가 없으면 0으로 설정한다.
  • 코드 라인 62에서 클럭의 duty cycle을 지정한다.
    • duty 사이클은 on에 해당하는 시간 / 1 주기 시간에 대한 백분율이다.
    • 참고: Duty Cycle | Wikipedia
  • 코드 라인 70~77에서 클럭의 rate를 설정한다.
    • rate 타입의 클럭인 경우 (*recalc_rate) 후크 함수를 지원하므로 이를 통해 rate 값을 구해온다.
    • rate 타입의 클럭이 아닌 경우 부모의 rate를 사용하거나 부모가 없으면 0으로 설정한다.

 

drivers/clk/clk.c -3/3-

        /*
         * Enable CLK_IS_CRITICAL clocks so newly added critical clocks
         * don't get accidentally disabled when walking the orphan tree and
         * reparenting clocks
         */
        if (core->flags & CLK_IS_CRITICAL) {
                unsigned long flags;

                clk_core_prepare(core);

                flags = clk_enable_lock();
                clk_core_enable(core);
                clk_enable_unlock(flags);
        }

        /*
         * walk the list of orphan clocks and reparent any that newly finds a
         * parent.
         */
        hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
                struct clk_core *parent = __clk_init_parent(orphan);

                /*
                 * We need to use __clk_set_parent_before() and _after() to
                 * to properly migrate any prepare/enable count of the orphan
                 * clock. This is important for CLK_IS_CRITICAL clocks, which
                 * are enabled during init but might not have a parent yet.
                 */
                if (parent) {
                        /* update the clk tree topology */
                        __clk_set_parent_before(orphan, parent);
                        __clk_set_parent_after(orphan, parent, NULL);
                        __clk_recalc_accuracies(orphan);
                        __clk_recalc_rates(orphan, 0);
                }
        }

        kref_init(&core->ref);
out:
        clk_pm_runtime_put(core);
unlock:
        clk_prepare_unlock();

        if (!ret)
                clk_debug_register(core);

        return ret;
}
  • 코드 라인 6~14에서 CLK_IS_CRITICAL 플래그를 사용한 경우 일시적으로 고아(orphan) 클럭이었다가 부모 클럭이 활성화되지 않은 상태에 놓여있어도 클럭이 항상 동작하게 한다.
  • 코드 라인 20~36에서 고아 클럭 리스트인 clk_orphan_list에 등록된 클럭들을 순회하며 부모 클럭이 발견되면 이에 대한 accuracy 및 rate 설정을 다시 한다.

 

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

 

__clk_init_parent()

drivers/clk/clk.c

static struct clk_core *__clk_init_parent(struct clk_core *core)
{
        u8 index = 0;

        if (core->num_parents > 1 && core->ops->get_parent)
                index = core->ops->get_parent(core->hw);

        return clk_core_get_parent_by_index(core, index);
}

부모 클럭을 알아와서 연결한다.

 


클럭 Provider

다음과 같이 클럭 provider를 추가하는 두 개의 API가 지원된다.

 

클럭 provider 추가 -1-

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.
 *
 * This function is *deprecated*. Use of_clk_add_hw_provider() instead.
 */
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(*cp), 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 %pOF\n", np);

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

        return ret;
}
EXPORT_SYMBOL_GPL(of_clk_add_provider);

디바이스 노드의 클럭 provder를 추가한다. 추가 시 clk를 반환하는 함수를 지정한다. 성공하면 0을 반환한다.

  • 코드 라인 9~15에서 of_clk_provider 구조체를 할당받아 구성한다.
  • 코드 라인 17~20에서 of_clk_providers에 추가하고 디버그 메시지를 출력한다.
  • 코드 라인 22~24에서 default 부모 클럭과 rate를 설정한다. 만일 실패하는 경우 provider 리스트에서 제거한다.
  • 코드 라인 26에서 클럭 설정 결과를 반환한다. 성공 시 0을 반환한다.

 

클럭 공급자 추가 -2-

of_clk_add_hw_provider()

drivers/clk/clk.c

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

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

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

        mutex_lock(&of_clk_mutex);
        list_add(&cp->link, &of_clk_providers);
        mutex_unlock(&of_clk_mutex);
        pr_debug("Added clk_hw provider from %pOF\n", np);

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

        return ret;
}
EXPORT_SYMBOL_GPL(of_clk_add_hw_provider);

디바이스 노드의 클럭 provider를 추가한다. 추가 시 clk_hw를 반환하는 함수를 지정한다. 성공하면 0을 반환한다.

  • 코드 라인 9~15에서 of_clk_provider 구조체를 할당받아 구성한다.
  • 코드 라인 17~20에서 of_clk_providers에 추가하고 디버그 메시지를 출력한다.
  • 코드 라인 22~24에서 default 부모 클럭과 rate를 설정한다. 만일 실패하는 경우 provider 리스트에서 제거한다.
  • 코드 라인 26에서 클럭 설정 결과를 반환한다. 성공 시 0을 반환한다.

 

Provider에 등록된 클럭 사용

다음 그림은 provider에 등록된 클럭을 사용하는 함수 이후의 호출 과정을 보여준다.

 

of_clk_get_from_provider()

drivers/clk/clk.c

/**
 * of_clk_get_from_provider() - Lookup a clock from 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_from_provider(struct of_phandle_args *clkspec)
{
        struct clk_hw *hw = of_clk_get_hw_from_clkspec(clkspec);

        return clk_hw_create_clk(NULL, hw, NULL, __func__);
}
EXPORT_SYMBOL_GPL(of_clk_get_from_provider);

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

 

of_clk_get_hw_from_clkspec()

drivers/clk/clk.c

static struct clk_hw *
of_clk_get_hw_from_clkspec(struct of_phandle_args *clkspec)
{
        struct of_clk_provider *provider;
        struct clk_hw *hw = ERR_PTR(-EPROBE_DEFER);

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

        mutex_lock(&of_clk_mutex);
        list_for_each_entry(provider, &of_clk_providers, link) {
                if (provider->node == clkspec->np) {
                        hw = __of_clk_get_hw_from_provider(provider, clkspec);
                        if (!IS_ERR(hw))
                                break;
                }
        }
        mutex_unlock(&of_clk_mutex);

        return hw;
}

of_clk_providers 리스트에서 루프를 돌며 요청 노드를 찾은 후 등록된 클럭 hw를 찾아 반환한다.

 

__of_clk_get_hw_from_provider()

drivers/clk/clk.c

static struct clk_hw *
__of_clk_get_hw_from_provider(struct of_clk_provider *provider,
                              struct of_phandle_args *clkspec)
{
        struct clk *clk;

        if (provider->get_hw)
                return provider->get_hw(clkspec, provider->data);

        clk = provider->get(clkspec, provider->data);
        if (IS_ERR(clk))
                return ERR_CAST(clk);
        return __clk_get_hw(clk);
}

클럭 hw를 반환한다.

  • 코드 라인 6~7에서 (*get_hw) 후크를 호출하여 클럭 hw를 찾아 반환한다.
  • 코드 라인 9~12에서 위의 후크 함수가 없으면 호환을 위해 사용되는 (*get) 후크 함수를 호출하여 클럭을 찾고, 그 클럭의 클럭 hw를 반환한다.

 

다음 그림은 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 등으로 구성하여 반환한다.

 


클럭 consumer 디바이스

클럭 consumer 할당 및 클럭과 연결

clk_hw_create_clk()

drivers/clk/clk.c

/**
 * clk_hw_create_clk: Allocate and link a clk consumer to a clk_core given
 * a clk_hw
 * @dev: clk consumer device
 * @hw: clk_hw associated with the clk being consumed
 * @dev_id: string describing device name
 * @con_id: connection ID string on device
 *
 * This is the main function used to create a clk pointer for use by clk
 * consumers. It connects a consumer to the clk_core and clk_hw structures
 * used by the framework and clk provider respectively.
 */
struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw,
                              const char *dev_id, const char *con_id)
{
        struct clk *clk;
        struct clk_core *core;

        /* This is to allow this function to be chained to others */
        if (IS_ERR_OR_NULL(hw))
                return ERR_CAST(hw);

        core = hw->core;
        clk = alloc_clk(core, dev_id, con_id);
        if (IS_ERR(clk))
                return clk;
        clk->dev = dev;

        if (!try_module_get(core->owner)) {
                free_clk(clk);
                return ERR_PTR(-ENOENT);
        }

        kref_get(&core->ref);
        clk_core_link_consumer(core, clk);

        return clk;
}

클럭 consumer를 할당하고 클럭 core에 연결하고, 클럭을 반환한다.

  • 코드 라인 11~15에서 clk 구조체를 할당하고 디바이스 정보로 초기화한다.
  • 코드 라인 17~20에서 클럭에 대한 모듈이 없으면 -ENOENT 에러를 반환한다.
  • 코드 라인 22에서 클럭 참조 카운터를 증가시키고, 클럭 consumer와 클럭 core를 연결한다.
  • 코드 라인 24에서 클럭을 반환한다.

 

클럭 사용

다음 그림은 of_clk_get() 함수 이후의 호출 과정을 보여준다.

 

of_clk_get()

drivers/clk/clk.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/clk.c

static struct clk *__of_clk_get(struct device_node *np,
                                int index, const char *dev_id,
                                const char *con_id)
{
        struct clk_hw *hw = of_clk_get_hw(np, index, con_id);

        return clk_hw_create_clk(NULL, hw, dev_id, con_id);
}

디바이스 노드에서 부모 index 또는 @con_id(이름)로 부모 클럭을 알아온다.

  • 코드 라인 5에서 @index로 지정한 부모 클럭 hw를 알아온다.
  • 코드 라인 7에서 clk 구조체를 생성하고, 클럭 코어에 연동한다. 이 때 클럭 코어의 참조 카운터가 1 증가된다.

 

of_clk_get_hw()

drivers/clk/clk.c

struct clk_hw *of_clk_get_hw(struct device_node *np, int index,
                             const char *con_id)
{
        int ret;
        struct clk_hw *hw;
        struct of_phandle_args clkspec;

        ret = of_parse_clkspec(np, index, con_id, &clkspec);
        if (ret)
                return ERR_PTR(ret);

        hw = of_clk_get_hw_from_clkspec(&clkspec);
        of_node_put(clkspec.np);

        return hw;
}

디바이스 노드에서 @con_id(이름) 또는 부모 @index로 부모 클럭 hw를 알아온다.

  • 코드 라인 8~10에서 디바이스 노드에서 index에 해당하는 클럭 argument 값들을 알아온다.
  • 코드 라인 12~15에서 클럭 argument들을 분석하여 해당 클럭 hw를 알아와서 반환한다.

 

of_parse_clkspec()

drivers/clk/clk.c

/**
 * of_parse_clkspec() - Parse a DT clock specifier for a given device node
 * @np: device node to parse clock specifier from
 * @index: index of phandle to parse clock out of. If index < 0, @name is used
 * @name: clock name to find and parse. If name is NULL, the index is used
 * @out_args: Result of parsing the clock specifier
 *
 * Parses a device node's "clocks" and "clock-names" properties to find the
 * phandle and cells for the index or name that is desired. The resulting clock
 * specifier is placed into @out_args, or an errno is returned when there's a
 * parsing error. The @index argument is ignored if @name is non-NULL.
 *
 * Example:
 *
 * phandle1: clock-controller@1 {
 *      #clock-cells = <2>;
 * }
 *
 * phandle2: clock-controller@2 {
 *      #clock-cells = <1>;
 * }
 *
 * clock-consumer@3 {
 *      clocks = <&phandle1 1 2 &phandle2 3>;
 *      clock-names = "name1", "name2";
 * }
 *
 * To get a device_node for `clock-controller@2' node you may call this
 * function a few different ways:
 *
 *   of_parse_clkspec(clock-consumer@3, -1, "name2", &args);
 *   of_parse_clkspec(clock-consumer@3, 1, NULL, &args);
 *   of_parse_clkspec(clock-consumer@3, 1, "name2", &args);
 *
 * Return: 0 upon successfully parsing the clock specifier. Otherwise, -ENOENT
 * if @name is NULL or -EINVAL if @name is non-NULL and it can't be found in
 * the "clock-names" property of @np.
 */
static int of_parse_clkspec(const struct device_node *np, int index,
                            const char *name, struct of_phandle_args *out_args)
{
        int ret = -ENOENT;

        /* Walk up the tree of devices looking for a clock property that matches */
        while (np) {
                /*
                 * For named clocks, first look up the name in the
                 * "clock-names" property.  If it cannot be found, then index
                 * will be an error code and of_parse_phandle_with_args() will
                 * return -EINVAL.
                 */
                if (name)
                        index = of_property_match_string(np, "clock-names", name);
                ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells",
                                                 index, out_args);
                if (!ret)
                        break;
                if (name && index >= 0)
                        break;

                /*
                 * No matching clock found on this node.  If the parent node
                 * has a "clock-ranges" property, then we can try one of its
                 * clocks.
                 */
                np = np->parent;
                if (np && !of_get_property(np, "clock-ranges", NULL))
                        break;
                index = 0;
        }

        return ret;
}

디바이스 노드에서 @name 또는 @index에 해당하는 phandle 및 argument 값들을 알아와서 출력 인자 @out_args에 대입해온다.

  • 코드 라인 7에서 요청한 디바이스 노드부터 시작하여 최상위 노드까지 검색을 수행한다.
  • 코드 라인 14~15에서 @name이 주어진 경우 해당 인덱스를 찾는다.
  • 코드 라인 16~21에서 인덱스에 해당하는 phandle 및 argument 값들을 알아와서 출력 인자 @out_args에 대입해온다. 만일 찾은 경우 성공을 반환하기 위해 루프를 벗어난다.
  • 코드 라인 28~32에서 못찾은 경우 부모 노드로 이동하고 재시도한다. 단 이동한 후 “clock-ranges” 속성을 발견하면 에러를 반환하기 위해 루프를 벗어난다.

 


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 %pOF\n",
                       node);

        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_from_provider(&clkspec);
                if (IS_ERR(pclk)) {
                        if (PTR_ERR(pclk) != -EPROBE_DEFER)
                                pr_warn("clk: couldn't get parent clock %d for %pOF\n",
                                        index, node);
                        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_from_provider(&clkspec);
                if (IS_ERR(clk)) {
                        if (PTR_ERR(clk) != -EPROBE_DEFER)
                                pr_warn("clk: couldn't get assigned clock %d for %pOF\n",
                                        index, node);
                        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~31에서 clkspec으로 클럭을 알아온다.
  • 코드 라인 33~36에서 “assigned-clocks” 속성에서 부모 index로 clkspec 값을 알아온다.
  • 코드 라인 37~40에서 clk_supplier 값이 0 이면서 알아온 부모 노드가 요청 노드와 동일한 경우 성공(0)리에 함수를 빠져나간다.
  • 코드 라인 41~48에서 clkspec 값으로 클럭을 알아온다. 검색이 실패하는 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 50~53에서 부모 클럭으로 pclk를 지정한다.

 

Rate 설정

__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_from_provider(&clkspec);
                        if (IS_ERR(clk)) {
                                if (PTR_ERR(clk) != -EPROBE_DEFER)
                                        pr_warn("clk: couldn't get clock %d for %pOF\n",
                                                index, node);
                                return PTR_ERR(clk);
                        }

                        rc = clk_set_rate(clk, rate);
                        if (rc < 0)
                                pr_err("clk: couldn't set %s clk rate to %u (%d), current rate: %lu\\
n",
                                       __clk_get_name(clk), rate, rc,
                                       clk_get_rate(clk));
                        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~30에서 clkspec 값으로 클럭을 알아온다. 검색이 실패하는 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 32~37에서 클럭의 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";
                };

 


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

다음의 플래그들은 최상위 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
 *
 * Please update clk_flags[] in drivers/clk/clk.c when making changes here!
 */
#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 */
                                /* unused */
                                /* unused */
#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 */
#define CLK_RECALC_NEW_RATES    BIT(9) /* recalc rates after notifications */
#define CLK_SET_RATE_UNGATE     BIT(10) /* clock needs to run to set rate */
#define CLK_IS_CRITICAL         BIT(11) /* do not gate, ever */
/* parents need enable during gate/ungate, set rate and re-parent */
#define CLK_OPS_PARENT_ENABLE   BIT(12)
/* duty cycle call may be forwarded to the parent clock */
#define CLK_DUTY_CYCLE_PARENT   BIT(13)
  • CLK_SET_RATE_GATE
    • rate 변경 시 반드시 gate가 닫혀 있어야 한다.
  • CLK_SET_PARENT_GATE
    • 입력 클럭 소스(부모 클럭)를 선택 시 반드시 gate가 닫혀 있어야 한다.
  • CLK_SET_RATE_PARENT
    • 현재 클럭 hw가 지원하는 rate 변경이 불가능한 경우 부모에 전파(propogation) 하여 부모 클럭부터 rate를 변경하게 한다.
  • CLK_IGNORE_UNUSED
    • 사용하지 않아도 gate를 닫지 않는다.
  • CLK_GET_RATE_NOCACHE
  • CLK_SET_RATE_NO_REPARENT
  • CLK_GET_ACCURACY_NOCACHE
  • CLK_RECALC_NEW_RATES
  • CLK_SET_RATE_UNGATE
  • CLK_IS_CRITICAL
  • CLK_OPS_PARENT_ENABLE
    • 부모 클럭이 enable된 상태에서만 operation을 수행할 수 있는 클럭 hw를 지원한다.
    • CCF는 부모 클럭이 닫혀있으면 자동으로 잠시 prepare & enable하고 이 클럭의 operation을 수행한 후 다시 disable & unprepare를 수행한다.
    • 참고: clk: core: support clocks which requires parents enable (part 1) (2016, v4.8-rc1)
  • CLK_DUTY_CYCLE_PARENT
  • CLK_IS_ROOT (deleted)
  • CLK_IS_BASIC (deleted)
    • clk_foo()와 같은 파생 클럭이 아닌 클럭이다.
    • common clock framework에 구현되어 있는 8개의 클럭 디바이스 드라이버는 모두 CLK_IS_BASIC 플래그가 설정되어 있다.
    • 참고: clk: Remove CLK_IS_BASIC clk flag (2019, v5.2-rc1)

 


구조체

clk 구조체

drivers/clk/clk.c

struct clk {
        struct clk_core *core;
        struct device *dev;
        const char *dev_id;
        const char *con_id;
        unsigned long min_rate;
        unsigned long max_rate;
        unsigned int exclusive_count;
        struct hlist_node clks_node;
};
  • *core
    • 클럭 코어 포인터
  • *dev
    • 디바이스 포인터
  • *dev_id
    • 디바이스 명
  • *con_id
    • connection id 문자열
  • min_rate
    • 최소 rate
  • max_rate
    • 최대 rate
  • exclusive_count
    • 베타적 사용 카운터
  • clks_node
    • 클럭 코어의 clks 리스트에 연결될 때 사용되는 노드

 

clk_core 구조체

drivers/clk/clk.c

struct clk_core {
        const char              *name;
        const struct clk_ops    *ops;
        struct clk_hw           *hw;
        struct module           *owner;
        struct device           *dev;
        struct device_node      *of_node;
        struct clk_core         *parent;
        struct clk_parent_map   *parents;
        u8                      num_parents;
        u8                      new_parent_index;
        unsigned long           rate;
        unsigned long           req_rate;
        unsigned long           new_rate;
        struct clk_core         *new_parent;
        struct clk_core         *new_child;
        unsigned long           flags;
        bool                    orphan;
        bool                    rpm_enabled;
        unsigned int            enable_count;
        unsigned int            prepare_count;
        unsigned int            protect_count;
        unsigned long           min_rate;
        unsigned long           max_rate;
        unsigned long           accuracy;
        int                     phase;
        struct clk_duty         duty;
        struct hlist_head       children;
        struct hlist_node       child_node;
        struct hlist_head       clks;
        unsigned int            notifier_count;
#ifdef CONFIG_DEBUG_FS
        struct dentry           *dentry;
        struct hlist_node       debug_node;
#endif
        struct kref             ref;
};
  • *name
    • 클럭명
  • *ops
    • 클럭 opearation 포인터
  • *hw
    • 클럭 hw 포인터
  • *owner
    • 모듈 포인터
  • *dev
    • 디바이스 포인터
  • of_node
    • 디바이스 노드 포인터
  • *parent
    • 부모 클럭 코어 포인터
  • *parents
    • 부모 클럭 맵 배열 포인터
  • num_parents
    • 부모 클럭 코어 수
    • 루트 클럭일 때 0이다.
  • new_parent_index
    • 새 부모 클럭 인덱스
  • rate
    • 현재 rate (클럭 hw가 지원하는 값)
  • req_rate
    • 요청 rate
  • new_rate
    • 변경될 새 rate
  • *new_parent
    • 변경될 새 부모 클럭 코어 포인터
  • *new_child
    • 변경될 새 child 클럭 코어 포인터
  • flags
    • 요청 플래그들
  • orphan
    • 고아 클럭 코어 여부
    • 참고로 부모가 고아 클럭 코어이면 연결된 자식 클럭 코어도 고아 상태이다.
  • rpm_enabled
    • 절전 기능 사용 여부
  • enable_count
    • clk_enable() 카운터 수
  • prepare_count
    • clk_prepare() 카운터 수
  • protect_count
    • protection 카운터 수
  • min_rate
    • 최소 rate
  • max_rate
    • 최대 rate
  • accuracy
    • 정확도
  • phase
    • 위상
  • duty
    • duty (pulse on 비율)
  • children
    • 자식 클럭 코어 리스트
    • clk_core->child_node들이 연결된다.
  • child_node
    • 부모 클럭 코어의 children 리스트에 연결할 때 사용되는 노드
  • clks
    • 클럭 Consumer 리스트
    • clk->clks_node들이 연결된다.
  • notifier_count
    • 통지 카운터
  • ref
    • 참조 카운터

 

clk_ops 구조체

drivers/clk/clk.c

/**
 * struct clk_ops -  Callback operations for hardware clocks; these are to
 * be provided by the clock implementation, and will be called by drivers
 * through the clk_* api.
 *
 * @prepare:    Prepare the clock for enabling. This must not return until
 *              the clock is fully prepared, and it's safe to call clk_enable.
 *              This callback is intended to allow clock implementations to
 *              do any initialisation that may sleep. Called with
 *              prepare_lock held.
 *
 * @unprepare:  Release the clock from its prepared state. This will typically
 *              undo any work done in the @prepare callback. Called with
 *              prepare_lock held.
 *
 * @is_prepared: Queries the hardware to determine if the clock is prepared.
 *              This function is allowed to sleep. Optional, if this op is not
 *              set then the prepare count will be used.
 *
 * @unprepare_unused: Unprepare the clock atomically.  Only called from
 *              clk_disable_unused for prepare clocks with special needs.
 *              Called with prepare mutex held. This function may sleep.
 *
 * @enable:     Enable the clock atomically. This must not return until the
 *              clock is generating a valid clock signal, usable by consumer
 *              devices. Called with enable_lock held. This function must not
 *              sleep.
 *
 * @disable:    Disable the clock atomically. Called with enable_lock held.
 *              This function must not sleep.
 *
 * @is_enabled: Queries the hardware to determine if the clock is enabled.
 *              This function must not sleep. Optional, if this op is not
 *              set then the enable count will be used.
 *
 * @disable_unused: Disable the clock atomically.  Only called from
 *              clk_disable_unused for gate clocks with special needs.
 *              Called with enable_lock held.  This function must not
 *              sleep.
 *
 * @save_context: Save the context of the clock in prepration for poweroff.
 *
 * @restore_context: Restore the context of the clock after a restoration
 *              of power.
 *
 * @recalc_rate Recalculate the rate of this clock, by querying hardware. The
 *              parent rate is an input parameter.  It is up to the caller to
 *              ensure that the prepare_mutex is held across this call.
 *              Returns the calculated rate.  Optional, but recommended - if
 *              this op is not set then clock rate will be initialized to 0.
 *
 * @round_rate: Given a target rate as input, returns the closest rate actually
 *              supported by the clock. The parent rate is an input/output
 *              parameter.
 *
 * @determine_rate: Given a target rate as input, returns the closest rate
 *              actually supported by the clock, and optionally the parent clock
 *              that should be used to provide the clock rate.
 *
 * @set_parent: Change the input source of this clock; for clocks with multiple
 *              possible parents specify a new parent by passing in the index
 *              as a u8 corresponding to the parent in either the .parent_names
 *              or .parents arrays.  This function in affect translates an
 *              array index into the value programmed into the hardware.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @get_parent: Queries the hardware to determine the parent of a clock.  The
 *              return value is a u8 which specifies the index corresponding to
 *              the parent clock.  This index can be applied to either the
 *              .parent_names or .parents arrays.  In short, this function
 *              translates the parent value read from hardware into an array
 *              index.  Currently only called when the clock is initialized by
 *              __clk_init.  This callback is mandatory for clocks with
 *              multiple parents.  It is optional (and unnecessary) for clocks
 *              with 0 or 1 parents.
 *
 * @set_rate:   Change the rate of this clock. The requested rate is specified
 *              by the second argument, which should typically be the return
 *              of .round_rate call.  The third argument gives the parent rate
 *              which is likely helpful for most .set_rate implementation.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @set_rate_and_parent: Change the rate and the parent of this clock. The
 *              requested rate is specified by the second argument, which
 *              should typically be the return of .round_rate call.  The
 *              third argument gives the parent rate which is likely helpful
 *              for most .set_rate_and_parent implementation. The fourth
 *              argument gives the parent index. This callback is optional (and
 *              unnecessary) for clocks with 0 or 1 parents as well as
 *              for clocks that can tolerate switching the rate and the parent
 *              separately via calls to .set_parent and .set_rate.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy
 *              is expressed in ppb (parts per billion). The parent accuracy is
 *              an input parameter.
 *              Returns the calculated accuracy.  Optional - if this op is not
 *              set then clock accuracy will be initialized to parent accuracy
 *              or 0 (perfect clock) if clock has no parent.
 *
 * @get_phase:  Queries the hardware to get the current phase of a clock.
 *              Returned values are 0-359 degrees on success, negative
 *              error codes on failure.
 *
 * @set_phase:  Shift the phase this clock signal in degrees specified
 *              by the second argument. Valid values for degrees are
 *              0-359. Return 0 on success, otherwise -EERROR.
 *
 * @get_duty_cycle: Queries the hardware to get the current duty cycle ratio
 *              of a clock. Returned values denominator cannot be 0 and must be
 *              superior or equal to the numerator.
 *
 * @set_duty_cycle: Apply the duty cycle ratio to this clock signal specified by
 *              the numerator (2nd argurment) and denominator (3rd  argument).
 *              Argument must be a valid ratio (denominator > 0
 *              and >= numerator) Return 0 on success, otherwise -EERROR.
 *
 * @init:       Perform platform-specific initialization magic.
 *              This is not not used by any of the basic clock types.
 *              Please consider other ways of solving initialization problems
 *              before using this callback, as its use is discouraged.
 *
 * @debug_init: Set up type-specific debugfs entries for this clock.  This
 *              is called once, after the debugfs directory entry for this
 *              clock has been created.  The dentry pointer representing that
 *              directory is provided as an argument.  Called with
 *              prepare_lock held.  Returns 0 on success, -EERROR otherwise.
 *
 *
 * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow
 * implementations to split any work between atomic (enable) and sleepable
 * (prepare) contexts.  If enabling a clock requires code that might sleep,
 * this must be done in clk_prepare.  Clock enable code that will never be
 * called in a sleepable context may be implemented in clk_enable.
 *
 * Typically, drivers will call clk_prepare when a clock may be needed later
 * (eg. when a device is opened), and clk_enable when the clock is actually
 * required (eg. from an interrupt). Note that clk_prepare MUST have been
 * called before clk_enable.
 */
struct clk_ops {
        int             (*prepare)(struct clk_hw *hw);
        void            (*unprepare)(struct clk_hw *hw);
        int             (*is_prepared)(struct clk_hw *hw);
        void            (*unprepare_unused)(struct clk_hw *hw);
        int             (*enable)(struct clk_hw *hw);
        void            (*disable)(struct clk_hw *hw);
        int             (*is_enabled)(struct clk_hw *hw);
        void            (*disable_unused)(struct clk_hw *hw);
        int             (*save_context)(struct clk_hw *hw);
        void            (*restore_context)(struct clk_hw *hw);
        unsigned long   (*recalc_rate)(struct clk_hw *hw,
                                        unsigned long parent_rate);
        long            (*round_rate)(struct clk_hw *hw, unsigned long rate,
                                        unsigned long *parent_rate);
        int             (*determine_rate)(struct clk_hw *hw,
                                          struct clk_rate_request *req);
        int             (*set_parent)(struct clk_hw *hw, u8 index);
        u8              (*get_parent)(struct clk_hw *hw);
        int             (*set_rate)(struct clk_hw *hw, unsigned long rate,
                                    unsigned long parent_rate);
        int             (*set_rate_and_parent)(struct clk_hw *hw,
                                    unsigned long rate,
                                    unsigned long parent_rate, u8 index);
        unsigned long   (*recalc_accuracy)(struct clk_hw *hw,
                                           unsigned long parent_accuracy);
        int             (*get_phase)(struct clk_hw *hw);
        int             (*set_phase)(struct clk_hw *hw, int degrees);
        int             (*get_duty_cycle)(struct clk_hw *hw,
                                          struct clk_duty *duty);
        int             (*set_duty_cycle)(struct clk_hw *hw,
                                          struct clk_duty *duty);
        void            (*init)(struct clk_hw *hw);
        void            (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
  • (*prepare)
    • 클럭이 출력되도록 prepare하는 후크 함수로 슬립할 수 있다.
    • 단 gated 클럭의 경우 enable까지 해야 클럭이 출력된다.
  • (*unprepare)
    • 출력 중인 클럭이 unprepare되도록하는 후크 함수로 슬립할 수 있다.
  • (*is_prepared)
    • 클럭이 준비되었는지 여부를 알려주는 후크 함수
  • (unprepare_unused)
    • 사용중이지 않은 클럭을 unprepre하는 후크 함수로 슬립할 수 있다.
  • (*enable)
    • atomic 하게 클럭의 enable(gate open)하는 후크 함수로 슬립하지 않는다.
  • (*disable)
    • atomic 하게 클럭을 disable(gate close)하는 후크 함수로 슬립하지 않는다.
  • (is_enabled)
    • 클럭의 enable 여부를 알려주는 후크 함수
  • (*disable_unused)
    • 사용중이지 않은 클럭을 atomic하게 disable하는 후크 함수
  • (*save_context)
    • power-off를 준비하기 위한 클럭의 context를 저장하는 후크 함수
  • (*restore_context)
    • 저장된 클럭 context를 읽어오는 후크 함수
  • (*recalc_rate)
    • rate 재산출 후크 함수
    • 부모 클럭 코어의 rate 변경 시 연동된다.
  • (*round_rate)
    • 요청한 rate에 대해 클럭 코어의 hw가 지원하는 가장 가까운 rate를 산출하는 후크 함수
    • rate (divider, multiplier, pll, ..) 류의 클럭 코어에서 사용된다.
  • (*determine_rate)
    • 요청한 rate에 대해 클럭 코어의 hw가 지원하는 가장 가까운 rate를 산출하는 후크 함수
    • mux, pll 류의 클럭 코어에서 rate가 조절될 때 사용된다.
  • (*set_parent)
    • 부모 클럭을 변경하는 후크 함수
    • mux 클럭에서 클럭 소스를 선택한다.
  • (*get_parent)
    • 연결된 부모 클럭에 대한 인덱스를 알아오는 후크 함수
  • (*set_rate)
    • rate 설정 후크 함수
  • (*set_rate_and_parent)
    • rate를 변경하고 부모 클럭을 변경하는 후크 함수
  • (*get_phase)
    • 위상 값을 알아오는 후크 함수
  • (*set_phase)
    • 위상 값을 설정하는 후크 함수
  • (*get_duty_cycle)
    • duty cycle 값을 알아오는 후크 함수
  • (*set_duty_cycle)
    • duty cycle 값을 설정하는 후크 함수
  • (*init)
    • 클럭 코어의 초기화 후크 함수

 

clk_hw 구조체

drivers/clk/clk.c

/**
 * struct clk_hw - handle for traversing from a struct clk to its corresponding
 * hardware-specific structure.  struct clk_hw should be declared within struct
 * clk_foo and then referenced by the struct clk instance that uses struct
 * clk_foo's clk_ops
 *
 * @core: pointer to the struct clk_core instance that points back to this
 * struct clk_hw instance
 *
 * @clk: pointer to the per-user struct clk instance that can be used to call
 * into the clk API
 *
 * @init: pointer to struct clk_init_data that contains the init data shared
 * with the common clock framework. This pointer will be set to NULL once
 * a clk_register() variant is called on this clk_hw pointer.
 */
struct clk_hw {
        struct clk_core *core;
        struct clk *clk;
        const struct clk_init_data *init;
};
  • *core
    • 클럭 코어 포인터
  • *clk
    • 클럭 포인터
  • *init
    • 클럭 초기화 데이터

 

clk_parent_data 구조체

drivers/clk/clk.c

/**
 * struct clk_parent_data - clk parent information
 * @hw: parent clk_hw pointer (used for clk providers with internal clks)
 * @fw_name: parent name local to provider registering clk
 * @name: globally unique parent name (used as a fallback)
 * @index: parent index local to provider registering clk (if @fw_name absent)
 */
struct clk_parent_data {
        const struct clk_hw     *hw;
        const char              *fw_name;
        const char              *name;
        int                     index;
};
  • *hw
    • 부코 클럭 hw 포인터
  • *fw_name
    • 부모 클럭 provider 명
  • *name
    • 유니크한 부모 클럭 명
  • index
    • 부모 클럭 코어에 대한 인덱스

 

참고

 

 

Timer -1- (Lowres Timer)

<kernel v5.4>

Lowres(Low Resolution) Timer

커널에서 사용하는 jifffies 기반의 타이머 tick을 소프트웨어 기법으로 구현한 타이머이다. 커널이 타이머를 요청할 때 만료 시간을 기재하는데 lowres 타이머는 정확하게 그 만료시점에 깨어나는 것을 보장하지 못하고 요청한 만료 시간의 최대 1/8(12.5%)의 지연된 오차를 가진다. 그 외 특징으로 lowres 타이머는 특정 아키텍처와 무관한 구조이다.

 

새로운 non-cascading wheel 구조 (v4.8~)

lowres 타이머는 커널 v4.8-rc1 에서 새로운 변화를 보여주면서 lowres 타이머에 대한 전반적인 설계가 변경되어 non-cascading wheel 모델이 소개되었다.

  • 타이머 휠에 등록된 타이머들은 cascading 하는 작업이 없어졌으므로 이로 인한 오버헤드가 줄었다.
  • fast lookup: 만료 타이머를 찾기 위한 lookup이 빨라졌다.
  • slack 관련한 동작과 API들도 더 이상 필요없게되어 삭제되었다.
  • 직전 implementation한 로직과 유사하게 요청한 만료 시각에 오차가 발생하는데, 최대 약 1/8(12.5%) 지연된 오차를 가진다.
    • 예) 40시간 후에 타이머를 동작시키게 하였지만 4.6시간 더 오래 걸려 타이머가 동작한다.
  • 참고:

 

타이머 리스트

admin 권한으로 타이머 리스트를 보려면 다음과 같이 한다. (커널 v4.8~)

$ cat /proc/timer_list
Timer List Version: v0.8
HRTIMER_MAX_CLOCK_BASES: 8
now at 79245435304 nsecs

cpu: 0
 clock 0:
  .base:       (____ptrval____)
  .index:      0
  .resolution: 1 nsecs
  .get_time:   ktime_get
  .offset:     0 nsecs
active timers:
 #0: <(____ptrval____)>, tick_sched_timer, S:01
 # expires at 79260000000-79260000000 nsecs [in 14564696 to 14564696 nsecs]
 #1: <(____ptrval____)>, it_real_fn, S:01
 # expires at 79374561805-79374561805 nsecs [in 129126501 to 129126501 nsecs]
 #2: <(____ptrval____)>, hrtimer_wakeup, S:01
 # expires at 77374835623-79874835623 nsecs [in -1870599681 to 629400319 nsecs]
...

 


타이머 및 벡터 관리 구조

두 개의 타이머 베이스

다음 그림과 같이 nohz에서 사용하는 타이머 베이스까지 두 개로 나뉘어 관리된다. 스케줄틱이 발생할 때마다 두 개의 타이머 베이스의 만료된 타이머들을 깨워 콜백함수를 호출한다.

 

다음 그림은 타이머들이 해시 벡터리스트로 구현되어 처리되는 모습을 보여준다. 100hz 시스템의 경우 최대 8 단계, 그 외의 시스템은 최대 9 단계 레벨로 구성된다.

  • 각 단계의 단위 틱은 2^(lvl*3) 이다. 즉 레벨 0는 1틱, 레벨 1은 8틱, 레벨 2는 64틱이다.
  • 스케줄 틱마다 base->clk의 값도 증가된다. 이 값으로 각 레벨의 해시 인덱스를 산출하고 이 인덱스 값에 따른 타이머들을 깨워 호출한다.

 

timer_list 구조체

include/linux/timer.h

struct timer_list {
        /*
         * All fields that change during normal runtime grouped to the
         * same cacheline
         */
        struct hlist_node       entry;
        unsigned long           expires;
        void                    (*function)(struct timer_list *);
        u32                     flags;

#ifdef CONFIG_LOCKDEP
        struct lockdep_map      lockdep_map;
#endif
};

타이머마다 만료 시각과 호출될 콜백 함수 정보가 담겨있다.

  •  entry
    • 동적 타이머들을 타이머 벡터 리스트로 연결 시 사용한다.
  • expires
    • 타이머가 만료될 미래의 jiffies 시점을 지정한다.
  • function
    • 타이머 만료 시 실행할 함수의 주소를 저장한다.
  • flags
    • TIMER_CPUMASK
      • 타이머 휠 인덱스  값으로 10비트를 사용하여 저장된다.
    • TIMER_MIGRATING
      • migration 진행중인 타이머 여부 (타이머 cpu 이동중)
    • TIMER_DEFERRABLE
      • 지연 타이머 여부
      • deferrable 전용 타이머 베이스를 사용한다.
    • TIMER_PINNED
      • cpu 고정(pinned) 타이머 여부
    • TIMER_IRQSAFE
      • 타이머가 인터럽트 핸들러에서 사용되지 않음을 나타낸다.
      • 인터럽트 latency 성능을 위해 추가되는 플래그로 내부에서 spinlock 시 인터럽트를 disable 하지 않는다.

 

timer_base 구조체

tvec_base 구조체명이 timer_base 구조체명으로 변경되었다.

kernel/time/timer.c

struct timer_base {
        raw_spinlock_t          lock;
        struct timer_list       *running_timer;
#ifdef CONFIG_PREEMPT_RT
        spinlock_t              expiry_lock;
        atomic_t                timer_waiters;
#endif
        unsigned long           clk;
        unsigned long           next_expiry;
        unsigned int            cpu;
        bool                    is_idle;
        bool                    must_forward_clk;
        DECLARE_BITMAP(pending_map, WHEEL_SIZE);
        struct hlist_head       vectors[WHEEL_SIZE];
} ____cacheline_aligned;

타이머들이 관리되는 타이머 베이스는 per-cpu로 관리되며, 타이머 리스트는 해시 벡터 휠로 관리된다.

  •  lock
    • 타이머 벡터 리스트 조작시 사용할 lock
  • running_timer
    • 타이머가 만료되어 현재 함수를 실행중인 타이머를 가리킨다.
  • clk
    • 처리할 시각(틱)이다. nohz 상태에 따라 동작이 상이하다.
      • nohz가 동작하지 않는 경우 이 시각은 현재 시각(jiffies)과 동일한 값이 되도록 매 스케줄 틱마다 증가된다.
      • nohz가 동작하는 경우에는 jiffies 보다 지연될 수 있다. cpu가 nohz에서 탈출할 때 지연된 시간만큼 처리한다.
        • 지연된 시간만큼 루프를 반복하며 틱을 처리하면 성능이 떨어지므로 nohz optimization 기법을 사용하여 처리하지 않아도 되는 틱은 생략하게 한다.
  • next_expiry
    • 다음 만료될 타이머의 시각(틱)
  • cpu
    • 현재 타이머 벡터 관리가 동작하는 cpu id
  • is_idle
    • 타이머 베이스의 idle 상태
  • must_forward_clk
    • nohz에서 타이머 베이스의 clk를 forward 한다. (nohz optimization)
  • pending_map
    • 펜딩 비트맵으로 타이머 휠 인덱스마다 1비트를 사용한다.
    • 타이머 휠의 인덱스 비트가 1로 설정되면 타이머가 1 개 이상이 대기중임을 의미한다.
  • vectors[]
    • 타이머들이 대기하는 타이머 휠 리스트이며, 해시 벡터로 구현되었다.

 

timer_bases[]

kernel/time/timer.c

static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);

timer_base는 성능향상을 위해 per-cpu를 사용하여 관리한다. nohz를 사용하지 않으면 하나의 타이머 베이스에 관리된다. 그러나 nohz를 사용하는 경우 다음과 같이 두 개의 타이머 휠로 나누어 관리된다.

  • BASE_STD
    • 기본(standard) 타이머를 위해 사용된다.
  • BASE_DEF
    • defferable 타이머를 위해 사용된다.

 

jiffies & 초기값

jiffies 값은 스케줄 틱이 발생할 때 마다 1씩 증가한다. 스케줄 틱은 CONFIG_HZ에 정한 시간에 한 번씩 발생한다.

  • 예) 100hz 시스템인 경우 1초에 100번의 스케줄 틱이 발생한다. 따라서 10ms 주기마다 1 tick이 발생하고 jiffies 값도 1씩 증가된다.

 

INITIAL_JIFFIES

include/linux/jiffies.h

/*
 * Have the 32 bit jiffies value wrap 5 minutes after boot
 * so jiffies wrap bugs show up earlier.
 */
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))

jiffies 초기값은 32bit 시스템에서 부트 후 약 5분이내에 overflow 될 수 있는 값을 주었다. 64비트 시스템에서는 overflow되려면 어마 어마한 시간이 흘러야 하므로 overflow될 걱정이 없다.

  • 초기값
    • 32bit 예) HZ=250
      • 0xfffe_db08 (-75000)
    • 64bit 예) HZ=250
      • 0x0000_0000_fffe_db08

 


APIs

주요 API

컴파일 타임에 정적으로 타이머 생성 및 초기화

  • DEFINE_TIMER()

런타임에 동적으로 타이머 생성 및 초기화

  • timer_setup()
  • timer_setup_on_stack()

타이머 베이스에 타이머 추가/변경/삭제

  • add_timer()
  • mod_timer()
  • del_timer()

 

시간 비교 API

jiffies 값을 읽어 시간을 직접 비교하는 경우 jiffies overflow 되는 시점에서 시간 비교가 의도치 않는 반대의 결과를 얻을 수 있다. 따라서 다음 함수들을 사용하여야 정확한 결과를 나타내게 할 수 있으므로 절대 jiffies 시간을 직접 비교 사용하는 일이 없도록 해야 한다.

  • 2개 시간 비교 함수
    • time_before(a,b)
      • a가 b 보다 먼저 시간값이면 true
    • time_after(a,b)
      • a가 b 보다 나중 시간값이면 true
    • time_before_eq(a,b)
      • a 가 b보다 먼저이거나 같은 시간이면 true
    • time_after_eq(a,b)
      • a 가 b 보다 나중이거나 같은 시간이면 true
  • jiffies와 시간 비교 함수
    • time_is_before_jiffies(a)
      • a가 jiffies 보다 먼저 시간값이면 true
    • time_is_after_jiffies(a)
      • a가 jiffies 보다 나중 시간값이면 true
    • time_is_before_eq_jiffies(a)
      • a 가 jiffies 보다 먼저이거나 같은 시간이면 true
    • time_is_after_eq_jiffies(a)
      • a 가 jiffies 보다 나중이거나 같은 시간이면 true

 

관련 API

  • msleep()
  • msleep_interruptible()
  • schedule_timeout_interruptible()
  • schedule_timeout_killable()
  • schedule_timeout_uninterruptible()
  • schedule_timeout_idle()
  • schedule_timeout()

 


타이머 추가/삭제

타이머 추가

add_timer()

kernel/time/timer.c

/**
 * add_timer - start a timer
 * @timer: the timer to be added
 *
 * The kernel will do a ->function(->data) callback from the
 * timer interrupt at the ->expires point in the future. The
 * current time is 'jiffies'.
 *
 * The timer's ->expires, ->function (and if the handler uses it, ->data)
 * fields must be set prior calling this function.
 *
 * Timers with an ->expires field in the past will be executed in the next
 * timer tick.
 */
void add_timer(struct timer_list *timer)
{
        BUG_ON(timer_pending(timer));
        mod_timer(timer, timer->expires);
}
EXPORT_SYMBOL(add_timer);

동적 타이머를 요청한다.

  • mod_timer()를 호출하여 만료 시간을 변경한다.

 

타이머 삭제

del_timer()

kernel/time/timer.c

/**
 * del_timer - deactive a timer.
 * @timer: the timer to be deactivated
 *
 * del_timer() deactivates a timer - this works on both active and inactive
 * timers.
 *
 * The function returns whether it has deactivated a pending timer or not.
 * (ie. del_timer() of an inactive timer returns 0, del_timer() of an
 * active timer returns 1.)
 */
int del_timer(struct timer_list *timer)
{
        struct tvec_base *base;
        unsigned long flags;
        int ret = 0;

        debug_assert_init(timer);

        if (timer_pending(timer)) {
                base = lock_timer_base(timer, &flags);
                ret = detach_if_pending(timer, base, true);
                spin_unlock_irqrestore(&base->lock, flags);
        }

        return ret;
}
EXPORT_SYMBOL(del_timer);

타이머를 타이머 베이스에서 제거하여 비활성화한다.

  • 코드 라인 7에서 타이머에서 사용자 트래킹 정보를 클리어한다.
  • 코드 라인 9~13에서 타이머 벡터 리스트에 등록되어 대기중인 타이머인 경우 그 리스트에서 제거한다.
  • 코드 라인 15에서 타이머가 활성화 상태였었으면 1을 반환하고, 그렇지 않은 경우 0을 반환한다.

 

펜딩 타이머 제거

detach_if_pending()

kernel/time/timer.c

static int detach_if_pending(struct timer_list *timer, struct timer_base *base,
                             bool clear_pending)
{
        unsigned idx = timer_get_idx(timer);

        if (!timer_pending(timer))
                return 0;

        if (hlist_is_singular_node(&timer->entry, base->vectors + idx))
                __clear_bit(idx, base->pending_map);

        detach_timer(timer, clear_pending);
        return 1;
}

펜딩된 타이머인 경우 리스트에서 제거한다. 활동(pending) 중인 타이머를 제거한 경우 1을 반환한다.

  • 코드 라인 6~7에서 타이머 벡터 리스트에 등록되어 대기하고 있는 타이머가 아닌 경우 이미 deactivate된 경우이므로 0을 반환한다.
  • 코드 라인 9~10에서 타이머의 인덱스에 해당하는 타이머 벡터 리스트에 자신 1건만 등록된 경우라면 해당 인덱스의 펜딩맵을 클리어한다.
  • 코드 라인 12~13에서 타이머를 타이머 베이스 휠에서 제거하고 1을 반환한다.

 

타이머 제거

detach_timer()

kernel/time/timer.c

static inline void detach_timer(struct timer_list *timer, bool clear_pending)
{
        struct list_head *entry = &timer->entry;

        debug_deactivate(timer);

        __hlist_del(entry);
        if (clear_pending)
                entry->pprev = NULL;
        entry->next = LIST_POISON2;
}

타이머 리스트에서 요청 타이머를 제거한다. 만일 clear pending 요청이 있는 경우 타이머 엔트리가 다음 타이머와 연결되지 않도록 분리한다.

 


타이머 변경

mod_timer()

kernel/time/timer.c

/**
 * mod_timer - modify a timer's timeout
 * @timer: the timer to be modified
 * @expires: new timeout in jiffies
 *
 * mod_timer() is a more efficient way to update the expire field of an
 * active timer (if the timer is inactive it will be activated)
 *
 * mod_timer(timer, expires) is equivalent to:
 *
 *     del_timer(timer); timer->expires = expires; add_timer(timer);
 *
 * Note that if there are multiple unserialized concurrent users of the
 * same timer, then mod_timer() is the only safe way to modify the timeout,
 * since add_timer() cannot modify an already running timer.
 *
 * The function returns whether it has modified a pending timer or not.
 * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
 * active timer returns 1.)
 */
int mod_timer(struct timer_list *timer, unsigned long expires)
{
        return __mod_timer(timer, expires, 0);
}
EXPORT_SYMBOL(mod_timer);

요청한 타이머를 제거하고 인수로 받은 만료 시점(timerout을 적용한 새 jiffies 시점)으로 조절하고 설정한다. inactive된 타이머도 active 시킨다.

 

__mod_timer()

kernel/time/timer.c

static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options)
{
        struct timer_base *base, *new_base;
        unsigned int idx = UINT_MAX;
        unsigned long clk = 0, flags;
        int ret = 0;

        BUG_ON(!timer->function);

        /*
         * This is a common optimization triggered by the networking code - if
         * the timer is re-modified to have the same timeout or ends up in the
         * same array bucket then just return:
         */
        if (timer_pending(timer)) {
                /*
                 * The downside of this optimization is that it can result in
                 * larger granularity than you would get from adding a new
                 * timer with this expiry.
                 */
                long diff = timer->expires - expires;

                if (!diff)
                        return 1;
                if (options & MOD_TIMER_REDUCE && diff <= 0)
                        return 1;

                /*
                 * We lock timer base and calculate the bucket index right
                 * here. If the timer ends up in the same bucket, then we
                 * just update the expiry time and avoid the whole
                 * dequeue/enqueue dance.
                 */
                base = lock_timer_base(timer, &flags);
                forward_timer_base(base);

                if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) &&
                    time_before_eq(timer->expires, expires)) {
                        ret = 1;
                        goto out_unlock;
                }

                clk = base->clk;
                idx = calc_wheel_index(expires, clk);

                /*
                 * Retrieve and compare the array index of the pending
                 * timer. If it matches set the expiry to the new value so a
                 * subsequent call will exit in the expires check above.
                 */
                if (idx == timer_get_idx(timer)) {
                        if (!(options & MOD_TIMER_REDUCE))
                                timer->expires = expires;
                        else if (time_after(timer->expires, expires))
                                timer->expires = expires;
                        ret = 1;
                        goto out_unlock;
                }
        } else {
                base = lock_timer_base(timer, &flags);
                forward_timer_base(base);
        }

요청한 타이머를 제거하고 최종 결정된 만료 시점으로 타이머를 설정한 후 다시 추가 하고 active 시킨다. 타이머가 active된 상태이면 1로 반환된다.

  • 코드 라인 16에서 타이머가 expire 되지 않고 아직 타이머 휠에서 기다리고 있는 중이다.
  • 코드 라인 22~27에서 새로운 만료 시각(@expires)으로 변경을 하려할 때 차이가 없으면 1을 결과로 함수를 빠져나간다.
    • 만일 만료 시각이 앞당겨졌는데 만료 시각을 앞으로 당기지 못하게 한 MOD_TIMER_REDUCE 옵션을 사용한 경우도 결과를 1로 함수를 빠져나간다.
  • 코드 라인 35~45에서 타이머가 등록된 타이머 베이스에서 산출한 타이머 휠 인덱스를 알아온다.
    • lock을 획득 한 후 타이머 베이스의 시각(clk)을 forward 하도록 갱신하고, 다시 한번 체크한다. 만료 시각이 앞당겨졌는데 만료 시각을 앞으로 당기지 못하게 한 MOD_TIMER_REDUCE 옵션을 사용한 경우도 결과를 1로 함수를 빠져나간다.
  • 코드 라인 52~59에서 타이머가 등록된 타이머 베이스에서 해당 타이머 휠 인덱스와 위에서 산출한 휠 인덱스가 동일하면 만료 시각을 갱신하고 결과를 1로 변경하고 함수를 빠져나간다.
  • 코드 라인 60~63에서 요청한 타이머가 타이머 휠에 없는 경우 타이머를 위한 타이머 베이스를 준비하고, 타이머 베이스의 시각(clk)을 forward 하도록 갱신한다.

 

        ret = detach_if_pending(timer, base, false);
        if (!ret && (options & MOD_TIMER_PENDING_ONLY))
                goto out_unlock;

        new_base = get_target_base(base, timer->flags);

        if (base != new_base) {
                /*
                 * We are trying to schedule the timer on the new base.
                 * However we can't change timer's base while it is running,
                 * otherwise del_timer_sync() can't detect that the timer's
                 * handler yet has not finished. This also guarantees that the
                 * timer is serialized wrt itself.
                 */
                if (likely(base->running_timer != timer)) {
                        /* See the comment in lock_timer_base() */
                        timer->flags |= TIMER_MIGRATING;

                        raw_spin_unlock(&base->lock);
                        base = new_base;
                        raw_spin_lock(&base->lock);
                        WRITE_ONCE(timer->flags,
                                   (timer->flags & ~TIMER_BASEMASK) | base->cpu);
                        forward_timer_base(base);
                }
        }

        debug_timer_activate(timer);

        timer->expires = expires;
        /*
         * If 'idx' was calculated above and the base time did not advance
         * between calculating 'idx' and possibly switching the base, only
         * enqueue_timer() and trigger_dyntick_cpu() is required. Otherwise
         * we need to (re)calculate the wheel index via
         * internal_add_timer().
         */
        if (idx != UINT_MAX && clk == base->clk) {
                enqueue_timer(base, timer, idx);
                trigger_dyntick_cpu(base, timer);
        } else {
                internal_add_timer(base, timer);
        }

out_unlock:
        raw_spin_unlock_irqrestore(&base->lock, flags);

        return ret;
}
  • 코드 라인 1~3에서 pending된 타이머인 경우에 한해 해당 타이머 벡터 리스트에서 제거한다. 실패 시 pending only인 경우 처리를 중단하고 빠져나간다.
  • 코드 라인 5~26에서 타이머의 cpu 변경이 필요한 경우 migrating을 한다.
  • 코드 라인 30~43에서 타이머의 만료 시점을 갱신하고 적절한 타이머 벡터 리스트에 새로 추가한다.

 

다음 그림은 타이머를 추가 또는 변경 시 호출되는 __mod_timer() 함수를 통해 타이머 베이스의 벡터 리스트에 타이머가 추가되는 모습을 보여준다.

  • 레벨이 올라갈수록 만료 시각에 대한 정확도는 레벨 당 8배 단위로 커지는 Granularity 값만큼 비례하여 떨어진다. 레벨 0의 오차가 8틱 이하였지만, 그 다음 레벨 1은 8배 커진 64틱 이하인것을 확인할 수 있다.

 

타이머 추가(internal)

internal_add_timer()

kernel/time/timer.c

static void
internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
        __internal_add_timer(base, timer);
        trigger_dyntick_cpu(base, timer);
}

요청 타이머 베이스에 타이머를 추가한다.

 

__internal_add_timer()

kernel/time/timer.c

static void
__internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
        unsigned int idx;

        idx = calc_wheel_index(timer->expires, base->clk);
        enqueue_timer(base, timer, idx);
}

요청 타이머 베이스에 타이머를 추가한다.

  • 코드 라인 5에서 타이머 베이스에서 사용할 타이머 휠의 인덱스 값을 알아온다.
  • 코드 라인 6에서 산출한 인덱스의 테이머 벡터 리스트에 타이머를 추가한다.
    • vectors[idx] 리스트 <— 타이머 추가

 

enqueue_timer()

kernel/time/timer.c

/*
 * Enqueue the timer into the hash bucket, mark it pending in
 * the bitmap and store the index in the timer flags.
 */
static void enqueue_timer(struct timer_base *base, struct timer_list *timer,
                          unsigned int idx)
{
        hlist_add_head(&timer->entry, base->vectors + idx);
        __set_bit(idx, base->pending_map);
        timer_set_idx(timer, idx);

        trace_timer_start(timer, timer->expires, timer->flags);
}

요청 타이머 베이스에서 인덱스에 해당하는 타이머 벡터 리스트에 타이머를 추가한다.

  • 코드 라인 4에서 요청 인덱스에 해당하는 타이머 벡터 리스트에 타이머를 추가한다.
  • 코드 라인 5에서 펜딩 맵의 인덱스에 해당하는 비트를 설정한다.
  • 코드 라인 6에서 타이머의 플래그 중 10비트 공간을 사용하여 인덱스를 기록한다.

 

timer_set_idx()

kernel/time/timer.c

static inline void timer_set_idx(struct timer_list *timer, unsigned int idx)
{
        timer->flags = (timer->flags & ~TIMER_ARRAYMASK) |
                        idx << TIMER_ARRAYSHIFT;
}

타이머의 플래그 bits[31..22]에 인덱스를 기록한다. (10 비트)

 

timer_pending()

include/linux/timer.h

/**
 * timer_pending - is a timer pending?
 * @timer: the timer in question
 *
 * timer_pending will tell whether a given timer is currently pending,
 * or not. Callers must ensure serialization wrt. other operations done
 * to this timer, eg. interrupt contexts, or other CPUs on SMP.
 *              
 * return value: 1 if the timer is pending, 0 if not.
 */     
static inline int timer_pending(const struct timer_list * timer)
{       
        return timer->entry.pprev != NULL;
}

타이머 벡터 리스트에서 대기중인 타이머인지 여부를 반환한다.

  • 타이머 벡터 리스트는 환형 더블리스트이므로 타이머가 혼자 등록되어 있어도 head와 연결되는 구조이다.  따라서 null이 들어가 있는 경우는 환형 더블리스트에서 제거된 경우밖에 없다. 즉 리스트에  존재하면 항상 true이다.

 

no-hz용 타이머 베이스 시각 forward

forward_timer_base()

kernel/time/timer.c

static inline void forward_timer_base(struct timer_base *base)
{
#ifdef CONFIG_NO_HZ_COMMON
        unsigned long jnow;

        /*
         * We only forward the base when we are idle or have just come out of
         * idle (must_forward_clk logic), and have a delta between base clock
         * and jiffies. In the common case, run_timers will take care of it.
         */
        if (likely(!base->must_forward_clk))
                return;

        jnow = READ_ONCE(jiffies);
        base->must_forward_clk = base->is_idle;
        if ((long)(jnow - base->clk) < 2)
                return;

        /*
         * If the next expiry value is > jiffies, then we fast forward to
         * jiffies otherwise we forward to the next expiry value.
         */
        if (time_after(base->next_expiry, jnow))
                base->clk = jnow;
        else
                base->clk = base->next_expiry;
#endif
}

nohz를 위해 현재 요청한 타이머 베이스의 시각을 forward 한다.

  • 코드 라인 11~12에서 nohz를 위해 타이머 베이스가 idle 상태인 경우 타이머 베이스를 forward할 수 있게 하였다. 그 경우가 아니면 함수를 빠져나간다. 다음 함수들에서 must_forward_clk 플래그를 1로 만든다.
    • timers_prepare_cpu()
    • get_next_timer_interrupt()
  • 코드 라인 14~17에서 must_forward_clk 값을 idle 상태에서만 사용할 수 있게 지정한다. 타이머 베이스가 현재 틱과 비교하여 2틱 미만인 경우 함수를 빠져나간다.
  • 코드 라인 23~26에서 만료 시각이 아직 남아 있는 경우 타이머 베이스의 시각에 현재 시각을 대입한다. 만일 만료 시각이 지났으면 다음 만료 시각으로 갱신한다.

 


휠 인덱스 산출

calc_wheel_index()

kernel/time/timer.c

static int calc_wheel_index(unsigned long expires, unsigned long clk)
{
        unsigned long delta = expires - clk;
        unsigned int idx;

        if (delta < LVL_START(1)) {
                idx = calc_index(expires, 0);
        } else if (delta < LVL_START(2)) {
                idx = calc_index(expires, 1);
        } else if (delta < LVL_START(3)) {
                idx = calc_index(expires, 2);
        } else if (delta < LVL_START(4)) {
                idx = calc_index(expires, 3);
        } else if (delta < LVL_START(5)) {
                idx = calc_index(expires, 4);
        } else if (delta < LVL_START(6)) {
                idx = calc_index(expires, 5);
        } else if (delta < LVL_START(7)) {
                idx = calc_index(expires, 6);
        } else if (LVL_DEPTH > 8 && delta < LVL_START(8)) {
                idx = calc_index(expires, 7);
        } else if ((long) delta < 0) {
                idx = clk & LVL_MASK;
        } else {
                /*
                 * Force expire obscene large timeouts to expire at the
                 * capacity limit of the wheel.
                 */
                if (expires >= WHEEL_TIMEOUT_CUTOFF)
                        expires = WHEEL_TIMEOUT_MAX;

                idx = calc_index(expires, LVL_DEPTH - 1);
        }
        return idx;
}

만료 시각과 클럭 베이스의 시각의 차이로 타이머 휠 인덱스 값을 산출한다.

  • 코드 라인 3에서 만료 시각과 클럭 베이스의 시각의 차이 틱을 구해  delta에 대입한다.
  • 코드 라인 6~7에서 delta 값이 1 레벨 시작 틱 값보다 작은 경우 0 레벨 기준으로 휠 인덱스를 산출한다.
  • 코드 라인 8~21에서 delta 값을 사용하여 1~7레벨까지를 기준으로 휠 인덱스를 산출한다.
  • 코드 라인 22~23에서 0보다 작은 delta 값에 해당하는 휠 인덱스를 산출한다.
  • 코드 라인 24~33에서 범위를 초과하는 delta 값인 경우 최대 값으로 휠 인덱스를 산출한다.
  • 코드 라인 34에서 산출한 휠 인덱스를 반환한다.

 

calc_index()

kernel/time/timer.c

/*
 * Helper function to calculate the array index for a given expiry
 * time.
 */
static inline unsigned calc_index(unsigned expires, unsigned lvl)
{
        expires = (expires + LVL_GRAN(lvl)) >> LVL_SHIFT(lvl);
        return LVL_OFFS(lvl) + (expires & LVL_MASK);
}

만료 시각과 레벨로 타이머 휠 인덱스를 알아온다.

  • 예) expires=0x43c0, lvl=2
    • new expires = (0x43c0 + 0x40) >> 6 = 0x110
    • return 0x80 + 0x10 = 0x90 (144)

 

매크로 함수들

kernel/time/timer.c

/*
 * The timer wheel has LVL_DEPTH array levels. Each level provides an array of
 * LVL_SIZE buckets. Each level is driven by its own clock and therefor each
 * level has a different granularity.
 *
 * The level granularity is:            LVL_CLK_DIV ^ lvl
 * The level clock frequency is:        HZ / (LVL_CLK_DIV ^ level)
 *
 * The array level of a newly armed timer depends on the relative expiry
 * time. The farther the expiry time is away the higher the array level and
 * therefor the granularity becomes.
 *
 * Contrary to the original timer wheel implementation, which aims for 'exact'
 * expiry of the timers, this implementation removes the need for recascading
 * the timers into the lower array levels. The previous 'classic' timer wheel
 * implementation of the kernel already violated the 'exact' expiry by adding
 * slack to the expiry time to provide batched expiration. The granularity
 * levels provide implicit batching.
 *
 * This is an optimization of the original timer wheel implementation for the
 * majority of the timer wheel use cases: timeouts. The vast majority of
 * timeout timers (networking, disk I/O ...) are canceled before expiry. If
 * the timeout expires it indicates that normal operation is disturbed, so it
 * does not matter much whether the timeout comes with a slight delay.
 *
 * The only exception to this are networking timers with a small expiry
 * time. They rely on the granularity. Those fit into the first wheel level,
 * which has HZ granularity.
 *
 * We don't have cascading anymore. timers with a expiry time above the
 * capacity of the last wheel level are force expired at the maximum timeout
 * value of the last wheel level. From data sampling we know that the maximum
 * value observed is 5 days (network connection tracking), so this should not
 * be an issue.
 *
 * The currently chosen array constants values are a good compromise between
 * array size and granularity.
 *
 * This results in the following granularity and range levels:
 *
 * HZ 1000 steps
 * Level Offset  Granularity            Range
 *  0      0         1 ms                0 ms -         63 ms
 *  1     64         8 ms               64 ms -        511 ms
 *  2    128        64 ms              512 ms -       4095 ms (512ms - ~4s)
 *  3    192       512 ms             4096 ms -      32767 ms (~4s - ~32s)
 *  4    256      4096 ms (~4s)      32768 ms -     262143 ms (~32s - ~4m)
 *  5    320     32768 ms (~32s)    262144 ms -    2097151 ms (~4m - ~34m)
 *  6    384    262144 ms (~4m)    2097152 ms -   16777215 ms (~34m - ~4h)
 *  7    448   2097152 ms (~34m)  16777216 ms -  134217727 ms (~4h - ~1d)
 *  8    512  16777216 ms (~4h)  134217728 ms - 1073741822 ms (~1d - ~12d)
 *
 * HZ  300
 * Level Offset  Granularity            Range
 *  0      0         3 ms                0 ms -        210 ms
 *  1     64        26 ms              213 ms -       1703 ms (213ms - ~1s)
 *  2    128       213 ms             1706 ms -      13650 ms (~1s - ~13s)
 *  3    192      1706 ms (~1s)      13653 ms -     109223 ms (~13s - ~1m)
 *  4    256     13653 ms (~13s)    109226 ms -     873810 ms (~1m - ~14m)
 *  5    320    109226 ms (~1m)     873813 ms -    6990503 ms (~14m - ~1h)
 *  6    384    873813 ms (~14m)   6990506 ms -   55924050 ms (~1h - ~15h)
 *  7    448   6990506 ms (~1h)   55924053 ms -  447392423 ms (~15h - ~5d)
 *  8    512  55924053 ms (~15h) 447392426 ms - 3579139406 ms (~5d - ~41d)
 *
 * HZ  250
 * Level Offset  Granularity            Range
 *  0      0         4 ms                0 ms -        255 ms
 *  1     64        32 ms              256 ms -       2047 ms (256ms - ~2s)
 *  2    128       256 ms             2048 ms -      16383 ms (~2s - ~16s)
 *  3    192      2048 ms (~2s)      16384 ms -     131071 ms (~16s - ~2m)
 *  4    256     16384 ms (~16s)    131072 ms -    1048575 ms (~2m - ~17m)
 *  5    320    131072 ms (~2m)    1048576 ms -    8388607 ms (~17m - ~2h)
 *  6    384   1048576 ms (~17m)   8388608 ms -   67108863 ms (~2h - ~18h)
 *  7    448   8388608 ms (~2h)   67108864 ms -  536870911 ms (~18h - ~6d)
 *  8    512  67108864 ms (~18h) 536870912 ms - 4294967288 ms (~6d - ~49d)
 *
 * HZ  100
 * Level Offset  Granularity            Range
 *  0      0         10 ms               0 ms -        630 ms
 *  1     64         80 ms             640 ms -       5110 ms (640ms - ~5s)
 *  2    128        640 ms            5120 ms -      40950 ms (~5s - ~40s)
 *  3    192       5120 ms (~5s)     40960 ms -     327670 ms (~40s - ~5m)
 *  4    256      40960 ms (~40s)   327680 ms -    2621430 ms (~5m - ~43m)
 *  5    320     327680 ms (~5m)   2621440 ms -   20971510 ms (~43m - ~5h)
 *  6    384    2621440 ms (~43m) 20971520 ms -  167772150 ms (~5h - ~1d)
 *  7    448   20971520 ms (~5h) 167772160 ms - 1342177270 ms (~1d - ~15d)
 */

 

LVL_SHIFT() & LVL_GRAN()

kernel/time/timer.c

/* Clock divisor for the next level */
#define LVL_CLK_SHIFT   3
#define LVL_CLK_DIV     (1UL << LVL_CLK_SHIFT)
#define LVL_CLK_MASK    (LVL_CLK_DIV - 1)
#define LVL_SHIFT(n)    ((n) * LVL_CLK_SHIFT)
#define LVL_GRAN(n)     (1UL << LVL_SHIFT(n))
  • LVL_SHIFT(n)
    • 레벨별 비트 수
      • =n * 3
  • LVL_GRAN(n)
    • 레벨별 틱 단위
      • =2^(n*3) 틱
      • 예) 0 레벨 = 2^0 = 1 틱
      • 예) 1 레벨 = 2^3 = 8 틱
      • 예) 2 레벨 = 2^6 = 64 틱
      • 예) 3 레벨 = 2^6 = 512 틱
      • 예) 4 레벨 = 2^6 = 4096 틱
      • 예) 5 레벨 = 2^6 = 32768 틱
      • 예) 6 레벨 = 2^6 = 262144 틱
      • 예) 7 레벨 = 2^21=2097152 틱
      • 예) 8 레벨 = 2^24=16777216 틱

 

LVL_OFFS()

kernel/time/timer.c

/* Size of each clock level */
#define LVL_BITS        6
#define LVL_SIZE        (1UL << LVL_BITS)
#define LVL_MASK        (LVL_SIZE - 1)
#define LVL_OFFS(n)     ((n) * LVL_SIZE)
  • LVL_OFFS(n)
    • 레벨별 벡터 offset
      • = n * 64
      • 예) 0 레벨 = 0 * 64 = 0
      • 예) 1 레벨 = 1 * 64 = 64
      • 예) 2 레벨 = 2 * 64 = 128
      • 예) 3 레벨 = 3 * 64 = 192
      • 예) 4 레벨 = 4 * 64 = 256
      • 예) 5 레벨 = 5 * 64 = 320
      • 예) 6 레벨 = 6 * 64 = 384
      • 예) 7 레벨 = 7 * 64 = 448
      • 예) 8 레벨 = 8 * 64 = 512

 

LVL_START()

kernel/time/timer.c

/*
 * The time start value for each level to select the bucket at enqueue
 * time.
 */
#define LVL_START(n)    ((LVL_SIZE - 1) << (((n) - 1) * LVL_CLK_SHIFT))
  • 레벨별 시작 틱 값
    • = 63 << (n-1) * 3
      • 예) 레벨 0 = 63 << (0-1) * 3 = 0 틱
      • 예) 레벨 1 = 63 << (1-1) * 3 = 63 틱
      • 예) 레벨 2 = 63 << (2-1) * 3 = 504 틱
      • 예) 레벨 3 = 63 << (3-1) * 3 =4032 틱
      • 예) 레벨 4 = 63 << (4-1) * 3 =32256 틱
      • 예) 레벨 5 = 63 << (5-1) * 3 = 258048 틱
      • 예) 레벨 6 = 63 << (6-1) * 3 = 2064384 틱
      • 예) 레벨 7 = 63 << (7-1) * 3 = 16515072 틱
      • 예) 레벨 8 = 63 << (8-1) * 3 = 132120576 틱

 


타이머 베이스

타이머 베이스 락 획득

lock_timer_base()

kernel/time/timer.c

/*
 * We are using hashed locking: Holding per_cpu(timer_bases[x]).lock means
 * that all timers which are tied to this base are locked, and the base itself
 * is locked too.
 *
 * So __run_timers/migrate_timers can safely modify all timers which could
 * be found in the base->vectors array.
 *
 * When a timer is migrating then the TIMER_MIGRATING flag is set and we need
 * to wait until the migration is done.
 */
static struct timer_base *lock_timer_base(struct timer_list *timer,
                                          unsigned long *flags)
        __acquires(timer->base->lock)
{
        for (;;) {
                struct timer_base *base;
                u32 tf;

                /*
                 * We need to use READ_ONCE() here, otherwise the compiler
                 * might re-read @tf between the check for TIMER_MIGRATING
                 * and spin_lock().
                 */
                tf = READ_ONCE(timer->flags);

                if (!(tf & TIMER_MIGRATING)) {
                        base = get_timer_base(tf);
                        raw_spin_lock_irqsave(&base->lock, *flags);
                        if (timer->flags == tf)
                                return base;
                        raw_spin_unlock_irqrestore(&base->lock, *flags);
                }
                cpu_relax();
        }
}

타이머가 사용할 현재 cpu의 타이머 베이스 락을 획득한다.

  • 만일 타이머가 migration 중이면 TIMER_MIGRATING 플래그를 사용하는데 이 플래그가 없어질 때까지 루프를 반복하며 lock을 잡을 때까지 기다린다.
  • 또한 lock을 잡은 후에 플래그가 변경된 경우 루프를 반복한다.

 

타이머 베이스 알아오기

get_timer_base()

kernel/time/timer.c

static inline struct timer_base *get_timer_base(u32 tflags)
{
        return get_timer_cpu_base(tflags, tflags & TIMER_CPUMASK);
}

현재 cpu에 대한 타이머 베이스를 반환한다.

 

get_target_base()

kernel/time/timer.c

static inline struct timer_base *
get_target_base(struct timer_base *base, unsigned tflags)
{
#if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON)
        if (static_branch_likely(&timers_migration_enabled) &&
            !(tflags & TIMER_PINNED))
                return get_timer_cpu_base(tflags, get_nohz_timer_target());
#endif
        return get_timer_this_cpu_base(tflags);
}

동작할 cpu의 타이머 베이스를 반환한다.

  • 코드 라인 5~7에서 nohz 시스템에서 이주 가능한 타이머인 경우 현재 cpu가 idle 상태일 때 절전을 위해 타이머가 동작될 인접한 busy cpu를 찾아 해당 cpu의 타이머 베이스를 반환한다.
  • 코드 라인 9에서 현재 cpu의 타이머 베이스를 반환한다.

 

get_timer_cpu_base()

kernel/time/timer.c

static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu)
{
        struct timer_base *base = per_cpu_ptr(&timer_bases[BASE_STD], cpu);

        /*
         * If the timer is deferrable and NO_HZ_COMMON is set then we need
         * to use the deferrable base.
         */
        if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && (tflags & TIMER_DEFERRABLE))
                base = per_cpu_ptr(&timer_bases[BASE_DEF], cpu);
        return base;
}

요청 cpu에 대한 타이머 베이스를 반환한다.

  • per-cpu로 관리되는 타이머 베이스는 nohz를 사용하는 경우 두 개로 나뉘어 관리되며 지연 가능한 타이머는 별도의 타이머 베이스를 사용한다.

 

get_timer_this_cpu_base()

kernel/time/timer.c

static inline struct timer_base *get_timer_this_cpu_base(u32 tflags)
{
        struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);

        /*
         * If the timer is deferrable and NO_HZ_COMMON is set then we need
         * to use the deferrable base.
         */
        if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && (tflags & TIMER_DEFERRABLE))
                base = this_cpu_ptr(&timer_bases[BASE_DEF]);
        return base;
}

현재 cpu에 대한 타이머 베이스를 반환한다.

  • per-cpu로 관리되는 타이머 베이스는 nohz를 사용하는 경우 두 개로 나뉘어 관리되며 지연 가능한 타이머는 별도의 타이머 베이스를 사용한다.

 

nohz 타이머용 cpu

get_nohz_timer_target()

kernel/sched/core.c

/*
 * In the semi idle case, use the nearest busy CPU for migrating timers
 * from an idle CPU.  This is good for power-savings.
 *
 * We don't do similar optimization for completely idle system, as
 * selecting an idle CPU will add more delays to the timers than intended
 * (as that CPU's timer base may not be uptodate wrt jiffies etc).
 */
int get_nohz_timer_target(void)
{
        int i, cpu = smp_processor_id();
        struct sched_domain *sd;

        if (!idle_cpu(cpu) && housekeeping_cpu(cpu, HK_FLAG_TIMER))
                return cpu;

        rcu_read_lock();
        for_each_domain(cpu, sd) {
                for_each_cpu(i, sched_domain_span(sd)) {
                        if (cpu == i)
                                continue;

                        if (!idle_cpu(i) && housekeeping_cpu(i, HK_FLAG_TIMER)) {
                                cpu = i;
                                goto unlock;
                        }
                }
        }

        if (!housekeeping_cpu(cpu, HK_FLAG_TIMER))
                cpu = housekeeping_any_cpu(HK_FLAG_TIMER);
unlock:
        rcu_read_unlock();
        return cpu;
}

절전을 위해 nohz 타이머를 위한 타겟 cpu를 알아온다.

  • 코드 라인 6~7에서 로컬 cpu가 busy 상태이면서 타이머에 대한 housekeeping이 가능하면 로컬 cpu를 반환한다.
  • 코드 라인 10~20에서 cpu가 속한 스케쥴 domain 수 만큼 순회하고 내부에서 순회중인 스케줄 도메인만큼 cpu를 순회하며 busy 상태이면서 타이머에 대한 housekeeping이 가능한 해당 cpu를 반환한다.
  • 코드 라인 22~23에서 찾지 못한 경우이다. cpu가 타이머에 대한 housekeeping이 불가능하면 housekeeping이 가능한 어떠한 cpu라도 찾아낸다.
  • 코드 라인 26에서 nohz 타이머로 사용할 cpu를 반환한다.

 


SOFTIRQ – TIMER 수행

lowres 타이머의 bottom-half로 동작하는 softirq 핸들러를 알아본다.

 

run_timer_softirq()

kernel/time/timer.c

/*
 * This function runs timers and the timer-tq in bottom half context.
 */
static __latent_entropy void run_timer_softirq(struct softirq_action *h)
{
        struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);

        __run_timers(base);
        if (IS_ENABLED(CONFIG_NO_HZ_COMMON))
                __run_timers(this_cpu_ptr(&timer_bases[BASE_DEF]));
}

로컬 cpu에 타이머 tick이 인입될 때 마다 호출되는 timer softirq에 등록한 함수이다. 만료된 타이머들의 해당 타이머 함수를 호출한다.

  • 코드 라인 5에서 스탠다드 타이머 베이스에서 만료된 타이머들의 해당 타이머 함수를 호출한다.
  • 코드 라인 6~7에서 deferrable 타이머 베이스에서 만료된 타이머들의 해당 타이머 함수를 호출한다.

 

__run_timers()

kernel/time/timer.c

/**
 * __run_timers - run all expired timers (if any) on this CPU.
 * @base: the timer vector to be processed.
 */
static inline void __run_timers(struct timer_base *base)
{
        struct hlist_head heads[LVL_DEPTH];
        int levels;

        if (!time_after_eq(jiffies, base->clk))
                return;

        timer_base_lock_expiry(base);
        raw_spin_lock_irq(&base->lock);

        /*
         * timer_base::must_forward_clk must be cleared before running
         * timers so that any timer functions that call mod_timer() will
         * not try to forward the base. Idle tracking / clock forwarding
         * logic is only used with BASE_STD timers.
         *
         * The must_forward_clk flag is cleared unconditionally also for
         * the deferrable base. The deferrable base is not affected by idle
         * tracking and never forwarded, so clearing the flag is a NOOP.
         *
         * The fact that the deferrable base is never forwarded can cause
         * large variations in granularity for deferrable timers, but they
         * can be deferred for long periods due to idle anyway.
         */
        base->must_forward_clk = false;

        while (time_after_eq(jiffies, base->clk)) {

                levels = collect_expired_timers(base, heads);
                base->clk++;

                while (levels--)
                        expire_timers(base, heads + levels);
        }
        raw_spin_unlock_irq(&base->lock);
        timer_base_unlock_expiry(base);
}

요청 타이머 베이스에서 만료된 타이머들의 함수를 실행한다.

  • 코드 라인 6~7에서 jiffies < base->clk 이다. 가장 가까운 타이머 시각이 아직 처리할 시각이 안되었으므로 함수를 빠져나간다.
  • 코드 라인 9~10에서 타이머 함수 호출 루틴이 동시에 호출되는 것을 막기 위해 lock을 획득한다. 또한 타이머 베이스를 조작하기 위한 lock도 획득한다.
  • 코드 라인 26에서 타이머 베이스에서 forward 처리를 하지 못하도록 막는다.
  • 코드 라인 28~35에서 타이머 베이스의 clk가 만료된 경우 다음과 같이 처리한다.
    • 만료된 타이머들을 heads 리스트에 모아온다. levels에는 최고 레벨이 저장된다.
    • 다음 루프 처리를 위해 타이머 베이스의 clk을 미리 증가시킨다.
    • 레벨별로 만료된 타이머에 해당하는 콜백 함수들을 호출한다.
  • 코드 라인 36~37에서 걸었던 lock들을 해제한다.

 

만료된 타이머들 수집

collect_expired_timers()

kernel/time/timer.c

static int collect_expired_timers(struct timer_base *base,
                                  struct hlist_head *heads)
{
        unsigned long now = READ_ONCE(jiffies);

        /*
         * NOHZ optimization. After a long idle sleep we need to forward the
         * base to current jiffies. Avoid a loop by searching the bitfield for
         * the next expiring timer.
         */
        if ((long)(now - base->clk) > 2) {
                unsigned long next = __next_timer_interrupt(base);

                /*
                 * If the next timer is ahead of time forward to current
                 * jiffies, otherwise forward to the next expiry time:
                 */
                if (time_after(next, now)) {
                        /*
                         * The call site will increment base->clk and then
                         * terminate the expiry loop immediately.
                         */
                        base->clk = now;
                        return 0;
                }
                base->clk = next;
        }
        return __collect_expired_timers(base, heads);
}

요청 타이머 베이스에서 만료된 타이머들을 @heads 리스트에 추가한다. levels에는 수집한 최고 레벨 + 1이 저장된다.

  • 코드 라인 11~27에서 nohz를 사용하면 타이머 베이스를 매 틱마다 처리하지 못할 수 있다. 이 때 3틱 이상 밀려 있는 상태면 매 틱마다 처리하지 않고 nohz optimization을 수행한다. 이 때 다음 타이머의 만료 시각이 현재 시각을 초과했는지 여부에 따라 다음과 같이 처리한다.
    • 아직 만료 시각이 남은 경우 지연된 base->clk을 현재 시각으로 갱신하고, 결과 값 0으로 함수를 빠져나간다.
    • 만료 시각이 지난 경우 base->clk에 다음 타이머 만료 시각을 대입하고, 다음 루틴을 계속 처리한다.
  • 코드 라인 28에서 만료된 타이머 함수들을 @heds 리스트로 수집한다.

 

__collect_expired_timers()

kernel/time/timer.c

static int __collect_expired_timers(struct timer_base *base,
                                    struct hlist_head *heads)
{
        unsigned long clk = base->clk;
        struct hlist_head *vec;
        int i, levels = 0;
        unsigned int idx;

        for (i = 0; i < LVL_DEPTH; i++) {
                idx = (clk & LVL_MASK) + i * LVL_SIZE;

                if (__test_and_clear_bit(idx, base->pending_map)) {
                        vec = base->vectors + idx;
                        hlist_move_list(vec, heads++);
                        levels++;
                }
                /* Is it time to look at the next level? */
                if (clk & LVL_CLK_MASK)
                        break;
                /* Shift clock for the next level granularity */
                clk >>= LVL_CLK_SHIFT;
        }
        return levels;
}

요청 타이머 베이스에서 만료된 타이머들을 @heads 리스트로 수집한다.

  • 코드 라인 9~17에서 0~마지막 레벨(100hz 이하에서 8, 초과 시 9)까지 순회하며 타이머 휠 인덱스 값에 해당하는 펜딩 맵에 비트가 설정된 경우 이를 클리어하고, 이에 해당하는 인덱스의 타이머 휠 벡터 리스트를 heads 리스트에 옮기고, 반환할 levels를 1 증가시킨다.
  • 코드 라인 19~23에서 다음 레벨을 처리할 필요가 없는 경우 루프를 벗어난다. 그렇지 않고 남은 경우 다음 레벨을 처리하기 위해 clk을 3 비트 쉬프트한다.
    • clk 값의 해당 레벨의 6비트 중 하위 3비트 값이 0인 경우 다음 레벨로 넘어간다.
  • 코드 라인 24에서 수집한 최고 레벨 + 1 값을 반환한다.

 

다음 그림은 base->clk 값에 의해 호출되는 레벨별 타이머 벡터 리스트들을 보여준다.

  • 레벨별로 쉬프트된 6bit clk 값의 하위 3비트가 0인 경우 다음 레벨도 처리하기 위해 이동한다.
  • clk 값이 0x4400 값인 경우 lvl 0 ~ lvl 3까지 이동하며 각 레벨의 6bit clk값 + lvl * 64로 idx를 산출한다. 그런 후 idx에 해당하는 펜딩맵이 설정된 벡터 리스트의 타이머들이 선택된다.

 

만료된 타이머들의 콜백 함수 호출

expire_timers()

kernel/time/timer.c

static void expire_timers(struct timer_base *base, struct hlist_head *head)
{
        /*
         * This value is required only for tracing. base->clk was
         * incremented directly before expire_timers was called. But expiry
         * is related to the old base->clk value.
         */
        unsigned long baseclk = base->clk - 1;

        while (!hlist_empty(head)) {
                struct timer_list *timer;
                void (*fn)(struct timer_list *);

                timer = hlist_entry(head->first, struct timer_list, entry);

                base->running_timer = timer;
                detach_timer(timer, true);

                fn = timer->function;

                if (timer->flags & TIMER_IRQSAFE) {
                        raw_spin_unlock(&base->lock);
                        call_timer_fn(timer, fn, baseclk);
                        base->running_timer = NULL;
                        raw_spin_lock(&base->lock);
                } else {
                        raw_spin_unlock_irq(&base->lock);
                        call_timer_fn(timer, fn, baseclk);
                        base->running_timer = NULL;
                        timer_sync_wait_running(base);
                        raw_spin_lock_irq(&base->lock);
                }
        }
}

@head 리스트의 만료된 타이머들의 콜백 함수들을 호출한다.

  • 호출하는 동안 base->running_timer에 타이머가 기록되고, 호출이 완료되면 null이 대입된다.
  • TIMER_IRQSAFE 플래그를 사용하면 인터럽트를 disable하지 않은 상태에서 spinlock을 사용한다.

 

타이머 콜백 함수 호출

call_timer_fn()

kernel/time/timer.c

static void call_timer_fn(struct timer_list *timer,
                          void (*fn)(struct timer_list *),
                          unsigned long baseclk)
{
        int count = preempt_count();

#ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the timer from inside the
         * function that is called from it, this we need to take into
         * account for lockdep too. To avoid bogus "held lock freed"
         * warnings as well as problems when looking into
         * timer->lockdep_map, make a copy and use that here.
         */
        struct lockdep_map lockdep_map;

        lockdep_copy_map(&lockdep_map, &timer->lockdep_map);
#endif
        /*
         * Couple the lock chain with the lock chain at
         * del_timer_sync() by acquiring the lock_map around the fn()
         * call here and in del_timer_sync().
         */
        lock_map_acquire(&lockdep_map);

        trace_timer_expire_entry(timer, baseclk);
        fn(timer);
        trace_timer_expire_exit(timer);

        lock_map_release(&lockdep_map);

        if (count != preempt_count()) {
                WARN_ONCE(1, "timer: %pS preempt leak: %08x -> %08x\n",
                          fn, count, preempt_count());
                /*
                 * Restore the preempt count. That gives us a decent
                 * chance to survive and extract information. If the
                 * callback kept a lock held, bad luck, but not worse
                 * than the BUG() we had.
                 */
                preempt_count_set(count);
        }
}

인수로 받은 fn은 타이머에 설정된 함수이다. 디버그를 위해 trace 출력등이 사용되었다.

 


타이머 설정

컴파일 타임에 정적 타이머 생성 및 초기화

include/linux/timer.h

DEFINE_TIMER()

#define DEFINE_TIMER(_name, _function)                          \
        struct timer_list _name =                               \
                __TIMER_INITIALIZER(_function, 0)

컴파일 타임에 타이머를 초기화한다. 인자로 타이머 이름과 콜백 함수를 지정한다.

  • 플래그는 사용하지 않고 0을 전달한다.

 

__TIMER_INITIALIZER()

include/linux/timer.h

#define __TIMER_INITIALIZER(_function, _flags) {                \
                .entry = { .next = TIMER_ENTRY_STATIC },        \
                .function = (_function),                        \
                .flags = (_flags),                              \
                __TIMER_LOCKDEP_MAP_INITIALIZER(                \
                        __FILE__ ":" __stringify(__LINE__))     \
        }

 

런타임에 동적 타이머 생성 및 초기화

timer_setup()

include/linux/timer.h

/**
 * timer_setup - prepare a timer for first use
 * @timer: the timer in question
 * @callback: the function to call when timer expires
 * @flags: any TIMER_* flags
 *
 * Regular timer initialization should use either DEFINE_TIMER() above,
 * or timer_setup(). For timers on the stack, timer_setup_on_stack() must
 * be used and must be balanced with a call to destroy_timer_on_stack().
 */
#define timer_setup(timer, callback, flags)                     \
        __init_timer((timer), (callback), (flags))

타이머를 사용하기 위해 준비한다. 인자로는 타이머와 콜백 함수 및 플래그를 지정한다.

 

__init_timer()

include/linux/timer.h

#define __init_timer(_timer, _fn, _flags)                               \
        init_timer_key((_timer), (_fn), (_flags), NULL, NULL)

 

timer_setup_on_stack()

include/linux/timer.h

#define setup_timer_on_stack(timer, callback, flags)                    \
        __init_timer_on_stack((timer), (callback), (flags))

타이머를 전달 받은 인자들로 초기화한다.

 

__init_timer_on_stack()

include/linux/timer.h

#define __init_timer_on_stack(_timer, _fn, _flags)                           \
        init_timer_on_stack_key((_timer), (_fn), (_flags), NULL, NULL)

 

init_timer_on_stack_key()

include/linux/timer.h

static inline void init_timer_on_stack_key(struct timer_list *timer,
                                           void (*func)(struct timer_list *),
                                           unsigned int flags, 
                                           const char *name,
                                           struct lock_class_key *key)
{
        init_timer_key(timer, func, flags, name, key);
}

 

init_timer_key()

kernel/time/timer.c

/**
 * init_timer_key - initialize a timer
 * @timer: the timer to be initialized
 * @func: timer callback function
 * @flags: timer flags
 * @name: name of the timer
 * @key: lockdep class key of the fake lock used for tracking timer
 *       sync lock dependencies
 *
 * init_timer_key() must be done to a timer prior calling *any* of the
 * other timer functions.
 */
void init_timer_key(struct timer_list *timer, 
                    void (*func)(struct timer_list *), unsigned int flags,
                    const char *name, struct lock_class_key *key)
{
        debug_init(timer);
        do_init_timer(timer, func, flags, name, key);
}
EXPORT_SYMBOL(init_timer_key);

타이머를 전달 받은 인자들로 초기화한다.

 

do_init_timer()

kernel/time/timer.c

static void do_init_timer(struct timer_list *timer,
                          void (*func)(struct timer_list *),
                          unsigned int flags,
                          const char *name, struct lock_class_key *key)
{
        timer->entry.pprev = NULL;
        timer->function = func;
        timer->flags = flags | raw_smp_processor_id();
        lockdep_init_map(&timer->lockdep_map, name, key, 0);
}

타이머를 전달 받은 인자들로 초기화한다.

 


초기화

init_timers()

kernel/time/timer.c

void __init init_timers(void)
{
        init_timer_cpus();
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

타이머를 사용할 수 있도록 초기화한다.

  • 코드 라인 3에서 cpu별로 타이머를 사용할 수 있도록 초기화한다.
  • 코드 라인 4에서 타이머 softirq를 열고 run_timer_softirq() 함수가 호출되도록 준비한다.

 

init_timer_cpus()

kernel/time/timer.c

static void __init init_timer_cpus(void)
{
        int cpu;

        for_each_possible_cpu(cpu)
                init_timer_cpu(cpu);
}

모든 possible cpu에 대해 타이머를 사용할 수 있도록 초기화한다.

 

init_timer_cpu()

kernel/time/timer.c

static void __init init_timer_cpu(int cpu)
{
        struct timer_base *base;
        int i;

        for (i = 0; i < NR_BASES; i++) {
                base = per_cpu_ptr(&timer_bases[i], cpu);
                base->cpu = cpu;
                raw_spin_lock_init(&base->lock);
                base->clk = jiffies;
                timer_base_init_expiry_lock(base);
        }
}

요청 cpu에 대해 타이머를 사용할 수 있도록 초기화한다.

 


기존 커널 관련 (~ 커널 v4.7)

 

기존 cascading wheel 구조

요청된 타이머의 관리를 위해 cpu 마다 타이머휠이 사용된다. 타이머 휠마다 tv1 ~ tv5까지의 5개 벡터로 나누어 관리하며, 각 벡터는 각각 256, 64, 64, 64, 64개의 리스트로 이루어진다.

  • CONFIG_BASE_SMALL 사용 시 커널 사이즈를 최소화하기 위해 리스트들은 각각 64, 16, 16, 16, 16개로 1/4로 줄어든다.

 

다음 그림은 타이머 벡터 간의 cascade 조건을 보여준다.

예) base->timer_jiffies 값이 0x400_0000인 경우 tv2 -> tv1, tv3 -> tv2, tv4 -> tv3, tv5 -> tv4의 순서로 full cascade 처리된다.

 

다음 그림은 만료시각이 다른 각종 타이머들을 추가했을 때 타이머휠에 등록된 타이머 상태들을 보여준다.

 

  • expires – base->timer_jiffies 하여 산출된 tick 값에 따라 512(256+64+64+64+64)개의 리스트 중 하나가 선택된다.
    • 예) timer_jiffies=100, hz=100, 7초 후에 timer가 호출되게 하려할 때 expires 값과 추가될 타이머 벡터 리스트는?
      • 7초면 700 tick이 필요하므로 expires=100+700이 대입되고 tv2.vec[32]에 추가된다.

 

다음 그림은 jiffies값이 35인 시점에 cpu#1에 tick이 발생하고 그 동안 tick이 발생하지 못해 처리 못했던 jiffies 들에 대한 처리를 한꺼번에 처리하도록 한다.

  • jiffies=35인 시점에 26~35까지 처리 못했던 만료된 타이머들의 함수들을 처리한다.

 

다음 그림은 tv2에 있는 두 개의 타이머가 tv1으로 cascade되는 모습을 보여준다.

 

Slack으로 만료 시간 조정

만료 시간을 조정하여 유사한 만료 시간들끼리 모아 한꺼번에 처리하도록 slack 정렬한다. 타이머 인터럽트가 조금이라도 덜 발생하도록하여 절전과 처리 성능에 도움을 준다.

 

아래 그림은 100hz 시스템에서 slack으로 9 tick을 준 경우와 timeout으로 2311 tick을 준 경우에 대해 만료 값이 어떻게 변화하는지를 보여준다.

 

커널 v4.7까지에서는 타이머 리스트가 disable 된 상태에 있기 때문에 아래와 같이 inactive 출력된다.

$ cat /proc/timer_stats
Timer Stats Version: v0.3
Sample period: 0.000 s
Collection: inactive
0 total events

 

다음과 같이 enable 시킨다.

# echo "1" > /proc/timer_stats

 

그런 후 출력이 가능하다. (lowres 타이머와 hrtimer가 섞여 있다)

# cat /proc/timer_stats
Timer Stats Version: v0.3
Sample period: 4.530 s
Collection: active
  453,     0 swapper/2        hrtimer_start_range_ns (tick_sched_timer)
   38,     0 swapper/0        hrtimer_start_range_ns (tick_sched_timer)
   50,     7 rcu_preempt      rcu_gp_kthread (process_timeout)
    5,  1632 ifplugd          hrtimer_start_range_ns (hrtimer_wakeup)
   16,     0 swapper/1        hrtimer_start (tick_sched_timer)
   36,     0 swapper/0        hrtimer_start (tick_sched_timer)
    4,  3992 sshd             sk_reset_timer (tcp_write_timer)
   17,     0 swapper/1        usb_hcd_poll_rh_status (rh_timer_func)
    4,  1582 ifplugd          hrtimer_start_range_ns (hrtimer_wakeup)
    4,  2230 ntpd             hrtimer_start_range_ns (posix_timer_fn)
    4,  3467 kworker/u8:2     queue_delayed_work_on (delayed_work_timer_fn)
   10,     0 swapper/0        sk_reset_timer (tcp_delack_timer)
    3, 26686 kworker/2:1      queue_delayed_work_on (delayed_work_timer_fn)
    3,    44 kworker/0:1      queue_delayed_work_on (delayed_work_timer_fn)
    1,  2058 thd              hrtimer_start_range_ns (hrtimer_wakeup)
   25,     0 swapper/1        hrtimer_start_range_ns (tick_sched_timer)
673 total events, 148.565 events/sec

 

참고

 

tick_init()

 

커널 v2.6.21에서 tickless라는 개념이 도입 되기전 커널에서는 각 cpu의 스케쥴러들은 고정된 주기(hz)의 time tick을 받아 태스크들의 스케쥴링을 수행했다. time tick을 줄여서 그냥 tick으로 불리고 있다. 현재는 커널 v3.10에 이르러 다음과 같이 3가지의 커널 옵션을 구분해서 사용한다.

  • CONFIG_HZ_PERIODIC
    • cpu의 idle 상태와 상관 없이 기존 커널과 같이 항상 tick을 발생한다.
    • 커널 버전 및 응용에 따라 다음 중 하나의 hz를 사용한다.
      • 100hz, 200hz, 250hz, 300hz, 500hz, 1000hz
        • rpi2: 100hz
  • CONFIG_NO_HZ_IDLE
    • cpu가 idle 상태로 들어가면 tick을 끈다.
      • 최대 1000hz -> 0hz로 줄여 전력 소모를 대폭 줄인다.
    • rcu callback 처리가 남아 있는 경우 해당 cpu는 no hz 모드로 진입하지 못한다.
    • rpi2: 현재 이 모드를 사용한다.
    • dynticks idle 또는 tickless idle 이라고도 불린다.
    • 참고: Clockevents and dyntick | LWN.net
  •  CONFIG_NO_HZ_FULL
    • cpu가 idle 상태로 들어가면 tick을 끈다.
      • 최대 1000hz -> 0hz로 줄여 전력 소모를 대폭 줄인다.
    • rcu callback 처리가 남아 있는 경우 해당 cpu는 no hz 모드로 진입하지 못한다.
    • single task가 동작중인 경우 tick을 1Hz 로 낮춘다. (많은 경우에 해당한다)
      • 최대 1000hz -> 1hz로 줄여 성능이 빨라지는 효과가 있다.
      • 디버깅 모드에서 1hz가 아니라 no hz로 바꾸어 문제점 분석에 사용할 수 있다.
    • full dynticks 또는 full tickless 라고도 불린다.
    • “nohz_full=” 부트 타임 커널 파라메터를 사용하는 경우에만 동작시킬 수 있다.
      • 설정되지 않는 경우 CONFIG_NO_HZ_IDLE 커널 옵션과 동일하게 동작한다.
      • 예)
        • “nohz_full=0-3”
          • cpu#0~#3까지 적용
        • “nohz_full=4,8-15”
          • cpu#4, #8~#15까지 적용
    • 참고:

 

1 HZ

  • 초당 하나의 타이머 인터럽트

 

NO_HZ

  • 특징
    • 최소한 하나의 cpu는 주기적으로  tick을 받는다.
    • SMP에서 유효하다.
    • 구현을 위해 하드웨어적으로 hr-timer가 필요하다.
  • 장점
    • 전력 소모가 크게 줄어 든다.
  • 단점
    • 시간 계산과 RCU callback 처리를 위해 구현이 복잡해졌다.
    • rcu callback 처리가 남아 있는 경우 해당 cpu는 no hz 모드로 진입하지 못한다.

 

Tick Broadcast

cpu가 깊은 idle 상태에 있을 때 tick broadcast를 수신받아 idle 상태에서 벗어난 후 리스케쥴 여부를 확인하고 진행할 태스크가 있는 경우 수행을 하게 한다.

  • 실행할 task가 없으면 cpu는 cpuidle_idle_call() 함수를 통해 idle loop 상태에 진입한다.
    • bootup을 담당한 첫 번째 cpu는 rest_init() 함수의 마지막 cpu_startup_entry() -> cpu_idle_loop()에서 idle 루프를 돈다.
    • 그 외 cpu 들을 online 상태로 변경하는 경우 secondary_start_kernel() 함수의 마지막 cpu_startup_entry() -> cpu_idle_loop()에서 idle 루프를 돈다.
  • Tick이 발생하여 리스케쥴링이 발생하는 경우 idle 상태를 벗어난다.
    • no hz에서 tick이 없는 경우 tick broadcast에 의해 깨어나 idle 상태를 탈출할 수 있다.
  • tick device 사용
    • 클럭소스는 clock_event_device를 사용한다.
    • tick_device 구조체를 사용하여 표현한다.
    • tick device 모드
      • periodic 모드
        • tick_broadcast_mask cpu 비트맵을 대상으로 tick_broadcast_start_periodic() 함수를 사용해 broadcast tick을 주기적으로 보낼 수 있다.
      • oneshot 모드
    • tick 디바이스의 등록 함수
      • tick_install_broadcast_device(newdev)

 

Generic clock events

  • tick 구현에 대한 generic core 루틴

 

tick_init()

kernel/time/tick-common.c

/**
 * tick_init - initialize the tick control
 */
void __init tick_init(void)
{
        tick_broadcast_init();
        tick_nohz_init();
}

tick broadcast framework 및 full nohz framework을 준비한다.

 

tick_broadcast_init()

kernel/time/tick-broadcast.c

void __init tick_broadcast_init(void)
{
        zalloc_cpumask_var(&tick_broadcast_mask, GFP_NOWAIT);
        zalloc_cpumask_var(&tick_broadcast_on, GFP_NOWAIT);
        zalloc_cpumask_var(&tmpmask, GFP_NOWAIT);
#ifdef CONFIG_TICK_ONESHOT
        zalloc_cpumask_var(&tick_broadcast_oneshot_mask, GFP_NOWAIT);
        zalloc_cpumask_var(&tick_broadcast_pending_mask, GFP_NOWAIT);
        zalloc_cpumask_var(&tick_broadcast_force_mask, GFP_NOWAIT);
#endif
}

tick broadcast framework을 준비한다.

  • tick_broadcast_mask
    • idle(sleep) 모드에 있는 cpu 비트맵
  • tick_broadcast_on
    • 주기적으로 broadcast가 수행되는 cpu 비트맵
  • tick_broadcast_oneshot_mask
    • oneshot으로 broadcast 해야 할 cpu 비트맵
  • tick_broadcast_pending_mask
    • broadcast가 지연된 cpu 비트맵
  • tick_broadcast_force_mask
    • 강제로 broadcast해야 할 cpu 비트맵
  • 참고: The tick broadcast framework | LWN.net

 

tick_nohz_init()

kernel/time/tick-sched.c

void __init tick_nohz_init(void)
{
        int cpu;

        if (!tick_nohz_full_running) {
                if (tick_nohz_init_all() < 0)
                        return;
        }

        if (!alloc_cpumask_var(&housekeeping_mask, GFP_KERNEL)) {
                WARN(1, "NO_HZ: Can't allocate not-full dynticks cpumask\n");
                cpumask_clear(tick_nohz_full_mask);
                tick_nohz_full_running = false; 
                return;
        }

        /*
         * Full dynticks uses irq work to drive the tick rescheduling on safe
         * locking contexts. But then we need irq work to raise its own
         * interrupts to avoid circular dependency on the tick 
         */
        if (!arch_irq_work_has_interrupt()) {
                pr_warning("NO_HZ: Can't run full dynticks because arch doesn't "
                           "support irq work self-IPIs\n");
                cpumask_clear(tick_nohz_full_mask);
                cpumask_copy(housekeeping_mask, cpu_possible_mask);
                tick_nohz_full_running = false;
                return;
        }
        
        cpu = smp_processor_id();

        if (cpumask_test_cpu(cpu, tick_nohz_full_mask)) {
                pr_warning("NO_HZ: Clearing %d from nohz_full range for timekeeping\n", cpu);
                cpumask_clear_cpu(cpu, tick_nohz_full_mask);
        }

        cpumask_andnot(housekeeping_mask,
                       cpu_possible_mask, tick_nohz_full_mask);

        for_each_cpu(cpu, tick_nohz_full_mask)
                context_tracking_cpu_set(cpu);

        cpu_notifier(tick_nohz_cpu_down_callback, 0);
        pr_info("NO_HZ: Full dynticks CPUs: %*pbl.\n",
                cpumask_pr_args(tick_nohz_full_mask));
}

CONFIG_NO_HZ_FULL 커널 옵션을 사용한 경우 full tickless(no hz)로 동작을 하기 위한 framework을 준비한다.

  • 코드 라인 5~8에서 “nohz_full=” 부트 타임 커널 파라메터를 사용하지 않은 경우 처리를 포기한다.
  • 코드 라인 10~15에서 housekeeping_mask에 cpu 마스크를 할당한다. 할당이 실패하는 경우 처리를 포기한다.
  • 코드 라인 22~29에서 SMP 머신이 아닌 경우 처리를 포기한다.
  • 코드 라인 31~36에서 tick_nohz_fullmask에서 현재 cpu에 해당하는 비트를 클리어한다.
  • 코드 라인 38~39에서 housekeeping_mask에 possible cpu들에 대해 nohz full이 설정되지 않은 cpu들만 설정한다.
    • housekeeping_mask <- cpu_possible_mask & ~tick_nohz_full_mask
  • 코드 라인 41~42에서 CONFIG_CONTEXT_TRACKING 커널 옵션을 사용하는 경우 nohz full 설정된 cpu들에 대해서만 context tracking을 허용하도록 설정한다.
  • 코드 라인 44에서 cpu 상태 변화 시 호출되도록 우선 순위를 0으로 cpu notify chain에 tick_nohz_cpu_down_callback() 함수를 추가한다.
  • 코드 라인 45~46에서 “NO_HZ: Full dynticks CPUs:”  정보 메시지를 출력한다.

 

참고

Interrupts -2- (irq chip)

<kernel v5.4>

IRQ Chip

인터럽트 컨트롤러 드라이버를 위해 hw 제어를 담당하는 구현 부분을 가진다.

  • 여러 제조사의 인터럽트 컨트롤러들은 각 인터럽트 라인의 통제 방법이 각각 다르다.
  • 리눅스 IRQ 코어 레이어에서 irq chip은 다양한 인터럽트 컨트롤러의 라인별 제어 액션을 통일하여 처리하도록 구현되었다.
    • 인터럽트 라인마다  마스킹 또는 set/clear 동작을 수행하는 서비스를 제공한다.
    • irq_chip에 있는 여러 개의 후크 포인터에 연결된 콜백 함수를 통해 처리한다.
      • (*irq_mask)
      • (*irq_unmask)
      • (*irq_set_type)
    • 각 제조사의 인터럽트 컨트롤러 디바이스 드라이버에는 irq_chip의 후크에 연결될 처리 함수들을 제공해야 한다.
  • 인터럽트 라인의 마스킹 및 set/clear 처리 유형 등의 처리가 각각 다른 경우 irq_chip을 분리하여 구성할 수 있다.
  • 인터럽트 라인 개개별로 h/w 제어를 담당하는 irq_chip을 다음 함수를 사용하여 지정한다.
    • irq_set_chip()

 

다음 그림은 하나의 인터럽트 컨트롤러가 대응하는 모든 인터럽트들을 하나의 irq_chip에서 제어하는 모습을 보여준다.

 

다음 그림은 하나의 인터럽트 컨트롤러가 대응하는 인터럽트들 중 h/w 제어 기능이 서로 다른 부분(part)들을 나누어 각각의 irq_chip에서 제어하는 모습을 보여준다.

 

다음 그림은 한 개의 디바이스 드라이버는 2 개의 irq_chip 구현을 가지고 있고, 또 다른 한 개의 디바이스 드라이버는 1 개의 irq_chip 구현을 가지고 있는 모습을 자세히 보여준다.

 

hierarchy irq chip 구성

2 개 이상의 인터럽트 컨트롤러를 hierarchy하게 구성하여 사용할 수 있다.

 

다음 그림은 두 개 이상 hierarchy 구성된 인터럽트 컨트롤러들이 연결되어 처리되는 모습을 보여준다.

  • 자식 인터럽트 컨트롤러 A에서 수신한 인터럽트들은 cascade(chained)로 부모 인터럽트 컨트롤러 B에 전달된다.

 

GPIO cascade 인터럽트 연동

인터럽트 컨트롤러 아래에 gpio 등에서 인터럽트의 멀티플렉싱이 가능한 장치들이 추가로 cascade 연결된 경우 이들의 하위 인터럽트 라인을 식별하기 위해 cascade 연동을 한다. 다음 GPIO 글에서 관련 연동 방법을 소개하기로 한다.

 


Device Tree 기반의 인터럽트 컨트롤러 초기화

irqchip_init()

drivers/irqchip/irqchip.c

void __init irqchip_init(void)
{
        of_irq_init(__irqchip_of_table);
        acpi_probe_device_table(irqchip);
}

디바이스 트리 및 ACPI가 지정한 인터럽트 컨트롤러의 초기화 함수를 호출한다.

  • 코드 라인 3에서 __irqchip_of_table 섹션에 있는 of_device_id 구조체들을 검사하여 매치되는 인터럽트 컨트롤러 초기화 함수를 찾아 호출한다.
    • rpi2:
      • 2 개의 인터럽트 컨트롤러 드라이버 호출
        • drivers/irqchip/irq-bcm2836.c – bcm2836_arm_irqchip_l1_intc_of_init()
        • drivers/irqchip/irq-bcm2835.c – bcm2835_armctrl_of_init()
    • rock960
      • 1 개의 인터럽트 컨트롤러 드라이버 호출
        • drivers/irqchip/irq-gic-v3.c – gic_of_init()
  • 코드 라인 4에서 ACPI가 지정한 인터럽트 컨트롤러의 초기화 함수를 호출한다.

 

of_irq_init()

drivers/of/irq.c -1/2-

/**
 * of_irq_init - Scan and init matching interrupt controllers in DT
 * @matches: 0 terminated array of nodes to match and init function to call
 *
 * This function scans the device tree for matching interrupt controller nodes,
 * and calls their initialization functions in order with parents first.
 */
void __init of_irq_init(const struct of_device_id *matches)
{
        const struct of_device_id *match;
        struct device_node *np, *parent = NULL;
        struct of_intc_desc *desc, *temp_desc;
        struct list_head intc_desc_list, intc_parent_list;

        INIT_LIST_HEAD(&intc_desc_list);
        INIT_LIST_HEAD(&intc_parent_list);

        for_each_matching_node_and_match(np, matches, &match) {
                if (!of_property_read_bool(np, "interrupt-controller") ||
                                !of_device_is_available(np))
                        continue;

                if (WARN(!match->data, "of_irq_init: no init function for %s\n",
                         match->compatible))
                        continue;

                /*
                 * Here, we allocate and populate an of_intc_desc with the node
                 * pointer, interrupt-parent device_node etc.
                 */
                desc = kzalloc(sizeof(*desc), GFP_KERNEL);
                if (!desc) {
                        of_node_put(np);
                        goto err;
                }

                desc->irq_init_cb = match->data;
                desc->dev = of_node_get(np);
                desc->interrupt_parent = of_irq_find_parent(np);
                if (desc->interrupt_parent == np)
                        desc->interrupt_parent = NULL;
                list_add_tail(&desc->list, &intc_desc_list);
        }

Device Tree에 있는 인터럽트 컨트롤러와 매치되는 인터럽트 컨트롤러의 초기화 함수를 호출한다. 단 2개 이상의 인터럽트 컨트롤러를 사용하는 경우 상위 인터럽트 컨트롤러부터 초기화한다.

  • 코드 라인 8~9에서 임시로 사용되는 리스트 2개를 초기화한다.
    • intc_desc_list:
      • irqchip 테이블의 이름과 Device Tree의 인터럽트 컨트롤러 드라이버명(compatible)으로 매치되어 구성된 desc 구조체 리스트
        • ARM 임베디드 시스템은 보통 1 ~ 2개의 인터럽트 컨트롤러를 사용한다.
    • intc_parent_list:
      • 초기화 성공한 인터럽트 컨트롤러의 desc 구조체 리스트로 최상위 인터럽트 컨트롤러가 가장 앞으로 구성된다.
  • 코드 라인 11~14에서 DTB 트리의 모든 노드에서 irqchip 테이블의 이름 과 Device Tree의 인터럽트 컨트롤러 드라이버명(compatible)으로 일치한 노드들에서 “interrupt-controller” 속성을 못찾았거나 “status” 속성이 “ok”가 아닌 경우 skip 한다.
  • 코드 라인 24~28에서 of_intc_dest 구조체를 할당받아 온다.
  • 코드 라인 30~34에서 할당받은 구조체를 설정한다.
    • desc->dev에 인터럽트 컨트롤러 노드를 가리키게한다.
    • desc->interrupt_parent에 상위 인터럽트 컨트롤러 노드를 가리키게 한다. 만일 상위 인터럽트 컨트롤러가 자기 자신을 가리키면 null을 대입한다.
    • 참고: 상위 인터럽트 컨트롤러를 찾는 법
      • “interrupt-parent” 속성이 위치한 노드가 가리키는 phandle 값을 알아온 후 그 값으로 노드를 검색하여 알아온다.
  • 코드 라인 35에서 intc_desc_list 임시 리스트의 후미에 할당받은 구조체를 추가한다.

 

drivers/of/irq.c -2/2-

        /*
         * The root irq controller is the one without an interrupt-parent.
         * That one goes first, followed by the controllers that reference it,
         * followed by the ones that reference the 2nd level controllers, etc.
         */
        while (!list_empty(&intc_desc_list)) {
                /*
                 * Process all controllers with the current 'parent'.
                 * First pass will be looking for NULL as the parent.
                 * The assumption is that NULL parent means a root controller.
                 */
                list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
                        int ret;

                        if (desc->interrupt_parent != parent)
                                continue;

                        list_del(&desc->list);

                        of_node_set_flag(desc->dev, OF_POPULATED);

                        pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
                                 desc->dev,
                                 desc->dev, desc->interrupt_parent);
                        ret = desc->irq_init_cb(desc->dev,
                                                desc->interrupt_parent);
                        if (ret) {
                                of_node_clear_flag(desc->dev, OF_POPULATED);
                                kfree(desc);
                                continue;
                        }

                        /*
                         * This one is now set up; add it to the parent list so
                         * its children can get processed in a subsequent pass.
                         */
                        list_add_tail(&desc->list, &intc_parent_list);
                }

                /* Get the next pending parent that might have children */
                desc = list_first_entry_or_null(&intc_parent_list,
                                                typeof(*desc), list);
                if (!desc) {
                        pr_err("of_irq_init: children remain, but no parents\n");
                        break;
                }
                list_del(&desc->list);
                parent = desc->dev;
                kfree(desc);
        }

        list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
                list_del(&desc->list);
                kfree(desc);
        }
err:
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
                list_del(&desc->list);
                of_node_put(desc->dev);
                kfree(desc);
        }
}
  • 코드 라인 6에서 intc_desc_list 리스트의 모든 엔트리가 없어질 때까지 루프를 돈다.
  • 코드 라인 12에서 intc_desc_list 리스트의 desc 엔트리에 대해 루프를 돈다.
  • 코드 라인 15~16에서 남은 인터럽트 컨트롤러 중 최상위가 아닌 경우 skip 한다.
  • 코드 라인 18~20에서 initc_desc_list에서 하나를 제거하고, OF_POPULATED 플래그를 설정한다.
  • 코드 라인 22~24에서 매치되어 사용할 인터럽트 컨트롤러 드라이버 이름과 주소 그리고 부모 인터럽트 컨트롤러 주소 정보를 출력한다.
    • “of_irq_init: init <driver_name> @ <addr>, parent <addr>”
  • 코드 라인 25~31에서 매치된 of_device_id 구조체의 data에서 인터럽트 컨트롤러 드라이버 시작 함수를 호출한다.
  • 코드 라인 37에서 초기화 성공한 인터럽트 컨트롤러 디스크립터 구조체 정보를 intc_parent_list 라는 이름의 임시 리스트에 추가한다.
    • 부모 없는 child 인터럽트 컨트롤러가 있는지 오류 체크를 위함
  • 코드 라인 41~46에서 초기화 성공한 인터럽트 컨트롤러가 없으면 “of_irq_init: children remain, but no parents” 메시지를 출력하고 처리를 중단한다.
    • A: 조부모 IC <- B: 부모 IC, C: 아들 IC 구성 시
      • A-> B -> C 순서대로 초기화되어야 한다.
  • 코드 라인 47~49에서 임시 변수 parent에 처리한 인터럽트 컨트롤러 노드를 가리키게 하고 intc_desc_list 라는 이름의 임시 리스트에서 제거한다.
  • 코드 라인 52~61에서 임시로 사용했던 intc_parent_list 및 intc_desc_list에 등록된 desc 구조체를 모두 할당 해제한다.

 

다음 그림은 2개의 인터럽트 컨트롤러에 대해 상위 인터럽트 컨트롤러의 초기화 함수부터 호출되는 것을 보여준다.

 

irqchip 테이블

__irqchip_of_table

  • .init.data 섹션에서 .dtb.init.rodata 바로 뒤에 위치한다.
  • 엔트리들은 of_device_id 구조체로 구성된다.
  • __irqchip_of_table = .; *(__irqchip_of_table) *(__irqchip_of_table_end)
    • arch/arm64/kernel/vmlinux.lds

 

드라이버 내에서 irqchip 초기화 선언

case 1) rock960용 gic-v3 드라이버

gic-v3 드라이버는 다음 기본 드라이버 이외에 시스템 구성에 따라 gic-v3-its, gic-v3-mbi 등 다양한 드라이버가 추가 호출될 수 있다.

drivers/irqchip/irq-gic-v3.c

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
  • __irqchip_of_table 섹션 위치에 of_device_id 구조체가 다음과 같이 정의 된다.
    • .compatible = “arm,gic-v3”
    • .data = gic_of_init()

 

case 2) rpi2용 2 개의 인터럽트 컨트롤러 드라이버

rpi2용 인터럽트 컨트롤러 드라이버는 두 개의 드라이버를 사용한다.

drivers/irqchip/irq-bcm2835.c

IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", armctrl_of_init);

 

drivers/irqchip/irq-bcm2836.c

IRQCHIP_DECLARE(bcm2836_arm_irqchip_l1_intc, "brcm,bcm2836-l1-intc",
                bcm2836_arm_irqchip_l1_intc_of_init);

 

IRQCHIP_DECLARE()

drivers/irqchip/irqchip.h

/*
 * This macro must be used by the different irqchip drivers to declare
 * the association between their DT compatible string and their
 * initialization function.
 *      
 * @name: name that must be unique accross all IRQCHIP_DECLARE of the
 * same file.
 * @compstr: compatible string of the irqchip driver
 * @fn: initialization function
 */
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

irq_chip 디바이스 드라이버를 구성할 때 사용하는 선언문으로 여기서 만들어진 of_device_id 구조체는 __irqchip_of_table 섹션에 등록된다.

 

include/linux/of.h

#define OF_DECLARE_2(table, name, compat, fn) \
                _OF_DECLARE(table, name, compat, fn, of_init_fn_2)

 

include/linux/of.h

#define _OF_DECLARE(table, name, compat, fn, fn_type)                   \
        static const struct of_device_id __of_table_##name              \
                __used __section(__##table##_of_table)                  \
                 = { .compatible = compat,                              \
                     .data = (fn == (fn_type)NULL) ? fn : fn  }

 


Chip 관련 설정

하나의 irq 디스크립터에 담당 Chip 지정

irq_set_chip()

kernel/irq/chip.c

/**
 *      irq_set_chip - set the irq chip for an irq
 *      @irq:   irq number
 *      @chip:  pointer to irq chip description structure
 */
int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);

        if (!desc)
                return -EINVAL;

        if (!chip)
                chip = &no_irq_chip;

        desc->irq_data.chip = chip;
        irq_put_desc_unlock(desc, flags);
        /*
         * For !CONFIG_SPARSE_IRQ make the irq show up in
         * allocated_irqs.
         */
        irq_mark_irq(irq);
        return 0;
}
EXPORT_SYMBOL(irq_set_chip);

irq 디스크립터에 해당 irq를 마스킹 또는 set/clear 동작을 시킬 수 있는 irq chip 정보를 설정한다.

  • 코드 라인 9~10에서 만일 인터럽트 컨트롤러 chip을 사용하지 않은 경우  no_irq_chip(name=”none”) 구조체 정보를 대신 대입한다.
  • 코드 라인 12에서 irq 디스크립터의 chip_data에 chip 정보를 기록한다.
  • 코드 라인 18에서 Sparse IRQ를 사용하지 않는 경우allocated_irqs 비트맵에 요청 irq에 해당하는 비트를 mark하여 irq가 사용됨을 표시한다.

 

irq_mark_irq()

kernel/irq/irqdesc.c

void irq_mark_irq(unsigned int irq)
{       
        mutex_lock(&sparse_irq_lock);
        bitmap_set(allocated_irqs, irq, 1);
        mutex_unlock(&sparse_irq_lock);
}

Sparse IRQ를 사용하지 않는 경우 전역 allocated_irqs 비트맵을 사용하여 irq 번호에 해당하는 비트를 설정하여 irq가 사용됨을 표시한다.

  • ARM64 시스템은 디폴트로 CONFIG_SPARSE_IRQ를 사용하므로 위의 비트맵 관리를 하지 않는다.

 

 


Generic Core 인터럽트 핸들러

irq 디스크립터 기반으로 핸들러를 지정하고 인터럽트 발생 시 이들이 호출될 수 있는 generic 인터럽트 핸들링을 할 수 있는 API들이 준비되어 있다.

 

다음 4단계의 인터럽트 핸들러 처리 루틴을 알아본다.

  • CPU Exception(or 인터럽트) 벡터
    • 인터럽트 발생 시 cpu에 처음 진입부
  • 인터럽트 Chip 핸들러
    • 인터럽트 발생 시 담당 인터럽트 컨트롤러가 최초 처리할 핸들러
  • Flow Handler
    • 각 인터럽트 번호마다 인터럽트 flow 유형별 처리를 지정하는데, 보통 커널에 마련된 generic default flow 핸들러를 사용하거, custom 핸들러를 등록하여 사용한다.
  • 최종 디바이스의 인터럽트 핸들러
    • 최종 사용자 디바이스 드라이버에서 해당 인터럽트를 처리하기 위한 핸들러

 

다음 그림은 단계별로 나뉜 인터럽트 핸들러가 처리되는 과정을 보여준다.

 

다음 그림은 GIC v3 인터럽트 컨트롤러를 사용하여 단계별로 인터럽트 핸들러가 처리되는 과정을 보여준다.

 

다음 그림은 RPI2용 두 개의 인터럽트 컨트롤러가 cascade 연결 구성하여 단계별로 인터럽트 핸들러가 처리되는 과정을 보여준다.

 

다음 그림은 GIC v3 인터럽트 컨트롤러에서 ppi 중 특정 인터럽트가 irq 파티션으로 cascade 연결 구성하여 단계별로 인터럽트 핸들러가 처리되는 과정을 보여준다.

 

irq별 flow 핸들러 설정

irq_set_handler()

include/linux/irq.h

static inline void
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
        __irq_set_handler(irq, handle, 0, NULL);
}

요청한 @irq에 대해 사용될 인터럽트 핸들러를 설정한다.

 

chained irq flow 핸들러 설정

irq_set_chained_handler()

include/linux/irq.h

/*
 * Set a highlevel chained flow handler for a given IRQ.
 * (a chained handler is automatically enabled and set to
 *  IRQ_NOREQUEST, IRQ_NOPROBE, and IRQ_NOTHREAD) 
 */
static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
        __irq_set_handler(irq, handle, 1, NULL);
}

요청한 @irq에 대해 하위 cascade 연결된 인터럽트 컨트롤러에게 인터럽트를 전달할 목적으로 사용되는 chained 핸들러를 설정한다.

  • rpi2: 로컬 인터럽트 컨트롤러가 부모이고 hwirq=8에 chained 핸들러를 설정한다.
    • chained handler: bcm2836_chained_handle_irq()

 

__irq_set_handler()

kernel/irq/chip.c

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                  const char *name)
{
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

        if (!desc)
                return;

        __irq_do_set_handler(desc, handle, is_chained, name);
        irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);

@irq에 대해 irq 디스크립터에 대한 lock을 획득한채로 irq flow 핸들러를 지정한다.

  • 코드 라인 6~9에서 irq 디스크립터에 대한 lock을 획득한다.
    • 세 번째 인자에 대해 0을 지정하여 칩에 대한 락은 획득하지 않는다.
  • 코드 라인 11에서 irq 핸들러를 설정하기 위해 호출한다.
  • 코드 라인 12에서 irq 디스크립터에 대한 lock을 해제한다.

 

__irq_do_set_handler()

kernel/irq/chip.c

static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
                     int is_chained, const char *name)
{
        if (!handle) {
                handle = handle_bad_irq;
        } else {
                struct irq_data *irq_data = &desc->irq_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
                /*
                 * With hierarchical domains we might run into a
                 * situation where the outermost chip is not yet set
                 * up, but the inner chips are there.  Instead of
                 * bailing we install the handler, but obviously we
                 * cannot enable/startup the interrupt at this point.
                 */
                while (irq_data) {
                        if (irq_data->chip != &no_irq_chip)
                                break;
                        /*
                         * Bail out if the outer chip is not set up
                         * and the interrupt supposed to be started
                         * right away.
                         */
                        if (WARN_ON(is_chained))
                                return;
                        /* Try the parent */
                        irq_data = irq_data->parent_data;
                }
#endif
                if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
                        return;
        }

        /* Uninstall? */
        if (handle == handle_bad_irq) {
                if (desc->irq_data.chip != &no_irq_chip)
                        mask_ack_irq(desc);
                irq_state_set_disabled(desc);
                if (is_chained)
                        desc->action = NULL;
                desc->depth = 1;
        }
        desc->handle_irq = handle;
        desc->name = name;

        if (handle != handle_bad_irq && is_chained) {
                unsigned int type = irqd_get_trigger_type(&desc->irq_data);

                /*
                 * We're about to start this interrupt immediately,
                 * hence the need to set the trigger configuration.
                 * But the .set_type callback may have overridden the
                 * flow handler, ignoring that we're dealing with a
                 * chained interrupt. Reset it immediately because we
                 * do know better.
                 */
                if (type != IRQ_TYPE_NONE) {
                        __irq_set_trigger(desc, type);
                        desc->handle_irq = handle;
                }

                irq_settings_set_noprobe(desc);
                irq_settings_set_norequest(desc);
                irq_settings_set_nothread(desc);
                desc->action = &chained_action;
                irq_activate_and_startup(desc, IRQ_RESEND);
        }
}

요청한 irq 디스크립터에 인터럽트 flow 핸들러와 이름을 대입하고 activate 한다.

  • 코드 라인 5~6에서 인자 @handle이 지정되지 않은 경우 인터럽트 핸들러로 handle_bad_irq() 함수를 대신 사용한다.
  • 코드 라인 7~33에서 커널이 irq 도메인 hierarchy를 지원하는 경우 핸들러가 설정되지 않은 최상위 irq 디스크립터를 찾는다.
    • 2 개 이상의 인터럽트 컨트롤러 칩을 거치는 경우 irq_data가 parent irq_data에 연결되어 구성된다.
  • 코드 라인 36~43에서 지정된 핸들러 함수가 없는 경우 irq 상태를 disable로 변경하고 depth=1로 설정한다. 기존에 chip 정보가 지정된 경우 irq를 mask 상태로 바꾼다.
  • 코드 라인 44~45에서 핸들러 함수와 이름을 대입한다.
  • 코드 라인 47~68에서 핸들러 함수가 지정되었고 chain 걸린 경우 irq 상태를 noprobe, norequest, nothread로 바꾸고 irq를 enable하고 activate 한다.
    • irq_desc->irq_data->state_use_accessors에 _IRQ_NOPROBE, _IRQ_NOREQUEST, _IRQ_NOTHREAD 플래그를 추가한다.

 

chip 및 IRQ 핸들러 설정

irq_set_chip_and_handler()

include/linux/irq.h

static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *chip,
                                            irq_flow_handler_t handle)
{
        irq_set_chip_and_handler_name(irq, chip, handle, NULL);
}

irq 디스크립터에 chip, flow handler를 대입한다. 단 이름(name)에는 null을 대입한다.

 

irq_set_chip_and_handler_name()

kernel/irq/chip.c

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                              irq_flow_handler_t handle, const char *name)
{
        irq_set_chip(irq, chip);
        __irq_set_handler(irq, handle, 0, name);
}
EXPORT_SYMBOL_GPL(irq_set_chip_and_handler_name);

irq 디스크립터에 chip 정보를 대입한다. 그리고 flow 핸들러와 이름을 대입하다.

 

per-cpu 지원  IRQ 설정

irq_set_percpu_devid()

kernel/irq/irqdesc.c

int irq_set_percpu_devid(unsigned int irq)
{
        return irq_set_percpu_devid_partition(irq, NULL);
}

@irq 번호에 대해 per-cpu 인터럽트로 설정한다.

  • GIC 컨트롤러등에서 SGI/PPI 인터럽트들은 같은 번호로 대응하는 모든 cpu에 대해 처리할 수 있다.
  • NULL을 입력하는 경우 모든 cpu들을 대상으로 지정한다.

 

irq_set_percpu_devid_partition()

kernel/irq/irqdesc.c

int irq_set_percpu_devid_partition(unsigned int irq,
                                   const struct cpumask *affinity)
{
        struct irq_desc *desc = irq_to_desc(irq);

        if (!desc)
                return -EINVAL;

        if (desc->percpu_enabled)
                return -EINVAL;

        desc->percpu_enabled = kzalloc(sizeof(*desc->percpu_enabled), GFP_KERNEL);

        if (!desc->percpu_enabled)
                return -ENOMEM;

        if (affinity)
                desc->percpu_affinity = affinity;
        else
                desc->percpu_affinity = cpu_possible_mask;

        irq_set_percpu_devid_flags(irq);
        return 0;
}

요청 irq를 per-cpu(코어별로 라우팅되는) 처리 용도로 준비한다. (@affinity가 null인 경우 모든 possible cpu에 대해 지정한다)

  • 코드 라인 4~7에서 요청 irq번호에 맞는 irq 디스크립터가 없는 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 9~10에서 이미 per-cpu로 설정된 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 12~15에서 cpu 비트맵을 할당하여 percpu_enabled에 대입한다. 할당이 실패한 경우 -ENOMEM 에러를 반환한다.
  • 코드 라인 17~20에서 @affinity 설정이 있으면 해당 affinity cpu들에 대해 지정하고, null인 경우 모든 possible cpu를 대상으로 지정한다.
  • 코드 라인 22에서 irqd에 noautoen, per_cpu, nothread, noprobe, per_cpu_devid 플래그를 설정한다.
  • 코드 라인 23에서 정상 값 0을 반환한다.

참고: genirq: Add support for per-cpu dev_id interrupts 

 

irq_set_percpu_devid_flags()

include/linux/irq.h

static inline void irq_set_percpu_devid_flags(unsigned int irq)
{
        irq_set_status_flags(irq,
                             IRQ_NOAUTOEN | IRQ_PER_CPU | IRQ_NOTHREAD |
                             IRQ_NOPROBE | IRQ_PER_CPU_DEVID);
}

irq 상태로 noautoen, per_cpu, nothread, noprobe, per_cpu_devid 플래그를 설정한다.

 


generic 인터럽트 flow 핸들러 수행

generic_handle_irq()

kernel/irq/irqdesc.c

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:        The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
        struct irq_desc *desc = irq_to_desc(irq);

        if (!desc)
                return -EINVAL;
        generic_handle_irq_desc(irq, desc);
        return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

요청한 @irq 번호에 해당하는 generic 핸들러 함수를 호출한다.

 

generic_handle_irq_desc()

include/linux/irqdesc.h

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
        desc->handle_irq(irq, desc);
}

요청한 @irq 디스크립터에 등록한 generic 핸들러 함수를 호출한다.

 

다양한 generic 인터럽트 flow 핸들러들

generic 인터럽트 핸들러가 각각의 형태별로 미리 준비되어 있다. 이들을 간단히 알아보자.

  • handle_percpu_irq()
    • per-cpu용 인터럽트 핸들러
    • ack -> handle_irq_event_percpu() 호출 -> eoi
  • handle_level_irq()
    • 레벨 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • mask_ack -> handle_irq_event() 호출 -> unmask(oneshot 스레드 완료시)
  • handle_simple_irq()
    • 별도의 ack, mask, unmask, eoi 등의 HW 처리가 전혀 필요 없는 핸들러이다.
    • handle_irq_event() 호출
  • handle_fasteoi_irq()
    • transparent 컨트롤러들을 위한 핸들러
    • mask(oneshot 일때) -> preflow 핸들러 호출 -> handle_irq_event() 호출 -> eoiunmask(oneshot 스레드 완료시)
  • handle_edge_irq()
    • 엣지 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • handle_level_irq()와 다른 점은 mask/unmask 처리를 하지 않는다.
    • ack -> handle_irq_event() 호출
  • handle_edge_eoi_irq()
    • 엣지 eoi 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • powerpc 아키텍처에서만 사용하는 특수한 형태이다.
    • handle_irq_event() 호출 -> eoi
  •  handle_nested_irq()
    •  irq thread로부터의 nested irq 핸들러
    • action->(*thread_fn) 호출
  • handle_percpu_devid_irq()
    • per-cpu용 인터럽트 핸들러
    • ack -> action->(*handler) 호출 -> eoi
  • handle_fasteoi_nmi()
    • Pesudo-NMI로 사용되는 인터럽트 핸들러
    • -> action->(*handler) 호출 -> eoi
  • handle_percpu_devid_fasteoi_nmi()
    • Pesudo-NMI로 사용되는 per-cpu 타입 인터럽트 핸들러
    • -> action->(*handler) 호출 -> eoi

 

다음 그림은 커널에 준비된 generic 인터럽트 핸들러들의 기능을 보여준다.

 

다음 그림은 타이머 인터럽트 핸들러가 호출되는 과정을 보여준다.

  • 1단계: exception vector
  • 2단계: controller 핸들러
  • 3단계: irq 라인(디스크립터)에 설정된 generic core 인터럽트 핸들러 (hierarchy 가능)
  • 4단계: 최종 consumer 디바이스에 위치한 인터럽트 핸들러(action에 등록된)
  • 5단계(option): consumer 디바이스는 별도의 dispatch 이벤트 핸들러를 사용하기도 한다.

 

핸들러 함수 -1- (level)

handle_level_irq()

kernel/irq/chip.c

/**
 *      handle_level_irq - Level type irq handler
 *      @irq:   the interrupt number
 *      @desc:  the interrupt description structure for this irq
 *
 *      Level type interrupts are active as long as the hardware line has
 *      the active level. This may require to mask the interrupt and unmask
 *      it after the associated handler has acknowledged the device, so the
 *      interrupt line is back to inactive.
 */
void    
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);
        mask_ack_irq(desc);

        if (!irq_may_run(desc))
                goto out_unlock;

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
        kstat_incr_irqs_this_cpu(irq, desc);

        /*
         * If its disabled or no action available
         * keep it masked and get out of here
         */ 
        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                goto out_unlock;
        }                               
                                        
        handle_irq_event(desc);
                        
        cond_unmask_irq(desc);

out_unlock:                
        raw_spin_unlock(&desc->lock);
}               
EXPORT_SYMBOL_GPL(handle_level_irq);

레벨 타입의 트리거를 가진 인터럽트 핸들러로 인터럽트 처리 전에 mask와 ack 처리를 수행하고 인터럽트 처리 후에 조건부로 unmask를 수행한다.

  • 코드 라인 5에서 인터럽트 컨트롤러를 통해 mask와 ack 처리를 한다.
  • 코드 라인 7~8에서 irq의 실행이 곧 준비되지 않을 것 같으면 인터럽트 처리를 포기하고 빠져나간다.
  • 코드 라인 10에서 istate에서 replay 및 waiting 플래그를 제거한다.
  • 코드 라인 11에서 해당 인터럽트 카운터를 증가시킨다.
  • 코드 라인 17~20에서 irq 디스크립터에 action 핸들러가 없거나 disable 설정된 경우 istate에 pending 플래그를 추가하고 함수를 빠져나간다.
  • 코드 라인 22에서 irq 디스크립터의 istate에서 pending 플래그를 제거하고, 모든 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다. 수행 중인 동안에는 irq 진행중 상태가 유지된다.
  • 코드 라인 24에서 조건에 따라 unmask 처리를 수행한다.

 

핸들러 함수 -2- (percpu, devid)

handle_percpu_devid_irq()

kernel/irq/chip.c

/**
 * handle_percpu_devid_irq - Per CPU local irq handler with per cpu dev ids
 * @desc:       the interrupt description structure for this irq
 *
 * Per CPU interrupts on SMP machines without locking requirements. Same as
 * handle_percpu_irq() above but with the following extras:
 *
 * action->percpu_dev_id is a pointer to percpu variables which
 * contain the real device id for the cpu on which this handler is
 * called
 */
void handle_percpu_devid_irq(struct irq_desc *desc)
{
        struct irq_chip *chip = irq_desc_get_chip(desc);
        struct irqaction *action = desc->action;
        unsigned int irq = irq_desc_get_irq(desc);
        irqreturn_t res;

        /*
         * PER CPU interrupts are not serialized. Do not touch
         * desc->tot_count.
         */
        __kstat_incr_irqs_this_cpu(desc);

        if (chip->irq_ack)
                chip->irq_ack(&desc->irq_data);

        if (likely(action)) {
                trace_irq_handler_entry(irq, action);
                res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
                trace_irq_handler_exit(irq, action, res);
        } else {
                unsigned int cpu = smp_processor_id();
                bool enabled = cpumask_test_cpu(cpu, desc->percpu_enabled);

                if (enabled)
                        irq_percpu_disable(desc, cpu);

                pr_err_once("Spurious%s percpu IRQ%u on CPU%u\n",
                            enabled ? " and unmasked" : "", irq, cpu);
        }

        if (chip->irq_eoi)
                chip->irq_eoi(&desc->irq_data);
}

요청 irq의 generic per-cpu 핸들러로, action에 등록한 핸들러 함수를 호출 시 irq의 현재 cpu에 대한 dev_id를 인수로 전달한다. 핸들러 함수 호출 전후로 ack 및 eoi 처리를 한다.

  • 코드 라인 17에서 요청한 irq 디스크립터의 action에 등록한 per-cpu dev_id 값을 알아온다.
  • 코드 라인 12에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 14~15에서 핸들러 함수 호출 전에 irq_chip의 irq_ack 콜백이 등록된 경우 호출한다.
  • 코드 라인 17~30에서 등록된 action 핸들러 함수를 호출한다. 호출 시 irq 번호와 dev_id를 인수로 넘겨준다. 현재 cpu에 해당하는 action 핸들러 함수가 없는 경우 disable 한다.
  • 코드 라인 32~33에서 핸들러 함수 호출 후에 irq_chip의 irq_eoi 콜백이 등록된 경우 호출한다.

 

핸들러 함수 -3- (fasteoi)

handle_fasteoi_irq()

kernel/irq/chip.c

/**
 *      handle_fasteoi_irq - irq handler for transparent controllers
 *      @desc:  the interrupt description structure for this irq
 *
 *      Only a single callback will be issued to the chip: an ->eoi()
 *      call when the interrupt has been serviced. This enables support
 *      for modern forms of interrupt handlers, which handle the flow
 *      details in hardware, transparently.
 */
void handle_fasteoi_irq(struct irq_desc *desc)
{
        struct irq_chip *chip = desc->irq_data.chip;

        raw_spin_lock(&desc->lock);

        if (!irq_may_run(desc))
                goto out;

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

        /*
         * If its disabled or no action available
         * then mask it and get out of here:
         */
        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                mask_irq(desc);
                goto out;
        }

        kstat_incr_irqs_this_cpu(desc);
        if (desc->istate & IRQS_ONESHOT)
                mask_irq(desc);

        preflow_handler(desc);
        handle_irq_event(desc);

        cond_unmask_eoi_irq(desc, chip);

        raw_spin_unlock(&desc->lock);
        return;
out:
        if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
                chip->irq_eoi(&desc->irq_data);
        raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_fasteoi_irq);

해당 인터럽트에 등록된 action 핸들러를 수행하고 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

  • 코드 라인 7~8에서 인터럽트를 처리할 수 있는 상황이 아닌 경우 out: 레이블로 이동하여 eoi 처리만 수행하고 함수를 빠져나간다.
  • 코드 라인 10에서 인터럽트 state에서 IRQS_REPLAY와 IRQS_WAITING을 제거한다.
  • 코드 라인 16~20에서 action 핸들러가 지정되지 않았거나 인터럽트 설정이 disable된 경우 인터럽트 state에 IRQS_PENDING을 추가하고 컨트롤러에 해당 인터럽트를 mask 한 후 out: 레이블로 이동하여 eoi 처리만 수행하고 함수를 빠져나간다.
  • 코드 라인 22에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 23~24에서 oneshot 인터럽트인 경우 해당 인터럽트를 mask 한다.
  • 코드 라인 26에서 preflow 핸들러가 설정된 경우 이를 호출한다.
  • 코드 라인 27에서 해당 인터럽트에 등록된 action 핸들러를 호출한다.
  • 코드 라인 29~32에서 몇 가지 조건에 따라 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한 후 함수를 정상적으로 완료한다.
  • 코드 라인 33~35에서 out: 레이블이다. IRQCHIP_EOI_IF_HANDLED 플래그 설정이 없는 경우 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

 

핸들러 함수 -4- (fasteoi, NMI)

handle_fasteoi_nmi()

kernel/irq/chip.c

/**
 *      handle_fasteoi_nmi - irq handler for NMI interrupt lines
 *      @desc:  the interrupt description structure for this irq
 *
 *      A simple NMI-safe handler, considering the restrictions
 *      from request_nmi.
 *
 *      Only a single callback will be issued to the chip: an ->eoi()
 *      call when the interrupt has been serviced. This enables support
 *      for modern forms of interrupt handlers, which handle the flow
 *      details in hardware, transparently.
 */
void handle_fasteoi_nmi(struct irq_desc *desc)
{
        struct irq_chip *chip = irq_desc_get_chip(desc);
        struct irqaction *action = desc->action;
        unsigned int irq = irq_desc_get_irq(desc);
        irqreturn_t res;

        __kstat_incr_irqs_this_cpu(desc);

        trace_irq_handler_entry(irq, action);
        /*
         * NMIs cannot be shared, there is only one action.
         */
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);

        if (chip->irq_eoi)
                chip->irq_eoi(&desc->irq_data);
}
EXPORT_SYMBOL_GPL(handle_fasteoi_nmi);

해당 인터럽트에 등록된 action 핸들러를 수행하고 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

  • 코드 라인 8에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 14에서 해당 인터럽트에 등록된 action 핸들러를 호출한다.
  • 코드 라인 17~18에서 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

 

핸들러 함수 -5- (percpu, devid, fasteoi, NMI)

handle_percpu_devid_fasteoi_nmi()

kernel/irq/chip.c

/**
 * handle_percpu_devid_fasteoi_nmi - Per CPU local NMI handler with per cpu
 *                                   dev ids
 * @desc:       the interrupt description structure for this irq
 *
 * Similar to handle_fasteoi_nmi, but handling the dev_id cookie
 * as a percpu pointer.
 */
void handle_percpu_devid_fasteoi_nmi(struct irq_desc *desc)
{
        struct irq_chip *chip = irq_desc_get_chip(desc);
        struct irqaction *action = desc->action;
        unsigned int irq = irq_desc_get_irq(desc);
        irqreturn_t res;

        __kstat_incr_irqs_this_cpu(desc);

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
        trace_irq_handler_exit(irq, action, res);

        if (chip->irq_eoi)
                chip->irq_eoi(&desc->irq_data);
}

해당 per-cpu 인터럽트에 등록된 action 핸들러를 수행하고 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

  • 코드 라인 8에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 11에서 해당 인터럽트에 등록된 action 핸들러를 호출한다.
  • 코드 라인 14~15에서 eoi 처리를 위해 irq_chip 드라이버의 (*irq_eoi) 후크 함수를 호출한다.

 

핸들러 함수 -6- (simple)

handle_simple_irq()

kernel/irq/chip.c

/**
 *      handle_simple_irq - Simple and software-decoded IRQs.
 *      @desc:  the interrupt description structure for this irq
 *
 *      Simple interrupts are either sent from a demultiplexing interrupt
 *      handler or come from hardware, where no interrupt hardware control
 *      is necessary.
 *
 *      Note: The caller is expected to handle the ack, clear, mask and
 *      unmask issues if necessary.
 */
void handle_simple_irq(struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);

        if (!irq_may_run(desc))
                goto out_unlock;

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                goto out_unlock;
        }

        kstat_incr_irqs_this_cpu(desc);
        handle_irq_event(desc);

out_unlock:
        raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_simple_irq);

해당 인터럽트에 등록된 action 핸들러를 수행한다. 단 ack 및 eoi 등의 hw 제어는 하나도 처리하지 않는다.

  • 코드 라인 5~6에서 인터럽트를 처리할 수 있는 상황이 아닌 경우 out_unlock: 레이블로 이동하여 함수를 빠져나간다.
  • 코드 라인 8에서 인터럽트 state에서 IRQS_REPLAY와 IRQS_WAITING을 제거한다.
  • 코드 라인 10~13에서 action 핸들러가 지정되지 않았거나 인터럽트 설정이 disable된 경우 인터럽트 state에 IRQS_PENDING을 추가하고 out_unlock 레이블로 이동하여 함수를 빠져나간다.
  • 코드 라인 15에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 16에서 해당 인터럽트에 등록된 action 핸들러를 호출한다.

 

핸들러 함수 -7- (edge)

handle_edge_irq()

kernel/irq/chip.c

/**
 *      handle_edge_irq - edge type IRQ handler
 *      @desc:  the interrupt description structure for this irq
 *
 *      Interrupt occures on the falling and/or rising edge of a hardware
 *      signal. The occurrence is latched into the irq controller hardware
 *      and must be acked in order to be reenabled. After the ack another
 *      interrupt can happen on the same source even before the first one
 *      is handled by the associated event handler. If this happens it
 *      might be necessary to disable (mask) the interrupt depending on the
 *      controller hardware. This requires to reenable the interrupt inside
 *      of the loop which handles the interrupts which have arrived while
 *      the handler was running. If all pending interrupts are handled, the
 *      loop is left.
 */
void handle_edge_irq(struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

        if (!irq_may_run(desc)) {
                desc->istate |= IRQS_PENDING;
                mask_ack_irq(desc);
                goto out_unlock;
        }

        /*
         * If its disabled or no action available then mask it and get
         * out of here.
         */
        if (irqd_irq_disabled(&desc->irq_data) || !desc->action) {
                desc->istate |= IRQS_PENDING;
                mask_ack_irq(desc);
                goto out_unlock;
        }

        kstat_incr_irqs_this_cpu(desc);

        /* Start handling the irq */
        desc->irq_data.chip->irq_ack(&desc->irq_data);

        do {
                if (unlikely(!desc->action)) {
                        mask_irq(desc);
                        goto out_unlock;
                }

                /*
                 * When another irq arrived while we were handling
                 * one, we could have masked the irq.
                 * Renable it, if it was not disabled in meantime.
                 */
                if (unlikely(desc->istate & IRQS_PENDING)) {
                        if (!irqd_irq_disabled(&desc->irq_data) &&
                            irqd_irq_masked(&desc->irq_data))
                                unmask_irq(desc);
                }

                handle_irq_event(desc);

        } while ((desc->istate & IRQS_PENDING) &&
                 !irqd_irq_disabled(&desc->irq_data));

out_unlock:
        raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL(handle_edge_irq);

해당 edge 트리거 타입 인터럽트에 등록된 action 핸들러를 수행한다. action 핸들러 수행 전에 ack를 처리한다.

  • 코드 라인 5에서 인터럽트 state에서 IRQS_REPLAY와 IRQS_WAITING을 제거한다.
  • 코드 라인 7~11에서 인터럽트를 처리할 수 있는 상황이 아닌 경우 인터럽트 state에 IRQS_PENDING을 추가하고, 인터럽트를 mask 처리한 후 out_unlock: 레이블로 이동하여 함수를 빠져나간다.
  • 코드 라인 17~21에서 action 핸들러가 지정되지 않았거나 인터럽트 설정이 disable된 경우 인터럽트 state에 IRQS_PENDING을 추가하고, 인터럽트를 mask 처리한 후 out_unlock: 레이블로 이동하여 함수를 빠져나간다.
  • 코드 라인 23에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 26에서 ack 처리를 위해 irq_chip 드라이버의 (*irq_ack) 후크 함수를 호출한다.
  • 코드 라인 28~48에서 펜딩된 인터럽트가 있는 경우 반복하며 해당 인터럽트에 등록된 action 핸들러를 호출한다.
    • action 핸들러가 지정되지 않은 경우 인터럽트를 mask 처리한 후 out_unlock: 레이블로 이동하여 함수를 빠져나간다.
    • 펜딩된 irq가 있는데 irq가 mask된 상태인 경우 인터럽트를 unmask 처리한다.

 

핸들러 함수 -8- (nested)

handle_nested_irq()

kernel/irq/chip.c

/*
 *      handle_nested_irq - Handle a nested irq from a irq thread
 *      @irq:   the interrupt number
 *
 *      Handle interrupts which are nested into a threaded interrupt
 *      handler. The handler function is called inside the calling
 *      threads context.
 */
void handle_nested_irq(unsigned int irq)
{
        struct irq_desc *desc = irq_to_desc(irq);
        struct irqaction *action;
        irqreturn_t action_ret;

        might_sleep();

        raw_spin_lock_irq(&desc->lock);

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

        action = desc->action;
        if (unlikely(!action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                goto out_unlock;
        }

        kstat_incr_irqs_this_cpu(desc);
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        raw_spin_unlock_irq(&desc->lock);

        action_ret = IRQ_NONE;
        for_each_action_of_desc(desc, action)
                action_ret |= action->thread_fn(action->irq, action->dev_id);

        if (!noirqdebug)
                note_interrupt(desc, action_ret);

        raw_spin_lock_irq(&desc->lock);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

out_unlock:
        raw_spin_unlock_irq(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_nested_irq);

hw interrupt context가 아닌 irq 스레드 context에서 동작하며, 해당 인터럽트에 등록된 모든 action 스레드 핸들러를 수행한다.

  • 코드 라인 7에서 Preemption point를 위해 필요 시 슬립한다.
  • 코드 라인 11에서 인터럽트 state에서 IRQS_REPLAY와 IRQS_WAITING을 제거한다.
  • 코드 라인 13~17에서 action 핸들러가 지정되지 않았거나 인터럽트 설정이 disable된 경우 인터럽트 state에 IRQS_PENDING을 추가하고out_unlock: 레이블로 이동하여 함수를 빠져나간다.
  • 코드 라인 19에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 20에서 핸들러를 호출하는 동안 IRQD_IRQ_INPROGRESS 플래그를 설정한다.
  • 코드 라인 23~25에서 인터럽트에 등록된 모든 action 스레드 핸들러를 호출한다.
  • 코드 라인31에서 핸들러를 호출하는 동안 사용한 IRQD_IRQ_INPROGRESS 플래그를 클리어한다.

 

핸들러 공용 함수들

irq_may_run()

kernel/irq/chip.c

static bool irq_may_run(struct irq_desc *desc)
{
        unsigned int mask = IRQD_IRQ_INPROGRESS | IRQD_WAKEUP_ARMED;

        /*
         * If the interrupt is not in progress and is not an armed
         * wakeup interrupt, proceed.
         */
        if (!irqd_has_set(&desc->irq_data, mask))
                return true;

        /*
         * If the interrupt is an armed wakeup source, mark it pending
         * and suspended, disable it and notify the pm core about the
         * event.
         */
        if (irq_pm_check_wakeup(desc))
                return false;

        /*
         * Handle a potential concurrent poll on a different core.
         */
        return irq_check_poll(desc);
}

irq의 실행이 곧 준비될 것 같으면 기다린다. 만일 처리가 불가능하면 false를 반환하고, 인터럽트 처리가 가능한 경우 true를 반환한다.

  • 코드 라인 9~10에서 irq 처리가 진행중이거나 armed wakeup 인터럽트가 아닌 경우 true를 반환하여 인터럽트가 계속 진행되게 한다.
  • 코드 라인 17~18에서 CONFIG_PM_SLEEP 커널 옵션을 사용하는 경우 Power Management 기능을 사용할 수 있는데 suspend 하지 않은 경우 true를 반환한다.
    • armed wakeup 인터럽트인 경우 인터럽트 디스크립터의 istate 상태에 suspended 및 pending을 추가하고 깨울 때까지 대기한다.
  • 코드 라인 23에서 irq polling을 중단 설정하지 않은 경우 다른 cpu에서 irq polling을 수행하는 중이면 완료될 때까지 기다린다.

 

핸들러 최종 호출 단계들

handle_irq_event()

kernel/irq/handle.c

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
        irqreturn_t ret;

        desc->istate &= ~IRQS_PENDING;
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        raw_spin_unlock(&desc->lock);

        ret = handle_irq_event_percpu(desc, action);

        raw_spin_lock(&desc->lock);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        return ret;
}

percpu 타입이 아닌 일반 irq 디스크립터에 등록된 모든 action 핸들러들을 호출하여 수행한다.

  • irq 디스크립터의 istate에서 pending 플래그를 제거하고 모든 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다.
  • 수행 중인 동안에는 irq 진행중 상태가 유지된다.

 

handle_irq_event_percpu()

kernel/irq/handle.c

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
        irqreturn_t retval;
        unsigned int flags = 0;

        retval = __handle_irq_event_percpu(desc, &flags);

        add_interrupt_randomness(desc->irq_data.irq, flags);

        if (!noirqdebug)
                note_interrupt(desc, retval);
        return retval;
}

percpu 타입 irq 디스크립터에 등록된 모든 action 핸들러들을 호출하여 수행한다.

 

__handle_irq_event_percpu()

kernel/irq/handle.c

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
        irqreturn_t retval = IRQ_NONE;
        unsigned int irq = desc->irq_data.irq;
        struct irqaction *action;

        record_irq_time(desc);

        for_each_action_of_desc(desc, action) {
                irqreturn_t res;

                trace_irq_handler_entry(irq, action);
                res = action->handler(irq, action->dev_id);
                trace_irq_handler_exit(irq, action, res);

                if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
                              irq, action->handler))
                        local_irq_disable();

                switch (res) {
                case IRQ_WAKE_THREAD:
                        /*
                         * Catch drivers which return WAKE_THREAD but
                         * did not set up a thread function
                         */
                        if (unlikely(!action->thread_fn)) {
                                warn_no_thread(irq, action);
                                break;
                        }

                        __irq_wake_thread(desc, action);

                        /* Fall through - to add to randomness */
                case IRQ_HANDLED:
                        *flags |= action->flags;
                        break;

                default:
                        break;
                }

                retval |= res;
        }

        return retval;
}

percpu 타입 irq 디스크립터에 등록된 모든 action 핸들러들을 호출하여 수행한다.

  • 코드 라인 9~14에서 irq 디스크립터에 등록된 모든 action 핸들러를 순회하며 호출한다.
  • 코드 라인 16~18에서 핸들러 수행 후 local irq가 disable 상태를 유지하지 못한 경우 경고 메시지를 출력하고 local irq를 disable 한다.
    • “irq N handler <function> enabled interrupts” 경고 메시지
  • 코드 라인 20~31에서 처리 결과가 IRQ_WAKE_THREAD인 경우 thread를 깨우고 계속 다음 행을 수행한다.
  • 코드 라인 34~40에서 처리 결과가 IRQ_HANDLED인 경우 flags에 action->flags를 추가하고, 그 외의 처리 결과는 skip 한다.
  • 코드 라인 42에서 retval에 처리 결과를 누적시키고 더 처리할 action이 있는 경우 루프를 돈다.

 


IRQ chip Operations

IRQ startup & shutdown

irq_startup()

kernel/irq/chip.c

int irq_startup(struct irq_desc *desc, bool resend, bool force)
{
        struct irq_data *d = irq_desc_get_irq_data(desc);
        struct cpumask *aff = irq_data_get_affinity_mask(d);
        int ret = 0;

        desc->depth = 0;

        if (irqd_is_started(d)) {
                irq_enable(desc);
        } else {
                switch (__irq_startup_managed(desc, aff, force)) {
                case IRQ_STARTUP_NORMAL:
                        ret = __irq_startup(desc);
                        irq_setup_affinity(desc);
                        break;
                case IRQ_STARTUP_MANAGED:
                        irq_do_set_affinity(d, aff, false);
                        ret = __irq_startup(desc);
                        break;
                case IRQ_STARTUP_ABORT:
                        irqd_set_managed_shutdown(d);
                        return 0;
                }
        }
        if (resend)
                check_irq_resend(desc);

        return ret;
}

요청 irq에 대해 enable 시키고 activate 처리를 수행하며 관련 콜백 함수를 호출한다.

  • 코드 라인 9~10에서 이미 startup된 경우 irq만 enable 한다.
  • 코드 라인 11~25에서 managed 상태에 따라 다음과 같이 처리한다.
    • normal 상태로 시작하는 경우 요청 irq의 startup을 수행 후 affinity를 선택 및 처리한다.
    • managed 상태로 시작하는 경우 요청 irq의 affnity 처리 후 startup을 처리한다.
    • abort 인 경우 irqd에 IRQD_MANAGED_SHUTDOWN 플래그를 설정한다.
  • 코드 라인 26~27에서 인자 @resend가 설정된 경우 pending된 irq를 다시 호출하게 tasklet을 리스케쥴하다.

 

__irq_startup()

kernel/irq/chip.c

static int __irq_startup(struct irq_desc *desc)
{
        struct irq_data *d = irq_desc_get_irq_data(desc);
        int ret = 0;

        /* Warn if this interrupt is not activated but try nevertheless */
        WARN_ON_ONCE(!irqd_is_activated(d));

        if (d->chip->irq_startup) {
                ret = d->chip->irq_startup(d);
                irq_state_clr_disabled(desc);
                irq_state_clr_masked(desc);
        } else {
                irq_enable(desc);
        }
        irq_state_set_started(desc);
        return ret;
}

요청 irq의 startup을 처리한다.

  • 코드 라인 9~12에서 irq_startup 후크에 등록된 함수가 있는 경우 이를 호출하고 disabled와 masked 상태를 클리어한다.
  • 코드 라인 13~15에서 irq_startup 후크에 비어 있는 경우 enable 처리한다.
  • 코드 라인 16에서 irqd에 IRQD_IRQ_STARTED 플래그를 설정한다.

 

irq_shutdown()

kernel/irq/chip.c

void irq_shutdown(struct irq_desc *desc)
{
        if (irqd_is_started(&desc->irq_data)) {
                desc->depth = 1;
                if (desc->irq_data.chip->irq_shutdown) {
                        desc->irq_data.chip->irq_shutdown(&desc->irq_data);
                        irq_state_set_disabled(desc);
                        irq_state_set_masked(desc);
                } else {
                        __irq_disable(desc, true);
                }
                irq_state_clr_started(desc);
        }
}

요청 irq에 대해 shutdown 또는 disable 처리한다.

  • 코드 라인 5~8에서 (*irq_shutdonw) 후크가 있으면 shutdown 처리를 수행한 후, irqd에 IRQD_IRQ_DISABLED및 IRQD_IRQ_MASKED플래그를 설정한다.
  • 코드 라인 9~11에서 (*irq_shutdonw) 후크가 있으면 disable 처리를 수행한다.
  • 코드 라인 12에서 irqd에 IRQD_IRQ_STARTED 플래그를 클리어한다.

 

IRQ Enable & Disable

irq_enable()

kernel/irq/chip.c

void irq_enable(struct irq_desc *desc)
{
        if (!irqd_irq_disabled(&desc->irq_data)) {
                unmask_irq(desc);
        } else {
                irq_state_clr_disabled(desc);
                if (desc->irq_data.chip->irq_enable) {
                        desc->irq_data.chip->irq_enable(&desc->irq_data);
                        irq_state_clr_masked(desc);
                } else {
                        unmask_irq(desc);
                }
        }
}

요청 irq를 enable 한다.

  • 코드 라인 3~4에서 irqd가 disable 상태가 아니면 unmask 처리한다.
  • 코드 라인 5~13에서 IRQD_IRQ_DISABLED 플래그를 제거하고, (*irq_enable) 후크를 호출한 후 IRQD_IRQ_MASKED 플래그를 제거한다. 만일 (*irq_enable) 후크가 없는 경우 unmask 처리한다.

 

irq_disable()

kernel/irq/chip.c

/**
 * irq_disable - Mark interrupt disabled
 * @desc:       irq descriptor which should be disabled
 *
 * If the chip does not implement the irq_disable callback, we
 * use a lazy disable approach. That means we mark the interrupt
 * disabled, but leave the hardware unmasked. That's an
 * optimization because we avoid the hardware access for the
 * common case where no interrupt happens after we marked it
 * disabled. If an interrupt happens, then the interrupt flow
 * handler masks the line at the hardware level and marks it
 * pending.
 *
 * If the interrupt chip does not implement the irq_disable callback,
 * a driver can disable the lazy approach for a particular irq line by
 * calling 'irq_set_status_flags(irq, IRQ_DISABLE_UNLAZY)'. This can
 * be used for devices which cannot disable the interrupt at the
 * device level under certain circumstances and have to use
 * disable_irq[_nosync] instead.
 */
void irq_disable(struct irq_desc *desc)
{
        __irq_disable(desc, irq_settings_disable_unlazy(desc));
}

요청 irq를 disable 한다.

 

__irq_disable()

kernel/irq/chip.c

static void __irq_disable(struct irq_desc *desc, bool mask)
{
        if (irqd_irq_disabled(&desc->irq_data)) {
                if (mask)
                        mask_irq(desc);
        } else {
                irq_state_set_disabled(desc);
                if (desc->irq_data.chip->irq_disable) {
                        desc->irq_data.chip->irq_disable(&desc->irq_data);
                        irq_state_set_masked(desc);
                } else if (mask) {
                        mask_irq(desc);
                }
        }
}

요청 irq를 disable 한다.

  • 코드 라인 3~5에서 이미 disable 상태인 경우 @mask 설정이 있으면 mask 처리를 수행한다.
  • 코드 라인 6~14에서 IRQD_IRQ_DISABLED 플래그를 설정하고, (*irq_disable) 후크를 호출한 후 IRQD_IRQ_MASKED 플래그를 설정한다. 만일 (*irq_disable) 후크가 없는 경우 mask 처리한다.

 

IRQ mask & unmask

mask_irq()

kernel/irq/chip.c

void mask_irq(struct irq_desc *desc)
{
        if (irqd_irq_masked(&desc->irq_data))
                return;

        if (desc->irq_data.chip->irq_mask) {
                desc->irq_data.chip->irq_mask(&desc->irq_data);
                irq_state_set_masked(desc);
        }
}

요청 irq를 마스크한다.

  • 코드 라인 3~4에서 이미 인터럽트가 mask되어 있는 경우 함수를 빠져나간다.
  • 코드 라인 6~9에서 chip->irq_mask 후크에 등록된 콜백 함수를 호출하고 masked 플래그를 설정한다.

 

unmask_irq()

kernel/irq/chip.c

void unmask_irq(struct irq_desc *desc)
{
        if (!irqd_irq_masked(&desc->irq_data)) 
                return;

        if (desc->irq_data.chip->irq_unmask) {
                desc->irq_data.chip->irq_unmask(&desc->irq_data);
                irq_state_clr_masked(desc);
        }
}

요청 irq를 언마스크한다.

  • 코드 라인 3~4에서 이미 인터럽트가 unmask되어 있는 경우 함수를 빠져나간다.
  • 코드 라인 6~9에서 chip->irq_unmask 후크에 등록된 콜백 함수를 호출하고 masked 플래그를 클리어한다.

 

cond_unmask_irq()

kernel/irq/chip.c

/*
 * Called unconditionally from handle_level_irq() and only for oneshot
 * interrupts from handle_fasteoi_irq()
 */
static void cond_unmask_irq(struct irq_desc *desc)
{
        /*
         * We need to unmask in the following cases:
         * - Standard level irq (IRQF_ONESHOT is not set)
         * - Oneshot irq which did not wake the thread (caused by a
         *   spurious interrupt or a primary handler handling it
         *   completely).
         */
        if (!irqd_irq_disabled(&desc->irq_data) &&
            irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot)
                unmask_irq(desc);
}

조건에 따라 요청 irq를 언마스크한다.

  • 다음의 조건에서 unmask 처리를 수행하지 않는다.
    • 이미 인터럽트가 disable된 상태
    • 이미 인터럽트가 mask된 상태
    • oneshot 인터럽트의 경우
      • mask 상태를 유지하기 위해 unmask를 수행하지 않는다.

 

IRQ mask_ack & unmask_eoi

mask_ack_irq()

kernel/irq/chip.c

static inline void mask_ack_irq(struct irq_desc *desc)
{
        if (desc->irq_data.chip->irq_mask_ack) {
                desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
                irq_state_set_masked(desc);
        } else {
                mask_irq(desc);
                if (desc->irq_data.chip->irq_ack)
                        desc->irq_data.chip->irq_ack(&desc->irq_data);
        }
}

요청 irq에 대해 mask와 ack 처리를 하고 masked 플래그를 설정한다. 보통 같은 인터럽트에 대해 계속 진입되는 것을 방지가힉 위해 인터럽트 핸들러 초입에서 호출되며  마스크 처리와 응답 처리를 같이 수행하게 한다.

  • 코드 라인 3~5에서 인터럽트 컨트롤러의 irq_mask_ack 후크에 등록한 콜백 함수를 호출하고, irqd를 masked 상태로 설정한다.
  • 코드 라인 6~10에서 등록되지 않은 경우 mask 처리를 수행하고, irq_ack 후크에 등록된 콜백 함수를 연속해서 호출한다.

 

cond_unmask_eoi_irq()

kernel/irq/chip.c

static void cond_unmask_eoi_irq(struct irq_desc *desc, struct irq_chip *chip)
{
        if (!(desc->istate & IRQS_ONESHOT)) {
                chip->irq_eoi(&desc->irq_data);
                return;
        }
        /*
         * We need to unmask in the following cases:
         * - Oneshot irq which did not wake the thread (caused by a
         *   spurious interrupt or a primary handler handling it
         *   completely).
         */
        if (!irqd_irq_disabled(&desc->irq_data) &&
            irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot) {
                chip->irq_eoi(&desc->irq_data);
                unmask_irq(desc);
        } else if (!(chip->flags & IRQCHIP_EOI_THREADED)) {
                chip->irq_eoi(&desc->irq_data);
        }
}

요청 irq의 eoi 처리를 하고 조건에 따라 unmask 처리한다.

  • 코드 라인 3~6에서 인터럽트가 oneshot 설정되지 않은 경우 eoi 처리 후 함수를 빠져나간다.
  • 코드 라인 13~16에서 irq가 enable된 상태에서 mask되었고 oneshot 스레드 처리중이 아닌 경우 eoi 처리 및 unmask 처리를 한다.
    • 스레드가 완료되지 않고 동작중인 경우 unmask 처리하지 않는다.
  • 코드 라인 17~19에서 eoi 스레드 플래그가 설정되지 않은 경우 eoi 처리만 한다.

 

기타

irq_domain_activate_irq()

kernel/irq/irqdomain.c

/**
 * irq_domain_activate_irq - Call domain_ops->activate recursively to activate
 *                           interrupt
 * @irq_data:   Outermost irq_data associated with interrupt
 * @reserve:    If set only reserve an interrupt vector instead of assigning one
 *
 * This is the second step to call domain_ops->activate to program interrupt
 * controllers, so the interrupt could actually get delivered.
 */
int irq_domain_activate_irq(struct irq_data *irq_data, bool reserve)
{
        int ret = 0;

        if (!irqd_is_activated(irq_data))
                ret = __irq_domain_activate_irq(irq_data, reserve);
        if (!ret)
                irqd_set_activated(irq_data);
        return ret;
}

irq domain에 구성된 최상위까지 activate 후크에 등록한 콜백 함수를 호출한다.

  • parent_data가 상위 irq_data를 가리키는 경우 재귀 호출된다.

 

irq_domain_deactivate_irq()

kernel/irq/irqdomain.c

/**
 * irq_domain_deactivate_irq - Call domain_ops->deactivate recursively to
 *                             deactivate interrupt
 * @irq_data: outermost irq_data associated with interrupt
 *
 * It calls domain_ops->deactivate to program interrupt controllers to disable
 * interrupt delivery.
 */
void irq_domain_deactivate_irq(struct irq_data *irq_data)
{
        if (irqd_is_activated(irq_data)) {
                __irq_domain_deactivate_irq(irq_data);
                irqd_clr_activated(irq_data);
        }
}

irq domain에 구성된 최상위까지 deactivate 후크에 등록한 콜백 함수를 호출한다.

  • parent_data가 상위 irq_data를 가리키는 경우 재귀 호출된다.

 

IRQ 설정

각 디바이스 드라이버에서 irq를 설정할 때 사용하는 함수는 다음 그림과 같다.

 

구조체

irq_chip 구조체

include/linux/irq.h

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @parent_device:      pointer to parent device for irqchip
 * @name:               name for /proc/interrupts
 * @irq_startup:        start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:       shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:         enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:        disable the interrupt
 * @irq_ack:            start of a new interrupt
 * @irq_mask:           mask an interrupt source
 * @irq_mask_ack:       ack and mask an interrupt source
 * @irq_unmask:         unmask an interrupt source
 * @irq_eoi:            end of interrupt
 * @irq_set_affinity:   Set the CPU affinity on SMP machines. If the force
 *                      argument is true, it tells the driver to
 *                      unconditionally apply the affinity setting. Sanity
 *                      checks against the supplied affinity mask are not
 *                      required. This is used for CPU hotplug where the
 *                      target CPU is not yet set in the cpu_online_mask.
 * @irq_retrigger:      resend an IRQ to the CPU
 * @irq_set_type:       set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:       enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:       function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:     configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:    un-configure an interrupt source for a secondary CPU
 * @irq_suspend:        function called from core code on suspend once per
 *                      chip, when one or more interrupts are installed
 * @irq_resume:         function called from core code on resume once per chip,
 *                      when one ore more interrupts are installed
 * @irq_pm_shutdown:    function called from core code on shutdown once per chip
 * @irq_calc_mask:      Optional function to set irq_data.mask for special cases
 * @irq_print_chip:     optional to print special chip info in show_interrupts
 * @irq_request_resources:      optional to request resources before calling
 *                              any other callback related to this irq
 * @irq_release_resources:      optional to release resources acquired with
 *                              irq_request_resources
 * @irq_compose_msi_msg:        optional to compose message content for MSI
 * @irq_write_msi_msg:  optional to write message content for MSI
 * @irq_get_irqchip_state:      return the internal state of an interrupt
 * @irq_set_irqchip_state:      set the internal state of a interrupt
 * @irq_set_vcpu_affinity:      optional to target a vCPU in a virtual machine
 * @ipi_send_single:    send a single IPI to destination cpus
 * @ipi_send_mask:      send an IPI to destination cpus in cpumask
 * @irq_nmi_setup:      function called from core code before enabling an NMI
 * @irq_nmi_teardown:   function called from core code after disabling an NMI
 * @flags:              chip specific flags
 */
struct irq_chip {
        struct device   *parent_device;
        const char      *name;
        unsigned int    (*irq_startup)(struct irq_data *data);
        void            (*irq_shutdown)(struct irq_data *data);
        void            (*irq_enable)(struct irq_data *data);
        void            (*irq_disable)(struct irq_data *data);

        void            (*irq_ack)(struct irq_data *data);
        void            (*irq_mask)(struct irq_data *data);
        void            (*irq_mask_ack)(struct irq_data *data);
        void            (*irq_unmask)(struct irq_data *data);
        void            (*irq_eoi)(struct irq_data *data);

        int             (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool
force);
        int             (*irq_retrigger)(struct irq_data *data);
        int             (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
        int             (*irq_set_wake)(struct irq_data *data, unsigned int on);

        void            (*irq_bus_lock)(struct irq_data *data);
        void            (*irq_bus_sync_unlock)(struct irq_data *data);

        void            (*irq_cpu_online)(struct irq_data *data);
        void            (*irq_cpu_offline)(struct irq_data *data);

        void            (*irq_suspend)(struct irq_data *data);
        void            (*irq_resume)(struct irq_data *data);
        void            (*irq_pm_shutdown)(struct irq_data *data);

        void            (*irq_calc_mask)(struct irq_data *data);

        void            (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
        int             (*irq_request_resources)(struct irq_data *data);
        void            (*irq_release_resources)(struct irq_data *data);

        void            (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
        void            (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

        int             (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state whichh
, bool *state);
        int             (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state whichh
, bool state);

        int             (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

        void            (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
        void            (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

        int             (*irq_nmi_setup)(struct irq_data *data);
        void            (*irq_nmi_teardown)(struct irq_data *data);

        unsigned long   flags;
};

인터럽트 HW 칩에 대한 디스크립터로 대부분의 멤버들은 operation 후크들로 이루어졌다.

  • * name
    • /proc/interrupts에 출력할 이름
  • (*irq_startup)
    • 인터럽트 startup
    • core – irq_startup() 함수에서 호출하는데 이 후크가 지정되지 않으면 core – irq_enable() 함수를 호출한다.
  • (*irq_shutdown)
    • 인터럽트 shutdown
    • core – irq_shutdown() 함수에서 호출하는데 이 후크가 지정되지 않으면 core – irq_disable() 함수를 호출한다.
  • (*irq_enable)
    • 인터럽트 enable
    • core – irq_enable() 함수에서 호출하는데 이 후크가 지정되지 않으면 chip->irq_unmask() 후크를 호출한다.
  • (*irq_disable)
    • 인터럽트 disable
    • core – irq_disable() 함수에서 호출한다.
  • (*irq_ack)
    • 인터럽트 핸들러에서 새 인터럽트가 들어온 경우 호출한다. (새 인터럽트의 시작)
    • ack가 호출되면 eoi가 처리되기 전에 cpu에 새 인터럽트가 진입하지 못한다.
  • (*irq_mask)
    • 인터럽트 소스 마스크
    • c0re – mask_irq() 함수에서 호출한다.
  • (*irq_mask_ack)
    • 인터럽트 핸들러에서 새 인터럽트가 들어온 경우 호출하며 irq 마스크도 같이 수행한다.
    • core – mask_ack_irq() 함수에서 호출하는데 이 후크가 지정되지 않으면 chip->irq_mask() 후크와 chip->irq_ack() 후크를 따로 하나씩 호출한다.
  • (*irq_unmask)
    • 인터럽트 소스 언마스크
    • c0re – unmask_irq() 함수에서 호출한다.
  • (*irq_eoi)
    • 인터럽트 핸들러 처리의 종료 시 호출한다.
  • (*irq_set_affinity)
    • SMP 머신에서 irq 소스를 허용할 cpu affinity를 설정한다.
    • core – irq_do_set_affinity() 함수에서 호출한다.
  • (*irq_retrigger)
    • cpu로 irq를 재 전송
  • (*irq_set_type)
    • irq 라인 트리거 타입을 설정한다.
  • (*irq_set_wake)
    • irq의 pm wake-on의 enable/disable 여부 설정
    • core – irq_set_irq_wake() -> set_irq_kake_real() 함수에서 호출한다.
  • (*irq_bus_lock)
    • slow bus chip에 대한 lock으로 인터럽트를 구성하고 해제할 때 lock을 사용 한다.
      • 사용 함수
        • setup_irq()
        • free_irq()
        • request_threaded_irq()
        • setup_percpu_irq()
        • free_percpu_irq()
        • request_percpu_irq()
        • irq_finalize_oneshot()
  • (*irq_bus_sync_unlock)
    • slow bus chip에 대한 sync와 unlock
  • (*irq_cpu_online)
    • cpu가 online될 때 core – irq_cpu_online() 함수에서 호출한다.
  • (*irq_cpu_offline)
    • cpu가 offline될 때 core – irq_cpu_offline() 함수에서 호출한다.
  • (*irq_suspend)
    • PM(Power Management) 기능을 사용하는 경우 core가 suspend 되는 경우 호출되어 irq chip도같이 suspend 동작을 알려 동작을 멈추게 한다.
  • (*irq_resume)
    • PM(Power Management) 기능을 사용하는 경우 suspend된 irq chip이 깨어나 다시 동작하게 한다.
  • (*irq_pm_shutdown)
    • irq에 대한 전원 소비를 없애기 위해 shutdown 한다.
  • (*irq_calc_mask)
    • irq_data.mask의 설정을 사용한 특별한 옵션 기능
  • (*irq_print_chip)
    • show interruptes 시 추가 옵션 칩 정보를 출력
  • (*irq_request_resources)
    • irq와 관련된 모든 콜백 함수를 호출하기 전에 리소스를 요청하기 위한 옵션
  • (*irq_release_resources)
    • irq_request_resources에서 얻은 리소스를 해제
  • (*irq_compose_msi_msg)
    • MSI를 위한 메시지 구성
  • (*irq_write_msi_msg)
    • MSI를 위한 메시지 기록 옵션
  • (*irq_get_irqchip_state)
    • irqchip 내부 상태를 가져온다.
  • (*irq_set_irqchip_state)
    • irqchip 내부 상태를 저장한다.
  • (*irq_set_vcpu_affinity)
    • 가상 cpu에 대한 affinity를 지정한다.
  • (*ipi_send_single)
    • 하나의 cpu에 IPI를 전달한다.
  • (*ipi_send_mask)
    • mask 지정된 cpu에 IPI를 전달한다.
  • (*irq_nmi_setup)
    • Pesudo-NMI를 설정한다.
  • (*irq_nmi_teardown)
    • Pesudo-NMI를 해제한다.
  • flags
    • chip 관련 플래그들

 

참고