안드로이드에서는 다양한 하드웨어를 관리하기 위해 트리를 이용하는데 이를 바로 DT Device Tree라고 한다.

이러한 기기트리는 리눅스 운영체제에서도 사용하며 DT 사용하는 이유는 다음과 같다.

 

먼저 기본적으로 많은 H/W 연결되어 사용되는데 H/W 제조사에서 각자 자신의 Code 개발하다보니 통합하는데 어려움이있고 리눅스에서 기기를 관리하는데 많은 어려움이 발생했다. DT 이러한 문제를 해결하고자 H/W vendor사에 Code작성을 위한 일종의 통합된 기준을 제공한 것이며 vendor사가 DT 맞게 코드를 작성함으로써 OS에서 Device 관리를 진행하는데 목적이 있다.

 

 

기본적으로 DT 인해 H/W 장치 주소 값을 설정하고 kernel에서 해당 장치에 접근하기 위한 명칭 OS에서 device 접근, 읽기, 쓰기 일련의 과정을 진행할 있도록 memory mapping 등을 정의할 있다. 이러한 디바이스 트리의 정보들은 모두 바이너리 형태이며 이는 vendor사에서 script(.dts파일) 작성하여 제공하면 이를 빌드하여 만들어진다.

 

Device Tree vendor사에서 만들고 Linux BootLoader 읽어서 사용한다.

위처럼 바이너리가 생성되면 바이너리들을 모아 .o파일로 /lib/firmware 해당 파일들을 저장한다. 그리고 Boot시에 BootLoader 해당 파일들을 메모리에 로드시키고

해당 내용을 읽어와 H/W 접근이 용이하도록 설정한다. (해당과정을 Device Tree Overlay라고한다)

 

 

DT 내용을 간단히 보면 가장 기본적으로 Device Tree 위와 같이 메모리의 위치와 H/W Size정보를 가지고 있다.

 

1. DT (by Linux)

기본적으로 DT 리눅스에서 사용하는 방법으로 위에서 언급한 바와 같이 많은 H/W들을 OS에서 접근하고 통합하여 관리하기 위해 사용한다.

vendor사에서는 이러한 Linux 요청에 맞게 DT script 작성해야만 하며 기본적으로 script 통해서 주소, 사이즈 등을 명시한다.

 

ex) NVIDIA 기기 DT script

/{
      compatible = "nvidia,harmony", "nvidia,tegra20";
      #address-cells = <1>;
      #size-cells = <1>;
      interrupt-parent = <&intc>;

chosen { };
      aliases { };

memory {
              device_type = "memory";
              reg = <0x00000000 0x40000000>;
      };

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

intc: interrupt-controller@50041000 {
                      compatible = "nvidia,tegra20-gic";
                      interrupt-controller;
                      #interrupt-cells = <1>;
                      reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
              };

serial@70006300 {
                      compatible = "nvidia,tegra20-uart";
                      reg = <0x70006300 0x100>;
                      interrupts = <122>;
              };

i2s1: i2s@70002800 {
                      compatible = "nvidia,tegra20-i2s";
                      reg = <0x70002800 0x100>;
                      interrupts = <77>;
                      codec = <&wm8903>;
              };

i2c@7000c000 {
                      compatible = "nvidia,tegra20-i2c";
                      #address-cells = <1>;
                      #size-cells = <0>;
                      reg = <0x7000c000 0x100>;
                      interrupts = <70>;

wm8903: codec@1a {
                              compatible = "wlf,wm8903";
                              reg = <0x1a>;
                              interrupts = <347>;
                      };
              };
      };

sound {
              compatible = "nvidia,harmony-sound";
              i2s-controller = <&i2s1>;
              i2s-codec = <&wm8903>;
      };
};



출처: <https://www.kernel.org/doc/html/latest/devicetree/usage-model.html>

위와 같이 .dts 파일을 작성할 때는 node 대한 내용들을 채워주는 식으로 작성한다.

/{ node
{ child -node1 {}
}
}

같이 루트 노드 아래 node, node 아래 child-node1라는 노드가 존재하고 노드의 내용을 {} 안에 정의하는 방식이다.

여기서 실제 데이터를 정의하는 방식은 key-value값으로 하나의 key 대한 value 지정하는 방식을 이용한다.

compatible = "nvidia,harmony-sound";

위와 같이 sound 노드의 compatible이라는 하나의 속성 값을  "nvidia,harmony-sound"; 정의할 있다.

또한 value값을 지정할 "", <>, []등을 사용하는데 해당 부분의 의미는 value 이진 데이터인지, 정수인지 등을 의미한다.

 

1) DTS 구조

DTS 작성할 때는 기본적으로 /(루트) 에서부터 시작한다. 해당 H/W자체가 루트가 전부라면 / {} 통해서 모든 내용을 정의할 있겠지만 기본적으로 하나의 chip에는 CPU, Memory, Sound 다양한 역할이 존재할 있기 때문에 / 아래 여러 노드를 가지게 되고 각각의 노드의 값들을 설정할 있다.

 

설정이라는 부분은 노드가 사용하는 주소 , 사이즈 등을 의미한다.

또한 기본적으로 compatible 대한 속성을 주로 있는데 이는 필수 값이다. 해당 속성은 제조사,모델 순으로 해당 노드의 정보를 명시하는 속성이다. 많은 장치가 존재하기 때문에 compatible에서는 제조사 모델명 등을 명시하며 해당 속성을 바탕으로 원하는 노드를 찾아갈 있다.

 

2) reg

주소 사이즈를 지정하는 것으로 보통 자식 노드에 대한 정보를 설정한다. 다시 말해 부모 노드에서 자식 노드에 대하여 주소 값과 사이즈를 가지고 있다고 생각하면 된다. 해당 값들을 설정하는 것은

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

속성 값을 이용하여 설정이 가능하다. address-cells 통해 주소 값을 지정할 있고 size-cells 통해서 크기를 결정할 있다.

 

위와 같이 reg 통해서 지정할 있다. reg 첫번 값이 Address이고 번째 값이 Size이다.

 

3) bus

또한 DT Script 통해서 BUS 대한 내용도 설정을 진행해야 한다.

Bus 해당 H/W data 주고 받기위한 line이다.

              };

serial@70006300 {
                      compatible = "nvidia,tegra20-uart";
                      reg = <0x70006300 0x100>;
                      interrupts = <122>;
              };

i2s1: i2s@70002800 {
                      compatible = "nvidia,tegra20-i2s";
                      reg = <0x70002800 0x100>;
                      interrupts = <77>;
                      codec = <&wm8903>;
              };

i2c@7000c000 {
                      compatible = "nvidia,tegra20-i2c";
                      #address-cells = <1>;
                      #size-cells = <0>;
                      reg = <0x7000c000 0x100>;
                      interrupts = <70>;

NVIDIA 예의 내용을 보면 해당 NVIDIA i2s, i2c, serial 통신을 통해서 데이터를 주고 받는 것을 있고 reg 속성을 통해 해당 버스들의 주소 값과 사이즈도 있다. 만약 해당 주소 값을 지속적으로 모니터링할 있다면 우리는 주고받는 데이터 내용을 모두 스니핑할 있을 것이다.

 

위와 같이 DT 통해서 H/W에서 Linux OS 통신하기위해 사용하는 BUS 대한 정보도 확인할 있다. 그리고 예시에는 없지만 만약 외부와 연결이 되는 H/W라면 external-bus라는 노드도 추가로 존재할 있다. 해당 노드는 외부와 연결 지원되는 BUS 모드에 따라 external-bus 아래에 i2c, spi, serial 등의 노드들이 존재할 것이다.

 

4) interrupt

DT에서는 interrupt 대한 정보도 설정해야 한다. 해당 정보는 예시에서 보이듯 interrupt 속성 값을 이용해서 지정할 있다.

 

5) 기타노드

  • allias : 별칭을 지정하기 위한 노드로 제조사,모델명 등으로 너무 이름이 길어지는 노드에 대해서 짧은 별칭을 부여하는 역할
  • chosen : OS 데이터를 읽을 있도록 파싱을 도와주는 노드로 Device 설정에는 영향이 없다.

+ Recent posts