FDT & DTB #1 (Device Tree)


5 분 소요

FDT & DTB #1 (Flattened Device Tree & Device Tree Blob)

FDTDTB는 동일한 용어이다. Device Tree reference에서는 DTB가 FDT binary blob로, falinux 포럼에서는 Device Tree Binary로, 문c 블로그에서는 Device Tree Blob으로 표현된다. 뭐가 맞을까?

부르는 사람마다, 사용하는 사람마다 약간의 차이가 보이는 것 같다. 객체와 인스턴스의 관계처럼. 일단 FDT에 대한 확장자 ‘.dtb’의 약자는 Device Tree reference에서 설명했듯, FDT binary blob 이라고 부르는 것 같은데, 여러군데서 찾아본 결과 output format은 이렇게 설명한다.

  • .dts : device tree source (board-level definitions)
  • .dtsi : device tree source include (SoC-level definitions)
  • .dtb : device tree blob

이거슨 무엇이냐?

FDT날 아주많이 괴롭힌 녀석이다. 하드웨어의 구조를 기술하기 위한, 즉, 운영체제가 읽으려는 전체 하드웨어를 구성하는 각 디바이스들의 구조 및 구성 방식을 담은 데이터 구조이다. FDT는 정해진 형식의 데이터 구조로 운영체제에 전달되며, 전달된 데이터를 통해 디바이스와 관련 데이터들을 초기화 한다.

Device Tree reference를 따르면 다음과 같이 설명되어 있다.

The primary purpose of Device Tree in Linux is to provide a way to describe non-discoverable hardware.

이제부터 여기 아래는 거의 eLinux - Device Tree Usage 번역 수준이다.

기본 데이터 구조

/dts-v1/;

/ {
    node1 {
        a-string-property = "A string";
        a-string-list-property = "first string", "second string";
        // hex is implied in byte arrays. no '0x' prefix is required
        a-byte-data-property = [01 23 34 56];
        child-node1 {
            first-child-property;
            second-child-property = <1>;
            a-string-property = "Hello, world";
        };
        child-node2 {
        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
        child-node1 {
        };
    };
};

데이터 구조는 Tree 구조로 이루어져 있으며, 각 NodeChild-nodes를 가질 수 있다. 각 NodeChild-nodes Property로 이루어져 있고. 각 PropertyKeyValue의 쌍으로 이루어진다.

  • / : 루트 노드
  • node1 : /의 자식 노드
  • child-node1 : node1의 자식 노드
  • String Property : "A string" 과 같은 형식
  • Cell Property : <1> or <1 2 3 4> 와 같은 형식
  • Binary Property : [01 23 34 56] 과 같은 형식
  • property가 비어 있을 수도 있음.
  • ,comma : key 하나에 string-list 나 여러 타입의 Property를 섞어 지정할 때 연결자로 사용된다.

Sample Machine

다음은 샘플 머신에 대한 DTB 구조를 살펴볼 것이다. 해당 장치에 대한 스펙은 이렇다.

  • One 32bit ARM CPU
  • processor local bus attached to memory mapped serial port, spi bus controller, i2c controller, interrupt controller, and external bus bridge
  • 256MB of SDRAM based at 0
  • 2 Serial ports based at 0x101F1000 and 0x101F2000
  • GPIO controller based at 0x101F3000
  • SPI controller based at 0x10170000 with following devices
    • MMC slot with SS pin attached to GPIO #1
  • External bus bridge with following devices
    • SMC SMC91111 Ethernet device attached to external bus based at 0x10100000
    • i2c controller based at 0x10160000 with following devices
      • Maxim DS1338 real time clock. Responds to slave address 1101000 (0x58)
    • 64MB of NOR flash based at 0x30000000

Initial Structure

/dts-v1/;

/ {
    compatible = "acme,coyotes-revenge";
};

compatible : 시스템의 이름을 지정한다. "제조사, 모델명" 형식의 문자열을 따른다. Name Space 충돌을 피해야 한다.

CPU

/dts-v1/;

/ {
    compatible = "acme,coyotes-revenge";

    cpus {
        cpu@0 {
            compatible = "arm,cortex-a9";
        };
        cpu@1 {
            compatible = "arm,cortex-a9";
        };
    };
};

cpu nodecompatible도 root node처럼 정확한 cpu model을 지정하는 문자열이다.

Node Names

cpu@0과 같은 node namename[@unit-addr] 형식의 이름을 갖추어야 한다. name은 ASCII string이며 최대 31자이며, 모델명이 아닌 장치의 종류를 명시한다.

unit-addr은 장치에 접근하는 데 사용되는 주소이며, reg property로 기술된다.

형제 노드는 각자 고유한 이름을 가져야 하지만, 주소가 다른 둘 이상의 같은 종류의 노드는 동일한 이름을 사용하는 것이 일반적이다.

Devices

/dts-v1/;

/ {
    compatible = "acme,coyotes-revenge";

    cpus {
        cpu@0 {
            compatible = "arm,cortex-a9";
        };
        cpu@1 {
            compatible = "arm,cortex-a9";
        };
    };

    serial@101F0000 {
        compatible = "arm,pl011";
    };

    serial@101F2000 {
        compatible = "arm,pl011";
    };

    gpio@101F3000 {
        compatible = "arm,pl061";
    };

    interrupt-controller@10140000 {
        compatible = "arm,pl190";
    };

    spi@10115000 {
        compatible = "arm,pl022";
    };

    external-bus {
        ethernet@0,0 {
            compatible = "smc,smc91c111";
        };

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            rtc@58 {
                compatible = "maxim,ds1338";
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
        };
    };
};

노드에 시스템의 각 장치들이 추가되었다.

주의하여 살펴볼 점은 모든 장치 노드에는 compatible 속성이 존재한다. 일반적으로 계층구조는 CPU 관점의 view로 나타낸다.

Understanding the compatible Property

compatible property는 OS가 어떤 Device Driver를 해당 장치에 Binding 할지 결정하기 위한 Key이다.

위에서 말했듯, "제조사,모델명" 구조로 이루어져 있으며, 호환되는 장치는 샘플 머신의 DTB에 기술된 “flash” node 처럼 _comma_로 구분된 string-list로 기술할 수 있다. 이 방법을 사용하면 기존 장치 드라이버를 새로운 Device에 Binding할 수 있다.

How Addressing Works

  • reg
  • #address-cells
  • #size-cells

reg는 실제 데이터를 의미한다. 부모 노드의 #blabla-cells의 값을 참조하며 #address-cells는 주소의 cell 개수를, #size-cells는 size의 cell 개수를 의미한다. reg의 각 cell의 개수는 가변적이므로, 데이터 단위를 나타내는 개수를 지정해주어야 한다. 각 cell은 UINT32, 즉, 4바이트 정수이다.

address는 해당 장치를 접근할 때 사용할 주소이며, size는 해당 주소에서 읽을 데이터 크기를 의미한다.

예를 들어 부모 node의 #address-cells = <2>; #size-cells = <1>; 이고, 자식 node의 reg = <0xbad 0xbeef 0x1000>; 이라고 하면, 이 장치는 메모리의 0x00000bad0000beef 위치에서 0x1000 byte만큼의 크기에 Mapping 되어 있다는 의미이다. (물론 64비트 시스템일 것이다.)

CPU addressing

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a9";
            reg = <0>;
        };
        cpu@1 {
            compatible = "arm,cortex-a9";
            reg = <1>;
        };
    };

cpu는 #blabla-cells를 보면 size가 없이 단일 address만 존재한다. reg의 값(address)이 node이름 @ 뒤의 주소와 같다는 것을 볼 수 있다.

Memory Mapped Devices

/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    ...

    serial@101f0000 {
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000 >;
    };

    serial@101f2000 {
        compatible = "arm,pl011";
        reg = <0x101f2000 0x1000 >;
    };

    gpio@101f3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
    };

    interrupt-controller@10140000 {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
    };

    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000 >;
    };

    ...

};

메모리에 매핑된 memory에 대한 주소지정방식이다. 각 장치의 base address와 할당된 영역의 size가 되겠다.

GPIO 장치에는 4개의 cell이 있다. 이 말은 즉슨, 두 개의 address와 size가 할당 되었다는 의미이다. 즉, 이 장치는 두 개의 나뉘어진 address range를 사용할 수 있다는 뜻이다.

    external-bus {
        #address-cells = <2>;
        #size-cells = <1>;

        ethernet@0,0 {
            compatible = "smc,smc91c111";
            reg = <0 0 0x1000>;
        };

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            reg = <1 0 0x1000>;
            rtc@58 {
                compatible = "maxim,ds1338";
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
            reg = <2 0 0x4000000>;
        };
    };

외부 버스를 이용하는 장치가 있을 수 있다. 주소로 인코딩 된 chip select number로 외부 버스에 소속된다.

위 address cell은 2개로, 하나는 chip select number이고, 다른 하나는 chip select의 base 로부터의 offset이다. size cell은 address의 offset 부분이다.

즉, 외부 버스를 이용하긴 하지만 memory mapping이 된 상태이다.

Non Memory Mapped Devices

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            **#address-cells = <1>;
            #size-cells = <0>;**
            reg = <1 0 0x1000>;
            rtc@58 {
                compatible = "maxim,ds1338";
                **reg = <58>;**
            };
        };

메모리 매핑 하지 않는 장치도 있을 수 있다. 그래서 CPU에서 직접 접근하지 못한다. 대신 상위 device driver를 통해 간접 접근 할 수 있다.

위 i2c 장치는 cpu 주소 지정과 같이 reg가 구성되어 있다는 것을 알 수 있다.

참조문헌

https://elinux.org/Device_Tree_Reference#Introduction https://elinux.org/Device_Tree_Usage http://linuxfactory.or.kr/dokuwiki/doku.php?id=fdt

관련 내용에 대한 질문이나 태클을 환영합니다. 댓글 남겨주세요.



태그: , , ,

카테고리: ,

작성:

업데이트:

댓글남기기