Device Tree Overlay Notes

원문 : http://lxr.free-electrons.com/source/Documentation/devicetree/overlay-notes.txt

디바이스 트리 오버레이 노트

이 문서는 drivers/of/overlay.c 에 구현된 커널 디바이스 트리 오버레이 기능의 구현에 대해 설명합니다. 이 문서는 Documentation/devicetree/dt-object-internal.txt & Documentation/devicetree/dynamic-resolution-notes.txt 와 같이 보면 좋습니다.

오버레이는 어떻게 동작하는가

디바이스 트리 오버레이의 목적은 커널의 라이브 트리를 수정하기 위함입니다. 또한 커널 상태에 영향을 주는 수정을 반영하고 있습니다. 커널은 주로 장치를 다루기 때문에, 활성화된 장치는 새로운 디바이스 노드를 생성해야 합니다. 비활성화 되었거나 제거된 장치는 등록이 취소되어야 합니다.

아래 예제는 베이스 트리에 있는 foo board 입니다.

---- foo.dts ----------------------
    /* FOO platform */
    / {
        compatible = "corp,foo";

        /* shared resources */
        res: res {
        };

        /* On chip peripherals */
        ocp: ocp {
            /* peripherals that are always instantiated */
            peripheral1 { ... };
        }
    };
---- foo.dts ------------------------

bat.dts 오버레이가 아래와 같이 로딩되었습니다.

---- bar.dts -------------------------
/plugin/;   /* allow undefined label references and record them */
/ {
    ....    /* various properties for loader use; i.e. part id etc. */
    fragment@0 {
        target = ;
        __overlay__ {
            /* bar peripheral */
            bar {
                compatible = "corp,bar";
                ... /* various properties and child nodes */
            }
        };
    };
};
---- bar.dts ---------------------------

결과적으로 foo + bar.dts는 아래와 같습니다.

---- foo+bar.dts ----------------------
    /* FOO platform + bar peripheral */
    / {
        compatible = "corp,foo";

        /* shared resources */
        res: res {
        };

        /* On chip peripherals */
        ocp: ocp {
            /* peripherals that are always instantiated */
            peripheral1 { ... };

            /* bar peripheral */
            bar {
                compatible = "corp,bar";
                ... /* various properties and child nodes */
            }
        }
    };
---- foo+bar.dts -------------------------

오버레이의 결과로, 새로운 장치 노드(bar)가 생성되었습니다. 따라서 bar 플랫폼 장치가 등록되고, 일치하는 디바이스 드라이버가 로드되면 예상되로 생성됩니다.

오버레이 Kernel API

API는 사용하기 매우 쉽습니다.

  1. of_overlay_create ()를 호출하여 오버레이를 만들고 적용합니다. 반환 값 이 오버레이를 식별하는 쿠키입니다.

  2. 이전에 오버레이를 제거하고 정리하는 of_overlay_destroy ()를 호출합니다. of_overlay_create ()에 대한 호출을 통해 생성됩니다. 다른것에 의해 겹쳐진 오버레이를 제거하는 것은 허용되지 않습니다.

마지막으로 한 번에 모든 오버레이를 제거해야하는 경우 of_overlay_destroy_all () : 올바른 순서로 모든 단일 항목을 제거합니다.

오버레이 DTS 포맷

DTS 오버레이는 아래와 같은 형식을 가져야 합니다.

{
    /* ignored properties by the overlay */

    fragment@0 {    /* first child node */

        target=;    /* phandle target of the overlay */
    or
        target-path="/path";    /* target path of the overlay */

        __overlay__ {
            property-a; /* add property-a to the target */
            node-a {/* add to an existing, or create a node-a */
                ...
            };
        };
    }
    fragment@1 {    /* second child node */
        ...
    };
    /* more fragments follow */
}

Using the non-phandle based target method allows one to use a base DT which does
not contain a symbols node, i.e. it was not compiled with the -@ option.
The symbols node is only required for the target= method, since it
contains the information required to map from a phandle to a tree location.

리눅스 커널에서 do while(0)이 자주 보이는 이유

리눅스 커널을 분석하다 보면, 아래와 같은 코드가 자주 나온다

#include foo do
{
      bla1
      bla2
} while(0);

do ~ while(0) 코드는 조건이 무조건 0이기 때문에 아무런 의미가 없다.

이런 코드를 사용하는 이유는 아래와 같은 경우 때문이다

if (
test == 0 )
{
    run();
}else
    foo

이런 코드를 넣게 되면, 첫번째 라인인 bla1 은 else의 컨디션에 영향을 받지만,

두번째 라인인 bla2는 항상 실행되는 코드가 되어버린다.

이런것을 막기 위해서 의미 없는 do ~ while( 0 ) 으로 막아주는 것이다.

사실 의미없이 { ~~ } 이렇게 중괄호로 막아줘도 되는데,

이렇게 막으면 몇몇 컴파일러에선 warning을 출력하기 때문에 사용하지 않는다고 한다.

Linux Memory Management

물리 메모리 영역을 설정하는 3가지 방법

  • ATAG

  • fixup 함수

좋은 방법이 아니다~

  • bootloader parameter

CONFIG_CMDLINE=“root=/dev/ram0 rw ramdisk=8192 initrd=0x41000000,8M console=ttySAC1,115200 init=/linuxrc mem=128M@0x40000000 mem=128M@0x48000000 mem=256M@0x50000000”

Node

접근 속도가 같은 메모리의 집합을 Bank라고 한다.

리눅스에서 뱅크를 표현하는 구조가 Node이다.

Node는 pgdatat 로 표현된다.

  • 해당 노드에 속해있는 물리메리오의 실제량(nodepresentpages)

  • 해당 물리 메모리가 메모리 맵의 몇번지에 위치하는지 알아내는 변수(nodestartpfn)

  • Zone을 담기위한 배결(node_zones)

Zone

노드 안에 있는 메모리를 관리

DMAZONE, NORMALZONE, HIGHMEM_ZONE

  • 물리 메모리 시작 주소와 크기

  • 버디 할당자가 사용할 구조체

Page Frame

zone에 속해 있는 물리메모리를 관리하는 최소 단위. page 구조체로 관리

mem_map 전역 배열이 가리키고 있음

Boddy Alloc

가장 저수준의 함수 : alloc_pages() , free_pages()

Slab Alloc

저수준 함수 : kmemcachealloc(), kmemcachefree()

슬랩이 버디로 부터 페이지 프레임을 더 할당받으려면 kmemcachegrow()

kmalloc은 slab으로 부터 받아오기 때문에 최대 128kb만 가능

Linux Interrupt Async

방법1 fasync를 이용하면 app가 등록한 callback을 driver 인터럽트 서비스 루틴에서 호출할 수 있다

방법2 wait_completion

방법3 interruptible_sleep_on_timeout()

방법3는 이슈가 조금 있다..

특정 command 수행후 발생하게되는 interrupt가 interruptible_sleep_on_timeout()함수 보다 먼저 발생하게

될경우 interruptible_sleep_on_timeout() 함수의 timeout 설정 시간 동안 sleep 되며 interrupt timeout 발생합니다.

Kernel Module

EXPORT_SYMBOL을 해줘야, 동적 모듈에서 커널 심볼을 호출할 수 있다.

기존의 커널 심볼이 EXPORT_SYMBOL_GPL 이라면, 동적 모듈에 라이센스가 GPL이어야 한다. 그래야 호출 가능하다.

EXPORT_SYMBOL을 해주기 위해서는, #include <linux/module.h> 를 넣어야 한다.

리눅스 커널이 수정되면 make module_prepare가 필요할 때가 있다.

관련 명령어

insmod 를 하면 module을 커널에 집어 넣는 명령어, 하지만 의존성을 검사해주지 않는다.

modprobe를 하면 의존성을 검사해서 다 같이 넣어 준다.

modinfo 를 하면 해당 모듈의 정보를 보여준다.

wait_for_completion

현재 스레드 외부에다 작업을 시작하도록 지시하고 끝나기를 기다릴 때 사용. 세마포어를 LOCKED 상태로 하여 사용할 수도 있지만, 세마포어는 거의 항상 세마포어를 획득할 수 있을 때에 치중하여 최적화되어 왔다. 따라서 이럴 때는 completion 을 사용하는 것이 좋다. 구현은 kernel/sched.c 를 참조.

  1. completion 초기화

<linux/completion.h>를 포함하여야 한다. 자료 타입은 struct completion.

1.1. Compile time 초기화

DECLARE_COMPLETION(my_completion);

1.2. Runtime 초기화

struct completion my_completion;

/_ … _/

init_completion(&my completion);

  1. 완료 기다리기

인터럽트가 불가능한 대기(죽일 수 없는 프로세스를 만들 수도 있다)를 수행한다. LONG_MAX 만큼 대기.

void wait_for_completion(struct completion *c);

timeout 이 있는 인터럽트가 불가능한 대기. timeout 값을 리턴한다. 따라서 expire 되면 0을, 완료되면 남은 timeout 값(jiffies 값)을 리턴한다.

unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout);

인터럽트가 가능한 대기를 수행한다.(추가필요:인터럽트 받으면 바로 리턴?)

int wait_for_completion_interruptible(struct completion *x);

인터럽트가 가능한 대기의 timeout 버전

unsigned long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout);

죽일 수 있는 대기(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)를 수행한다.(추가 필요:kill받으면 바로 리턴?)

int wait_for_completion_killable(struct completion *x);

Blocking 없는 wait_for_completion. complete 되지 않았으면 바로 0을 리턴한다.

bool try_wait_for_completion(struct completion *x);

  1. 완료 이벤트 알리기

completion을 기다리는 스레드(waiter)가 있는지 확인한다. waiter가 있으면(wait_for_completion()이 진행 중이면) 0을, 없으면 1을 리턴한다.

bool completion_done(completion *x);

대기 중인 스레드 하나만 깨우기

void complete(struct completion *c);

모든 스레드 깨우기

void complete_all(struct completion *c);

  1. 재사용을 위해 다시 초기화하기

INIT_COMPLETION(struct completion c);

  1. 모듈 종료 함수에서 종료 후 완료를 기다리리고 알릴 때(나중에 추가하기:이 함수 없어졌나?)

void complete_and_exit(struct completion *c, long retval);

코드로 읽는 리눅스 드라이버 2장

부팅 순서

X86기반에서는 BIOS->GRUB->실제모드커널->보호모드커널->init프로세스->사용자 프로세스와데몬

Low-mem

일반적으로 주소 지정이 가능한 커널 메모리 영역. kalloc()이 이 영역의 주소를 반환한다.

initrd

메모리에 상주하는 가상 디스크 이미지로, 부트로더가 올린다.

커널 시동 직후 초기 루트 파일 시스템

커널2.6에서는 initrd에 비해 여러가지 장점이 많은 initramfs기능이 추가됨

I/O 스케줄러

2.6에서는 데드라인, 예측(Default), CFQ, Noop이라는 4가지 I/O 스케줄러를 제공

init

소스중에서 ini/main.c코드를 보면 initramfs에서 init을 찾는다

init은 /etc/rc.sysinit의 초기화 스크립트를 수행한다

jiffies

시스템 시동 이후 시스템 타이머를 호출한 횟수를 저장한다

따라서 초당 jiffies가 올라가는 수는 타이머 해상도에 의존적이다

jiffies와 관련된 함수중 tme_after()는 값을 비교해주는 매크로이며, 오버플로우를 고려했다

timeafter(), timebefore(), timeaftereq(), timebeforeeq()

getjiffies64() 라는 함수가 있음

긴 지연

jiffies 단위의 지연을 긴 지연이라고 한다.

busy-wait방식:

while(time_before(jiffies, timeout)) continue;

sleep방식:

schedule_timeout();

(주의)타임 아웃의 하한값만 보장함, 더 잘수도 있다

또 다른 함수는, waiteventtimeout(), msleep()

sleep방식은 process문맥에서만 가능

타이머

미래에 특정 시점에 호출하려는 API

관련함수 : inittimer(), addtimer(), timerpending(). modtimer(), del_timer()

한번만 호출되므로 주기적호출을 하기 위해서는 다시 생성

사용자 영역 함수 : setitimer(), getitimer()

짧은 지연

jiffy 미만의 지연을 말함, 유일한 방법 Busy-wait

관련 API : mdelay(). udelay(), ndelay()

현재 시간

하드웨어가 지원하는 가장 높은 해상도로 시각을 읽으려면

do_gettimeofday()

그 외, time(), localtime(), mktime(), gettimeofday()

원자적 함수

atomic_inc() 같이, 따로 처리를 안해줘도 원자적인 수행이 가능한 함수가 있다

해당 함수들의 목록은 include/asm/atomic.h 에 있음

read-write lock

읽기 쓰기 잠금, 하나의 인스턴스로 read, write를 분리해서 임계 영역을 설정

readlock(), readunlock(), writelock, writeunlock()

seqlock

읽기보다 쓰기가 빈번한 곳에서 사용하면 좋다

리눅스 메모리 할당

x86에서는 Kernel 1G / User 3G를 설정한다.

코드로 읽는 리눅스 드라이버 3장

커널 스레드

커널 스래드를 보려면 ps -aux 하면된다.

이때 [ ] 사이에 있는 것들이 커널 스레드이다. [이름/n] 이렇게 있으면

뒤에 n인 cpu affinity가 있다는 의미이다.

daemonize()를 call 하게 되면, kthread로 변신한다

일반 커널 스래드 종류

softirq – bottom half작업을 요청하기 위해, interrupt handler가 부른다

events – work queue에 관련된 스레드

pdflush – 페이지 캐시의 내용을 강제로 쓴는 일을 담당

시그날

커널 스래드가 특정 시그날을 활성화 하고 싶으면, allow_signal()을 해야 한다.

signal_pending()은 시그날이 왔는지 확인하는 함수인데, 함수명과 다르게 바로 리턴되는듯..

Wait Queue

대기큐는 이벤트나 시스템 자원을 기다려야 하는 스레드를 담는다

DECLARE_WAIT_QUEUE_HEAD() <– 생성

DECLARE_WAITQUEUE() <– 생성

remove_wait_queue() <– 삭제

wake_up_interruptible() <– 깨우기

add_wait_queue() <– 대기큐에 넣기

Thread Status

TASK_RUNNING, TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE, TASK_STOPPED, EXIT_ZOMBIE

User mode helper

커널에서 user 영역의 프로그램을 호출할 수 있는 기능