안드로이드에서는 다양한 하드웨어를 관리하기 위해 트리를 이용하는데 이를 바로 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 설정에는 영향이 없다.
'OS > Linux' 카테고리의 다른 글
[Linux Kernel] Kernel 분석(v5.14.16) - entry.S (0) | 2021.11.14 |
---|---|
[Linux Kernel] Kernel 분석(v5.14.16) - head.S (0) | 2021.11.07 |
[Linux] 메모리 영역 (2) - Heap (0) | 2020.12.26 |
[Linux] 메모리 영역 (1) - Code, Data, Stack (0) | 2020.12.20 |
[Linux] IPC 통신(PIPE, Message, Shared, Memory Map, Socket, RPC) (0) | 2019.08.08 |