The structure block is the devicetree itself. It is a sequence of 32-bit big-endian tokens, some followed by data, with
zero bytes after the data so that every token starts on a multiple of 4 bytes. It sits at the header’s
off_dt_struct. The tokens and the encoding are from the
Devicetree Specification, section 5.4.
Tokens
| Token | Value | What follows |
|---|---|---|
FDT_BEGIN_NODE | 0x00000001 | The node’s name as a string with its terminating zero, including the unit address if there is one (serial@7e215040), then zero bytes up to a multiple of 4. The root node’s name is empty: one zero byte, padded. |
FDT_END_NODE | 0x00000002 | Nothing. It ends the node opened by the matching FDT_BEGIN_NODE. |
FDT_PROP | 0x00000003 | Two words, len and nameoff, then len bytes of value, then zero bytes up to a multiple of 4. nameoff is an offset into the strings block. A len of 0 is an empty property. |
FDT_NOP | 0x00000004 | Nothing. Readers ignore it. A tool can write it over a node or property to delete it without moving the rest. |
FDT_END | 0x00000009 | Nothing. It appears once, as the last token. |
Nodes
A node is encoded as: optional FDT_NOPs, FDT_BEGIN_NODE with its name, optional NOPs and the
node’s properties (each an FDT_PROP), then all of its child nodes, each encoded the same way, then optional
NOPs, and FDT_END_NODE. All of a node’s properties come before its child nodes. The whole block is the root
node followed by one FDT_END.
Because a node’s bytes include everything under it, a node is a contiguous range of the block, and the viewer’s map shows
each node as the range from its FDT_BEGIN_NODE to its FDT_END_NODE, with its properties and child
nodes in that range. A node’s path is not stored: it is the names of the nodes from the root, joined by /.
Properties
A property is a name and a value of bytes. The file does not say what the bytes are: a string, a list of numbers, an address. That comes from the property’s name (see standard properties).
Example
The first bytes of the structure block of the Raspberry Pi Zero 2 W blob, which starts at 0x48:
00000048: 0000 0001 0000 0000 FDT_BEGIN_NODE, name "" (the root), padded
00000050: 0000 0003 0000 0028 0000 0000 FDT_PROP, len 40, nameoff 0 (compatible)
0000005c: "raspberrypi,model-zero-2-w" 00 "brcm,bcm2837" 00 the 40 bytes of the value: two strings
00000084: 0000 0003 0000 0016 0000 000b FDT_PROP, len 22, nameoff 11 (model)
00000090: "Raspberry Pi Zero 2 W" 00 + 2 zero bytes 22 bytes, padded to 24
The name at offset 0 of the strings block is compatible and the one at offset 11, directly after its zero
byte, is model.
Versions below 16
From libfdt, the reference reader: before version 16 a node’s name in the stream is its full path (/soc/serial@1000),
and libfdt gives the part after the last /; and a property value of 8 bytes or more starts on an 8-byte boundary
of the blob, so 4 zero bytes are added before it when it would not. Version 16 dropped both. The viewer applies these rules
to older blobs. It was run on version 3 and version 16 blobs written by dtc, not on a real file of those versions.
What the viewer checks
That each token is one of the five, that names and values end inside the block, that every name offset is inside the strings
block and ends in a zero, that nodes are closed and that FDT_END comes once at the end, that properties come
before child nodes, that the root is the only top-level node and has the empty name, that no node has two properties or two
children of the same name, and that no two nodes have the same phandle. A problem is listed and the walk goes on as far as the
bytes allow.