← Documents Documentation/devicetree/usage-model.rst GitHub 원문 ↗

Linux 6.18.37 · Devicetree

Linux and the Devicetree

Linux가 DT를 플랫폼 식별, runtime 구성, device population에 사용하고 root·simple-bus·AMBA 노드를 장치 모델에 등록하는 방식을 설명합니다.

Source pathDocumentation/devicetree/usage-model.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

usage-model.rst:1-420

Linux가 DT를 플랫폼 식별, runtime 구성, device population에 사용하고 root·simple-bus·AMBA 노드를 장치 모델에 등록하는 방식을 설명합니다. 영어 원문 전체와 한국어 전문 번역을 함께 제공하며 함수명, symbol, source path, DTS·C 코드와 원문 줄 좌표를 보존합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ========================
4 Linux and the Devicetree
5 ========================
6
7 The Linux usage model for device tree data
8
9 :Author: Grant Likely <[email protected]>
10
11 This article describes how Linux uses the device tree. An overview of
12 the device tree data format can be found on the device tree usage page
13 at devicetree.org\ [1]_.
14
15 .. [1] https://www.devicetree.org/specifications/
16
17 The "Open Firmware Device Tree", or simply Devicetree (DT), is a data
18 structure and language for describing hardware. More specifically, it
19 is a description of hardware that is readable by an operating system
20 so that the operating system doesn't need to hard code details of the
21 machine.
22
23 Structurally, the DT is a tree, or acyclic graph with named nodes, and
24 nodes may have an arbitrary number of named properties encapsulating
25 arbitrary data. A mechanism also exists to create arbitrary
26 links from one node to another outside of the natural tree structure.
27
28 Conceptually, a common set of usage conventions, called 'bindings',
29 is defined for how data should appear in the tree to describe typical
30 hardware characteristics including data busses, interrupt lines, GPIO
31 connections, and peripheral devices.
32
33 As much as possible, hardware is described using existing bindings to
34 maximize use of existing support code, but since property and node
35 names are simply text strings, it is easy to extend existing bindings
36 or create new ones by defining new nodes and properties. Be wary,
37 however, of creating a new binding without first doing some homework
38 about what already exists. There are currently two different,
39 incompatible, bindings for i2c busses that came about because the new
40 binding was created without first investigating how i2c devices were
41 already being enumerated in existing systems.
42
43 1. History
44 ----------
45 The DT was originally created by Open Firmware as part of the
46 communication method for passing data from Open Firmware to a client
47 program (like to an operating system). An operating system used the
48 Device Tree to discover the topology of the hardware at runtime, and
49 thereby supported a majority of available hardware without hard coded
50 information (assuming drivers were available for all devices).
51
52 Since Open Firmware is commonly used on PowerPC and SPARC platforms,
53 the Linux support for those architectures has for a long time used the
54 Device Tree.
55
56 In 2005, when PowerPC Linux began a major cleanup and to merge 32-bit
57 and 64-bit support, the decision was made to require DT support on all
58 powerpc platforms, regardless of whether or not they used Open
59 Firmware. To do this, a DT representation called the Flattened Device
60 Tree (FDT) was created which could be passed to the kernel as a binary
61 blob without requiring a real Open Firmware implementation. U-Boot,
62 kexec, and other bootloaders were modified to support both passing a
63 Device Tree Binary (dtb) and to modify a dtb at boot time. DT was
64 also added to the PowerPC boot wrapper (``arch/powerpc/boot/*``) so that
65 a dtb could be wrapped up with the kernel image to support booting
66 existing non-DT aware firmware.
67
68 Some time later, FDT infrastructure was generalized to be usable by
69 all architectures. At the time of this writing, 6 mainlined
70 architectures (arm, microblaze, mips, powerpc, sparc, and x86) and 1
71 out of mainline (nios) have some level of DT support.
72
73 2. Data Model
74 -------------
75 If you haven't already read the Device Tree Usage\ [1]_ page,
76 then go read it now. It's okay, I'll wait....
77
78 2.1 High Level View
79 -------------------
80 The most important thing to understand is that the DT is simply a data
81 structure that describes the hardware. There is nothing magical about
82 it, and it doesn't magically make all hardware configuration problems
83 go away. What it does do is provide a language for decoupling the
84 hardware configuration from the board and device driver support in the
85 Linux kernel (or any other operating system for that matter). Using
86 it allows board and device support to become data driven; to make
87 setup decisions based on data passed into the kernel instead of on
88 per-machine hard coded selections.
89
90 Ideally, data driven platform setup should result in less code
91 duplication and make it easier to support a wide range of hardware
92 with a single kernel image.
93
94 Linux uses DT data for three major purposes:
95
96 1) platform identification,
97 2) runtime configuration, and
98 3) device population.
99
100 2.2 Platform Identification
101 ---------------------------
102 First and foremost, the kernel will use data in the DT to identify the
103 specific machine. In a perfect world, the specific platform shouldn't
104 matter to the kernel because all platform details would be described
105 perfectly by the device tree in a consistent and reliable manner.
106 Hardware is not perfect though, and so the kernel must identify the
107 machine during early boot so that it has the opportunity to run
108 machine-specific fixups.
109
110 In the majority of cases, the machine identity is irrelevant, and the
111 kernel will instead select setup code based on the machine's core
112 CPU or SoC. On ARM for example, setup_arch() in
113 arch/arm/kernel/setup.c will call setup_machine_fdt() in
114 arch/arm/kernel/devtree.c which searches through the machine_desc
115 table and selects the machine_desc which best matches the device tree
116 data. It determines the best match by looking at the 'compatible'
117 property in the root device tree node, and comparing it with the
118 dt_compat list in struct machine_desc (which is defined in
119 arch/arm/include/asm/mach/arch.h if you're curious).
120
121 The 'compatible' property contains a sorted list of strings starting
122 with the exact name of the machine, followed by an optional list of
123 boards it is compatible with sorted from most compatible to least. For
124 example, the root compatible properties for the TI BeagleBoard and its
125 successor, the BeagleBoard xM board might look like, respectively::
126
127 compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3";
128 compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";
129
130 Where "ti,omap3-beagleboard-xm" specifies the exact model, it also
131 claims that it is compatible with the OMAP 3450 SoC, and the omap3 family
132 of SoCs in general. You'll notice that the list is sorted from most
133 specific (exact board) to least specific (SoC family).
134
135 Astute readers might point out that the Beagle xM could also claim
136 compatibility with the original Beagle board. However, one should be
137 cautioned about doing so at the board level since there is typically a
138 high level of change from one board to another, even within the same
139 product line, and it is hard to nail down exactly what is meant when one
140 board claims to be compatible with another. For the top level, it is
141 better to err on the side of caution and not claim one board is
142 compatible with another. The notable exception would be when one
143 board is a carrier for another, such as a CPU module attached to a
144 carrier board.
145
146 One more note on compatible values. Any string used in a compatible
147 property must be documented as to what it indicates. Add
148 documentation for compatible strings in Documentation/devicetree/bindings.
149
150 Again on ARM, for each machine_desc, the kernel looks to see if
151 any of the dt_compat list entries appear in the compatible property.
152 If one does, then that machine_desc is a candidate for driving the
153 machine. After searching the entire table of machine_descs,
154 setup_machine_fdt() returns the 'most compatible' machine_desc based
155 on which entry in the compatible property each machine_desc matches
156 against. If no matching machine_desc is found, then it returns NULL.
157
158 The reasoning behind this scheme is the observation that in the majority
159 of cases, a single machine_desc can support a large number of boards
160 if they all use the same SoC, or same family of SoCs. However,
161 invariably there will be some exceptions where a specific board will
162 require special setup code that is not useful in the generic case.
163 Special cases could be handled by explicitly checking for the
164 troublesome board(s) in generic setup code, but doing so very quickly
165 becomes ugly and/or unmaintainable if it is more than just a couple of
166 cases.
167
168 Instead, the compatible list allows a generic machine_desc to provide
169 support for a wide common set of boards by specifying "less
170 compatible" values in the dt_compat list. In the example above,
171 generic board support can claim compatibility with "ti,omap3" or
172 "ti,omap3450". If a bug was discovered on the original beagleboard
173 that required special workaround code during early boot, then a new
174 machine_desc could be added which implements the workarounds and only
175 matches on "ti,omap3-beagleboard".
176
177 PowerPC uses a slightly different scheme where it calls the .probe()
178 hook from each machine_desc, and the first one returning TRUE is used.
179 However, this approach does not take into account the priority of the
180 compatible list, and probably should be avoided for new architecture
181 support.
182
183 2.3 Runtime configuration
184 -------------------------
185 In most cases, a DT will be the sole method of communicating data from
186 firmware to the kernel, so also gets used to pass in runtime and
187 configuration data like the kernel parameters string and the location
188 of an initrd image.
189
190 Most of this data is contained in the /chosen node, and when booting
191 Linux it will look something like this::
192
193 chosen {
194 bootargs = "console=ttyS0,115200 loglevel=8";
195 initrd-start = <0xc8000000>;
196 initrd-end = <0xc8200000>;
197 };
198
199 The bootargs property contains the kernel arguments, and the initrd-*
200 properties define the address and size of an initrd blob. Note that
201 initrd-end is the first address after the initrd image, so this doesn't
202 match the usual semantic of struct resource. The chosen node may also
203 optionally contain an arbitrary number of additional properties for
204 platform-specific configuration data.
205
206 During early boot, the architecture setup code calls of_scan_flat_dt()
207 several times with different helper callbacks to parse device tree
208 data before paging is set up. The of_scan_flat_dt() code scans through
209 the device tree and uses the helpers to extract information required
210 during early boot. Typically the early_init_dt_scan_chosen() helper
211 is used to parse the chosen node including kernel parameters,
212 early_init_dt_scan_root() to initialize the DT address space model,
213 and early_init_dt_scan_memory() to determine the size and
214 location of usable RAM.
215
216 On ARM, the function setup_machine_fdt() is responsible for early
217 scanning of the device tree after selecting the correct machine_desc
218 that supports the board.
219
220 2.4 Device population
221 ---------------------
222 After the board has been identified, and after the early configuration data
223 has been parsed, then kernel initialization can proceed in the normal
224 way. At some point in this process, unflatten_device_tree() is called
225 to convert the data into a more efficient runtime representation.
226 This is also when machine-specific setup hooks will get called, like
227 the machine_desc .init_early(), .init_irq() and .init_machine() hooks
228 on ARM. The remainder of this section uses examples from the ARM
229 implementation, but all architectures will do pretty much the same
230 thing when using a DT.
231
232 As can be guessed by the names, .init_early() is used for any machine-
233 specific setup that needs to be executed early in the boot process,
234 and .init_irq() is used to set up interrupt handling. Using a DT
235 doesn't materially change the behaviour of either of these functions.
236 If a DT is provided, then both .init_early() and .init_irq() are able
237 to call any of the DT query functions (of_* in include/linux/of*.h) to
238 get additional data about the platform.
239
240 The most interesting hook in the DT context is .init_machine() which
241 is primarily responsible for populating the Linux device model with
242 data about the platform. Historically this has been implemented on
243 embedded platforms by defining a set of static clock structures,
244 platform_devices, and other data in the board support .c file, and
245 registering it en-masse in .init_machine(). When DT is used, then
246 instead of hard coding static devices for each platform, the list of
247 devices can be obtained by parsing the DT, and allocating device
248 structures dynamically.
249
250 The simplest case is when .init_machine() is only responsible for
251 registering a block of platform_devices. A platform_device is a concept
252 used by Linux for memory or I/O mapped devices which cannot be detected
253 by hardware, and for 'composite' or 'virtual' devices (more on those
254 later). While there is no 'platform device' terminology for the DT,
255 platform devices roughly correspond to device nodes at the root of the
256 tree and children of simple memory mapped bus nodes.
257
258 About now is a good time to lay out an example. Here is part of the
259 device tree for the NVIDIA Tegra board::
260
261 /{
262 compatible = "nvidia,harmony", "nvidia,tegra20";
263 #address-cells = <1>;
264 #size-cells = <1>;
265 interrupt-parent = <&intc>;
266
267 chosen { };
268 aliases { };
269
270 memory {
271 device_type = "memory";
272 reg = <0x00000000 0x40000000>;
273 };
274
275 soc {
276 compatible = "nvidia,tegra20-soc", "simple-bus";
277 #address-cells = <1>;
278 #size-cells = <1>;
279 ranges;
280
281 intc: interrupt-controller@50041000 {
282 compatible = "nvidia,tegra20-gic";
283 interrupt-controller;
284 #interrupt-cells = <1>;
285 reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
286 };
287
288 serial@70006300 {
289 compatible = "nvidia,tegra20-uart";
290 reg = <0x70006300 0x100>;
291 interrupts = <122>;
292 };
293
294 i2s1: i2s@70002800 {
295 compatible = "nvidia,tegra20-i2s";
296 reg = <0x70002800 0x100>;
297 interrupts = <77>;
298 codec = <&wm8903>;
299 };
300
301 i2c@7000c000 {
302 compatible = "nvidia,tegra20-i2c";
303 #address-cells = <1>;
304 #size-cells = <0>;
305 reg = <0x7000c000 0x100>;
306 interrupts = <70>;
307
308 wm8903: codec@1a {
309 compatible = "wlf,wm8903";
310 reg = <0x1a>;
311 interrupts = <347>;
312 };
313 };
314 };
315
316 sound {
317 compatible = "nvidia,harmony-sound";
318 i2s-controller = <&i2s1>;
319 i2s-codec = <&wm8903>;
320 };
321 };
322
323 At .init_machine() time, Tegra board support code will need to look at
324 this DT and decide which nodes to create platform_devices for.
325 However, looking at the tree, it is not immediately obvious what kind
326 of device each node represents, or even if a node represents a device
327 at all. The /chosen, /aliases, and /memory nodes are informational
328 nodes that don't describe devices (although arguably memory could be
329 considered a device). The children of the /soc node are memory mapped
330 devices, but the codec@1a is an i2c device, and the sound node
331 represents not a device, but rather how other devices are connected
332 together to create the audio subsystem. I know what each device is
333 because I'm familiar with the board design, but how does the kernel
334 know what to do with each node?
335
336 The trick is that the kernel starts at the root of the tree and looks
337 for nodes that have a 'compatible' property. First, it is generally
338 assumed that any node with a 'compatible' property represents a device
339 of some kind, and second, it can be assumed that any node at the root
340 of the tree is either directly attached to the processor bus, or is a
341 miscellaneous system device that cannot be described any other way.
342 For each of these nodes, Linux allocates and registers a
343 platform_device, which in turn may get bound to a platform_driver.
344
345 Why is using a platform_device for these nodes a safe assumption?
346 Well, for the way that Linux models devices, just about all bus_types
347 assume that its devices are children of a bus controller. For
348 example, each i2c_client is a child of an i2c_master. Each spi_device
349 is a child of an SPI bus. Similarly for USB, PCI, MDIO, etc. The
350 same hierarchy is also found in the DT, where I2C device nodes only
351 ever appear as children of an I2C bus node. Ditto for SPI, MDIO, USB,
352 etc. The only devices which do not require a specific type of parent
353 device are platform_devices (and amba_devices, but more on that
354 later), which will happily live at the base of the Linux /sys/devices
355 tree. Therefore, if a DT node is at the root of the tree, then it
356 really probably is best registered as a platform_device.
357
358 Linux board support code calls of_platform_populate(NULL, NULL, NULL, NULL)
359 to kick off discovery of devices at the root of the tree. The
360 parameters are all NULL because when starting from the root of the
361 tree, there is no need to provide a starting node (the first NULL), a
362 parent struct device (the last NULL), and we're not using a match
363 table (yet). For a board that only needs to register devices,
364 .init_machine() can be completely empty except for the
365 of_platform_populate() call.
366
367 In the Tegra example, this accounts for the /soc and /sound nodes, but
368 what about the children of the SoC node? Shouldn't they be registered
369 as platform devices too? For Linux DT support, the generic behaviour
370 is for child devices to be registered by the parent's device driver at
371 driver .probe() time. So, an i2c bus device driver will register a
372 i2c_client for each child node, an SPI bus driver will register
373 its spi_device children, and similarly for other bus_types.
374 According to that model, a driver could be written that binds to the
375 SoC node and simply registers platform_devices for each of its
376 children. The board support code would allocate and register an SoC
377 device, a (theoretical) SoC device driver could bind to the SoC device,
378 and register platform_devices for /soc/interrupt-controller, /soc/serial,
379 /soc/i2s, and /soc/i2c in its .probe() hook. Easy, right?
380
381 Actually, it turns out that registering children of some
382 platform_devices as more platform_devices is a common pattern, and the
383 device tree support code reflects that and makes the above example
384 simpler. The second argument to of_platform_populate() is an
385 of_device_id table, and any node that matches an entry in that table
386 will also get its child nodes registered. In the Tegra case, the code
387 can look something like this::
388
389 static void __init harmony_init_machine(void)
390 {
391 /* ... */
392 of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
393 }
394
395 "simple-bus" is defined in the Devicetree Specification as a property
396 meaning a simple memory mapped bus, so the of_platform_populate() code
397 could be written to just assume simple-bus compatible nodes will
398 always be traversed. However, we pass it in as an argument so that
399 board support code can always override the default behaviour.
400
401 [Need to add discussion of adding i2c/spi/etc child devices]
402
403 Appendix A: AMBA devices
404 ------------------------
405
406 ARM Primecells are a certain kind of device attached to the ARM AMBA
407 bus which include some support for hardware detection and power
408 management. In Linux, struct amba_device and the amba_bus_type is
409 used to represent Primecell devices. However, the fiddly bit is that
410 not all devices on an AMBA bus are Primecells, and for Linux it is
411 typical for both amba_device and platform_device instances to be
412 siblings of the same bus segment.
413
414 When using the DT, this creates problems for of_platform_populate()
415 because it must decide whether to register each node as either a
416 platform_device or an amba_device. This unfortunately complicates the
417 device creation model a little bit, but the solution turns out not to
418 be too invasive. If a node is compatible with "arm,primecell", then
419 of_platform_populate() will register it as an amba_device instead of a
420 platform_device.
421

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Linux와 Devicetree

1-16

Grant Likely가 작성한 이 글은 Linux가 Device Tree 데이터를 사용하는 방식을 설명합니다. Device Tree 데이터 형식의 개요는 `https://www.devicetree.org/specifications/`의 Device Tree 사용 페이지에서 볼 수 있으며, 문서에는 `GPL-2.0` 라이선스가 적용됩니다.

하드웨어 기술 언어와 binding

17-42

Open Firmware Device Tree, 줄여서 Devicetree(DT)는 하드웨어를 설명하는 자료 구조이자 언어입니다. 운영체제가 읽을 수 있는 하드웨어 설명을 제공하므로 운영체제가 시스템의 세부 사항을 hard code할 필요가 없습니다.

구조적으로 DT는 이름이 있는 노드로 이루어진 트리 또는 비순환 그래프입니다. 각 노드는 임의 데이터를 담는 이름 있는 속성을 원하는 수만큼 가질 수 있고, 자연스러운 트리 구조 밖에서 한 노드와 다른 노드를 임의로 연결하는 메커니즘도 제공합니다.

개념적으로는 데이터 버스, 인터럽트 선, GPIO 연결, 주변 장치 같은 일반적인 하드웨어 특성을 설명할 때 데이터가 트리에 나타나는 방식을 공통 사용 규약인 binding으로 정의합니다.

기존 지원 코드를 최대한 활용하려면 가능한 한 기존 binding으로 하드웨어를 설명해야 합니다. 속성과 노드 이름이 단순한 문자열이므로 새 노드와 속성을 정의해 binding을 확장하거나 만들기는 쉽지만, 기존 방식을 조사하지 않고 새 binding을 만들어서는 안 됩니다.

실제로 기존 시스템이 I2C 장치를 열거하는 방법을 먼저 조사하지 않은 채 새 binding을 만든 탓에 현재 서로 호환되지 않는 I2C 버스 binding이 두 개 존재합니다.

Open Firmware에서 범용 FDT로

43-72

DT는 원래 Open Firmware가 자신과 운영체제 같은 client program 사이에 데이터를 전달하는 통신 방식의 일부로 만들었습니다. 운영체제는 runtime에 Device Tree에서 하드웨어 topology를 발견하여, 필요한 드라이버가 있다는 전제 아래 hard-code된 정보 없이 사용 가능한 하드웨어 대부분을 지원할 수 있었습니다.

Open Firmware는 PowerPC와 SPARC 플랫폼에서 널리 사용되었기 때문에 Linux의 두 아키텍처 지원도 오래전부터 Device Tree를 사용했습니다.

2005년에 PowerPC Linux가 대대적인 정리를 시작하고 32비트와 64비트 지원을 통합하면서, Open Firmware 사용 여부와 관계없이 모든 powerpc 플랫폼에 DT 지원을 요구하기로 했습니다.

이를 위해 실제 Open Firmware 구현 없이도 binary blob으로 커널에 전달할 수 있는 Flattened Device Tree(FDT) 표현을 만들었습니다. U-Boot, kexec와 다른 bootloader는 Device Tree Binary(dtb)를 전달하고 부팅 시 dtb를 수정하도록 바뀌었습니다.

DT는 PowerPC boot wrapper인 `arch/powerpc/boot/*`에도 추가되어, dtb를 커널 이미지와 함께 감싸 기존의 DT 비인식 firmware에서도 부팅할 수 있게 했습니다.

그 뒤 FDT 기반 구조는 모든 아키텍처에서 쓸 수 있도록 일반화되었습니다. 이 글을 쓸 당시 mainline의 6개 아키텍처인 arm, microblaze, mips, powerpc, sparc, x86과 mainline 밖의 nios가 일정 수준의 DT 지원을 제공했습니다.

데이터 중심 플랫폼 구성의 세 목적

73-99

아직 Device Tree Usage[1] 페이지를 읽지 않았다면 먼저 읽어야 합니다. 가장 중요한 점은 DT가 하드웨어를 설명하는 단순한 자료 구조라는 사실입니다. DT가 모든 하드웨어 구성 문제를 자동으로 해결하지는 않습니다.

DT가 제공하는 것은 하드웨어 구성을 Linux 커널 또는 다른 운영체제의 보드·장치 드라이버 지원과 분리하는 언어입니다. 시스템별 선택을 hard code하는 대신 커널에 전달된 데이터를 바탕으로 설정을 결정하므로 보드와 장치 지원이 데이터 중심이 됩니다.

이상적인 데이터 중심 플랫폼 설정은 코드 중복을 줄이고 하나의 커널 이미지로 다양한 하드웨어를 더 쉽게 지원하게 합니다.

Linux는 DT 데이터를 세 가지 주요 목적으로 사용합니다.

  • 1. platform identification
  • 2. runtime configuration
  • 3. device population

root compatible을 이용한 플랫폼 식별

100-149

커널은 무엇보다 먼저 DT 데이터로 특정 시스템을 식별합니다. 모든 플랫폼 세부 사항이 일관되고 신뢰할 수 있게 Device Tree에 기술되는 이상적인 환경이라면 커널에 특정 플랫폼이 중요하지 않겠지만, 실제 하드웨어에는 예외가 있으므로 커널은 early boot에 시스템을 식별하여 machine-specific fixup을 실행할 기회를 가져야 합니다.

대부분은 시스템의 정확한 정체보다 핵심 CPU나 SoC가 중요합니다. ARM의 `arch/arm/kernel/setup.c`에 있는 `setup_arch()`는 `arch/arm/kernel/devtree.c`의 `setup_machine_fdt()`를 호출합니다. 이 함수는 `machine_desc` 테이블을 검색하여 Device Tree 데이터와 가장 잘 맞는 `machine_desc`를 선택합니다.

일치는 root Device Tree 노드의 `compatible` 속성과 `struct machine_desc`의 `dt_compat` 목록을 비교해 결정합니다. `struct machine_desc`는 `arch/arm/include/asm/mach/arch.h`에 정의되어 있습니다.

`compatible` 속성은 정확한 시스템 이름으로 시작하고, 선택적으로 호환되는 보드를 호환성이 높은 순서에서 낮은 순서로 나열한 정렬된 문자열 목록입니다. TI BeagleBoard와 후속 BeagleBoard xM의 root 값은 각각 다음과 같습니다.

compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3";
compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";

`ti,omap3-beagleboard-xm`은 정확한 모델을 지정하면서 OMAP 3450 SoC 및 일반적인 omap3 SoC 제품군과도 호환됨을 선언합니다. 목록은 가장 구체적인 정확한 보드에서 가장 덜 구체적인 SoC 제품군 순으로 정렬됩니다.

Beagle xM이 원래 Beagle 보드와 호환된다고 선언할 수도 있지만, 같은 제품군 안에서도 보드 간 변경 폭이 크고 보드 간 호환의 정확한 뜻을 정하기 어렵기 때문에 최상위 보드 수준에서는 보수적으로 다른 보드와의 호환을 선언하지 않는 편이 좋습니다.

한 보드가 다른 보드의 carrier 역할을 하는 경우, 예를 들어 CPU module이 carrier board에 연결되는 경우는 주목할 만한 예외입니다. `compatible` 속성에 쓰는 모든 문자열은 의미를 문서화해야 하며, 문서는 `Documentation/devicetree/bindings`에 추가합니다.

machine_desc 선택과 특수 보드 예외

150-182

ARM 커널은 각 `machine_desc`의 `dt_compat` 항목이 `compatible` 속성에 나타나는지 검사합니다. 일치하면 해당 `machine_desc`가 시스템을 구동할 후보가 됩니다. 전체 테이블을 검색한 뒤 `setup_machine_fdt()`는 각 `machine_desc`가 `compatible`의 어느 항목과 일치했는지를 기준으로 가장 호환되는 항목을 반환하며, 일치 항목이 없으면 `NULL`을 반환합니다.

이 구조는 같은 SoC 또는 SoC 제품군을 쓰는 많은 보드를 하나의 `machine_desc`가 지원할 수 있다는 관찰에 기반합니다. 그러나 특정 보드에만 early boot 특수 설정이 필요한 예외는 항상 생깁니다. 일반 설정 코드가 문제 보드를 직접 검사하도록 만들면 예외가 몇 개만 넘어도 코드가 금방 복잡하고 유지하기 어려워집니다.

대신 `compatible` 목록을 사용하면 일반 `machine_desc`가 `dt_compat`에 덜 구체적인 값을 지정하여 폭넓은 보드를 지원할 수 있습니다. 위 예제의 일반 보드 지원은 `ti,omap3` 또는 `ti,omap3450`과의 호환을 선언할 수 있습니다.

원래 BeagleBoard에서 early boot workaround가 필요한 버그가 발견되면, workaround를 구현하고 `ti,omap3-beagleboard`에만 일치하는 새 `machine_desc`를 추가할 수 있습니다.

PowerPC는 각 `machine_desc`의 `.probe()` hook을 호출하고 처음으로 `TRUE`를 반환한 항목을 사용하는 조금 다른 방식을 씁니다. 이 방식은 `compatible` 목록의 우선순위를 고려하지 않으므로 새 아키텍처 지원에는 피하는 편이 좋습니다.

runtime 구성과 early flat tree scan

183-219

대부분의 경우 DT는 firmware가 커널에 데이터를 전달하는 유일한 수단이므로 kernel parameter 문자열과 initrd 이미지 위치 같은 runtime 및 구성 데이터도 전달합니다.

이 데이터 대부분은 `/chosen` 노드에 들어가며 Linux 부팅 시 다음과 같은 형태입니다.

chosen {
        bootargs = "console=ttyS0,115200 loglevel=8";
        initrd-start = <0xc8000000>;
        initrd-end = <0xc8200000>;
};

`bootargs` 속성은 커널 인자를 담고 `initrd-*` 속성은 initrd blob의 주소와 크기를 정의합니다. `initrd-end`는 initrd 이미지 바로 다음의 첫 주소이므로 일반적인 `struct resource` 의미와 다릅니다. `/chosen`은 플랫폼별 구성 데이터를 위한 속성을 원하는 수만큼 추가로 포함할 수도 있습니다.

early boot 동안 아키텍처 설정 코드는 paging을 구성하기 전에 서로 다른 helper callback을 사용하여 `of_scan_flat_dt()`를 여러 번 호출합니다. 이 함수는 Device Tree를 훑고 helper를 통해 early boot에 필요한 정보를 추출합니다.

일반적으로 `early_init_dt_scan_chosen()`은 커널 매개변수를 포함한 `/chosen`을 분석하고, `early_init_dt_scan_root()`는 DT address space model을 초기화하며, `early_init_dt_scan_memory()`는 사용 가능한 RAM의 크기와 위치를 결정합니다.

ARM에서는 보드를 지원하는 올바른 `machine_desc`를 선택한 뒤 `setup_machine_fdt()`가 Device Tree의 early scan을 담당합니다.

unflatten과 machine 초기화 hook

220-249

보드를 식별하고 early 구성 데이터를 분석한 뒤에는 일반적인 커널 초기화를 진행할 수 있습니다. 이 과정의 어느 시점에 `unflatten_device_tree()`를 호출하여 데이터를 더 효율적인 runtime 표현으로 변환합니다.

이때 ARM의 `machine_desc`에 있는 `.init_early()`, `.init_irq()`, `.init_machine()` 같은 시스템별 설정 hook도 호출합니다. 이 절은 ARM 구현을 예제로 사용하지만 DT를 사용하는 모든 아키텍처의 동작은 거의 같습니다.

이름이 나타내듯 `.init_early()`는 부팅 초기에 필요한 시스템별 설정을, `.init_irq()`는 인터럽트 처리를 설정합니다. DT 사용은 두 함수의 동작을 실질적으로 바꾸지 않습니다. DT가 제공되면 두 hook 모두 `include/linux/of*.h`의 `of_*` query 함수를 호출해 플랫폼 추가 데이터를 가져올 수 있습니다.

DT 맥락에서 가장 중요한 hook은 플랫폼 정보로 Linux device model을 채우는 `.init_machine()`입니다. 과거 embedded 플랫폼은 board support `.c` 파일에 정적 clock 구조, `platform_device`와 기타 데이터를 정의하고 `.init_machine()`에서 한꺼번에 등록했습니다.

DT를 사용하면 플랫폼마다 정적 장치를 hard code하는 대신 DT를 분석하여 장치 목록을 얻고 device structure를 동적으로 할당할 수 있습니다.

platform_device에 대응하는 DT 노드

250-257

가장 단순한 경우 `.init_machine()`은 `platform_device` 묶음을 등록하기만 합니다. Linux의 `platform_device`는 하드웨어가 검출할 수 없는 memory-mapped 또는 I/O-mapped 장치와 composite 또는 virtual 장치를 표현하는 개념입니다.

DT에는 platform device라는 용어가 없지만, 대략 트리 root의 장치 노드와 단순한 memory-mapped bus 노드의 자식이 platform device에 해당합니다.

NVIDIA Tegra DT와 노드 유형 구분

258-334

다음 NVIDIA Tegra 보드의 Device Tree 일부에는 root의 호환성·주소 형식·메모리, `simple-bus` SoC, interrupt controller, UART, I2S, I2C codec와 이들을 연결하는 sound 노드가 들어 있습니다.

/{
      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>;
      };
};

`.init_machine()` 시점에 Tegra board support 코드는 이 DT에서 어느 노드에 `platform_device`를 만들지 결정해야 합니다. 하지만 트리만 보면 각 노드가 어떤 장치인지, 실제로 장치를 나타내는지조차 곧바로 명확하지 않습니다.

`/chosen`, `/aliases`, `/memory`는 장치를 설명하지 않는 정보 노드입니다. `/soc`의 자식은 memory-mapped 장치지만 `codec@1a`는 I2C 장치입니다. `sound` 노드는 장치가 아니라 다른 장치가 audio subsystem을 만들도록 연결되는 방식을 나타냅니다.

보드 설계를 아는 사람은 각 장치를 구분할 수 있지만, 커널에는 각 노드를 처리하는 일반 규칙이 필요합니다.

root 장치 등록과 bus driver의 자식 생성

335-380

커널은 트리 root에서 시작하여 `compatible` 속성이 있는 노드를 찾습니다. 일반적으로 `compatible`이 있는 노드는 어떤 형태로든 장치를 나타낸다고 가정하며, root의 노드는 processor bus에 직접 연결되었거나 다른 방식으로 설명할 수 없는 기타 시스템 장치라고 가정합니다.

Linux는 이런 각 노드에 `platform_device`를 할당하고 등록하며, 이 장치는 이후 `platform_driver`에 bind될 수 있습니다.

Linux device model의 거의 모든 `bus_type`은 장치가 bus controller의 자식이라고 가정합니다. 각 `i2c_client`는 `i2c_master`의 자식이고, 각 `spi_device`는 SPI 버스의 자식입니다. USB, PCI, MDIO도 마찬가지입니다. DT에서도 I2C 장치 노드는 항상 I2C 버스 노드의 자식이며 SPI, MDIO, USB도 같은 계층을 따릅니다.

특정 유형의 parent device가 필요 없는 장치는 `platform_device`와 뒤에서 설명할 `amba_device`뿐이며, 이들은 Linux `/sys/devices` 트리의 root에 놓일 수 있습니다. 따라서 DT root의 노드는 `platform_device`로 등록하는 것이 가장 타당합니다.

Linux board support 코드는 root 장치 검색을 시작하기 위해 `of_platform_populate(NULL, NULL, NULL, NULL)`을 호출합니다. 첫 번째 `NULL`은 시작 노드를 root로 사용한다는 뜻이고 마지막 `NULL`은 parent `struct device`가 없다는 뜻이며, 아직 match table도 사용하지 않으므로 모든 인자가 `NULL`입니다.

장치 등록만 필요한 보드라면 `.init_machine()`은 `of_platform_populate()` 호출 외에는 완전히 비어 있어도 됩니다.

Tegra 예제에서는 `/soc`와 `/sound`가 이렇게 등록됩니다. SoC 노드의 자식은 일반적으로 parent device driver가 `.probe()`할 때 등록합니다. I2C bus driver는 각 자식 노드에 `i2c_client`를, SPI bus driver는 `spi_device` 자식을 등록하며 다른 `bus_type`도 같은 방식입니다.

이 모델대로라면 SoC 노드에 bind하는 드라이버가 각 자식의 `platform_device`를 등록할 수 있습니다. board support 코드는 SoC 장치를 등록하고, 이론적인 SoC driver는 `.probe()`에서 `/soc/interrupt-controller`, `/soc/serial`, `/soc/i2s`, `/soc/i2c`의 `platform_device`를 만들 수 있습니다.

of_device_id 재귀와 simple-bus

381-402

일부 `platform_device`의 자식을 다시 `platform_device`로 등록하는 방식은 흔합니다. Device Tree 지원 코드는 이를 반영하여 위 예제를 더 단순하게 만듭니다.

`of_platform_populate()`의 두 번째 인자는 `of_device_id` 테이블입니다. 이 테이블의 항목과 일치하는 노드는 그 자식 노드도 등록합니다. Tegra 코드는 다음처럼 작성할 수 있습니다.

static void __init harmony_init_machine(void)
{
      /* ... */
      of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}

Devicetree Specification은 `simple-bus`를 단순한 memory-mapped bus를 뜻하는 속성으로 정의합니다. `of_platform_populate()`가 `simple-bus` 호환 노드를 항상 순회한다고 가정할 수도 있지만, board support 코드가 기본 동작을 언제든 바꿀 수 있도록 match table을 인자로 전달합니다.

원문에는 I2C, SPI 등의 자식 장치 추가에 관한 논의를 보충해야 한다는 메모가 남아 있습니다.

부록 A: ARM Primecell과 AMBA 장치

403-420

ARM Primecell은 ARM AMBA 버스에 연결되는 특정 유형의 장치로, 하드웨어 검출과 전원 관리를 일부 지원합니다. Linux에서는 `struct amba_device`와 `amba_bus_type`으로 Primecell 장치를 표현합니다.

문제는 AMBA 버스의 모든 장치가 Primecell은 아니며, Linux에서는 같은 bus segment에서 `amba_device`와 `platform_device` 인스턴스가 sibling인 경우가 일반적이라는 점입니다.

DT를 사용할 때 `of_platform_populate()`는 각 노드를 `platform_device` 또는 `amba_device` 중 무엇으로 등록할지 결정해야 합니다. 이 때문에 장치 생성 모델이 조금 복잡해지지만 해결책은 크지 않습니다.

노드가 `arm,primecell`과 호환되면 `of_platform_populate()`는 그 노드를 `platform_device` 대신 `amba_device`로 등록합니다.