Flattened Devicetree Format: Go parsing library

Also referred to as Devicetree Blob (DTB). It is a flat binary encoding of data (primarily devicetree data, although other data is possible as well). The data is internally stored as a tree of named nodes and properties. Nodes contain properties and child nodes, while properties are name-value pairs.

The Devicetree Blobs (.dtb files) are compiled from the Devicetree Source files (.dts) through the Devicetree compiler (DTC).

On Linux systems that support this, the blobs can be accessed in /sys/firmware/fdt:

The encoding of strings used in the strings_block and structure_block is actually a subset of ASCII:

https://devicetree-specification.readthedocs.io/en/v0.3/devicetree-basics.html#node-names

Example files:

Application

["Linux", "Das U-Boot"]

File extension

dtb

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Flattened Devicetree Format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Go source code to parse Flattened Devicetree Format

dtb.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
	"io"
)


/**
 * Also referred to as Devicetree Blob (DTB). It is a flat binary encoding
 * of data (primarily devicetree data, although other data is possible as well).
 * The data is internally stored as a tree of named nodes and properties. Nodes
 * contain properties and child nodes, while properties are name-value pairs.
 * 
 * The Devicetree Blobs (`.dtb` files) are compiled from the Devicetree Source
 * files (`.dts`) through the Devicetree compiler (DTC).
 * 
 * On Linux systems that support this, the blobs can be accessed in
 * `/sys/firmware/fdt`:
 * 
 * * <https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-firmware-ofw>
 * 
 * The encoding of strings used in the `strings_block` and `structure_block` is
 * actually a subset of ASCII:
 * 
 * <https://devicetree-specification.readthedocs.io/en/v0.3/devicetree-basics.html#node-names>
 * 
 * Example files:
 * 
 * * <https://github.com/qemu/qemu/tree/master/pc-bios>
 * @see <a href="https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html">Source</a>
 * @see <a href="https://elinux.org/images/f/f4/Elc2013_Fernandes.pdf">Source</a>
 */

type Dtb_Fdt int
const (
	Dtb_Fdt__BeginNode Dtb_Fdt = 1
	Dtb_Fdt__EndNode Dtb_Fdt = 2
	Dtb_Fdt__Prop Dtb_Fdt = 3
	Dtb_Fdt__Nop Dtb_Fdt = 4
	Dtb_Fdt__End Dtb_Fdt = 9
)
var values_Dtb_Fdt = map[Dtb_Fdt]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 9: {}}
func (v Dtb_Fdt) isDefined() bool {
	_, ok := values_Dtb_Fdt[v]
	return ok
}
type Dtb struct {
	Magic []byte
	TotalSize uint32
	OfsStructureBlock uint32
	OfsStringsBlock uint32
	OfsMemoryReservationBlock uint32
	Version uint32
	MinCompatibleVersion uint32
	BootCpuidPhys uint32
	LenStringsBlock uint32
	LenStructureBlock uint32
	_io *kaitai.Stream
	_root *Dtb
	_parent kaitai.Struct
	_raw_memoryReservationBlock []byte
	_raw_stringsBlock []byte
	_raw_structureBlock []byte
	_f_memoryReservationBlock bool
	memoryReservationBlock *Dtb_MemoryBlock
	_f_stringsBlock bool
	stringsBlock *Dtb_Strings
	_f_structureBlock bool
	structureBlock *Dtb_FdtBlock
}
func NewDtb() *Dtb {
	return &Dtb{
	}
}

func (this Dtb) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb) Read(io *kaitai.Stream, parent kaitai.Struct, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp1 = tmp1
	this.Magic = tmp1
	if !(bytes.Equal(this.Magic, []uint8{208, 13, 254, 237})) {
		return kaitai.NewValidationNotEqualError([]uint8{208, 13, 254, 237}, this.Magic, this._io, "/seq/0")
	}
	tmp2, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.TotalSize = uint32(tmp2)
	tmp3, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.OfsStructureBlock = uint32(tmp3)
	tmp4, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.OfsStringsBlock = uint32(tmp4)
	tmp5, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.OfsMemoryReservationBlock = uint32(tmp5)
	tmp6, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.Version = uint32(tmp6)
	tmp7, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.MinCompatibleVersion = uint32(tmp7)
	if !(this.MinCompatibleVersion <= this.Version) {
		return kaitai.NewValidationGreaterThanError(this.Version, this.MinCompatibleVersion, this._io, "/seq/6")
	}
	tmp8, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.BootCpuidPhys = uint32(tmp8)
	tmp9, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.LenStringsBlock = uint32(tmp9)
	tmp10, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.LenStructureBlock = uint32(tmp10)
	return err
}
func (this *Dtb) MemoryReservationBlock() (v *Dtb_MemoryBlock, err error) {
	if (this._f_memoryReservationBlock) {
		return this.memoryReservationBlock, nil
	}
	this._f_memoryReservationBlock = true
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.OfsMemoryReservationBlock), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp11, err := this._io.ReadBytes(int(this.OfsStructureBlock - this.OfsMemoryReservationBlock))
	if err != nil {
		return nil, err
	}
	tmp11 = tmp11
	this._raw_memoryReservationBlock = tmp11
	_io__raw_memoryReservationBlock := kaitai.NewStream(bytes.NewReader(this._raw_memoryReservationBlock))
	tmp12 := NewDtb_MemoryBlock()
	err = tmp12.Read(_io__raw_memoryReservationBlock, this, this._root)
	if err != nil {
		return nil, err
	}
	this.memoryReservationBlock = tmp12
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return this.memoryReservationBlock, nil
}
func (this *Dtb) StringsBlock() (v *Dtb_Strings, err error) {
	if (this._f_stringsBlock) {
		return this.stringsBlock, nil
	}
	this._f_stringsBlock = true
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.OfsStringsBlock), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp13, err := this._io.ReadBytes(int(this.LenStringsBlock))
	if err != nil {
		return nil, err
	}
	tmp13 = tmp13
	this._raw_stringsBlock = tmp13
	_io__raw_stringsBlock := kaitai.NewStream(bytes.NewReader(this._raw_stringsBlock))
	tmp14 := NewDtb_Strings()
	err = tmp14.Read(_io__raw_stringsBlock, this, this._root)
	if err != nil {
		return nil, err
	}
	this.stringsBlock = tmp14
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return this.stringsBlock, nil
}
func (this *Dtb) StructureBlock() (v *Dtb_FdtBlock, err error) {
	if (this._f_structureBlock) {
		return this.structureBlock, nil
	}
	this._f_structureBlock = true
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.OfsStructureBlock), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp15, err := this._io.ReadBytes(int(this.LenStructureBlock))
	if err != nil {
		return nil, err
	}
	tmp15 = tmp15
	this._raw_structureBlock = tmp15
	_io__raw_structureBlock := kaitai.NewStream(bytes.NewReader(this._raw_structureBlock))
	tmp16 := NewDtb_FdtBlock()
	err = tmp16.Read(_io__raw_structureBlock, this, this._root)
	if err != nil {
		return nil, err
	}
	this.structureBlock = tmp16
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return this.structureBlock, nil
}
type Dtb_FdtBeginNode struct {
	Name string
	Padding []byte
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb_FdtNode
}
func NewDtb_FdtBeginNode() *Dtb_FdtBeginNode {
	return &Dtb_FdtBeginNode{
	}
}

func (this Dtb_FdtBeginNode) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_FdtBeginNode) Read(io *kaitai.Stream, parent *Dtb_FdtNode, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp17, err := this._io.ReadBytesTerm(0, false, true, true)
	if err != nil {
		return err
	}
	this.Name = string(tmp17)
	tmp19, err := this._io.Pos()
	if err != nil {
		return err
	}
	tmp18 := -(tmp19) % 4
	if tmp18 < 0 {
		tmp18 += 4
	}
	tmp20, err := this._io.ReadBytes(int(tmp18))
	if err != nil {
		return err
	}
	tmp20 = tmp20
	this.Padding = tmp20
	return err
}
type Dtb_FdtBlock struct {
	Nodes []*Dtb_FdtNode
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb
}
func NewDtb_FdtBlock() *Dtb_FdtBlock {
	return &Dtb_FdtBlock{
	}
}

func (this Dtb_FdtBlock) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_FdtBlock) Read(io *kaitai.Stream, parent *Dtb, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 1;; i++ {
		tmp21 := NewDtb_FdtNode()
		err = tmp21.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		_it := tmp21
		this.Nodes = append(this.Nodes, _it)
		if _it.Type == Dtb_Fdt__End {
			break
		}
	}
	return err
}
type Dtb_FdtNode struct {
	Type Dtb_Fdt
	Body kaitai.Struct
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb_FdtBlock
}
func NewDtb_FdtNode() *Dtb_FdtNode {
	return &Dtb_FdtNode{
	}
}

func (this Dtb_FdtNode) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_FdtNode) Read(io *kaitai.Stream, parent *Dtb_FdtBlock, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp22, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.Type = Dtb_Fdt(tmp22)
	switch (this.Type) {
	case Dtb_Fdt__BeginNode:
		tmp23 := NewDtb_FdtBeginNode()
		err = tmp23.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp23
	case Dtb_Fdt__Prop:
		tmp24 := NewDtb_FdtProp()
		err = tmp24.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp24
	}
	return err
}
type Dtb_FdtProp struct {
	LenProperty uint32
	OfsName uint32
	Property []byte
	Padding []byte
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb_FdtNode
	_f_name bool
	name string
}
func NewDtb_FdtProp() *Dtb_FdtProp {
	return &Dtb_FdtProp{
	}
}

func (this Dtb_FdtProp) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_FdtProp) Read(io *kaitai.Stream, parent *Dtb_FdtNode, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp25, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.LenProperty = uint32(tmp25)
	tmp26, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.OfsName = uint32(tmp26)
	tmp27, err := this._io.ReadBytes(int(this.LenProperty))
	if err != nil {
		return err
	}
	tmp27 = tmp27
	this.Property = tmp27
	tmp29, err := this._io.Pos()
	if err != nil {
		return err
	}
	tmp28 := -(tmp29) % 4
	if tmp28 < 0 {
		tmp28 += 4
	}
	tmp30, err := this._io.ReadBytes(int(tmp28))
	if err != nil {
		return err
	}
	tmp30 = tmp30
	this.Padding = tmp30
	return err
}
func (this *Dtb_FdtProp) Name() (v string, err error) {
	if (this._f_name) {
		return this.name, nil
	}
	this._f_name = true
	tmp31, err := this._root.StringsBlock()
	if err != nil {
		return "", err
	}
	thisIo := tmp31._io
	_pos, err := thisIo.Pos()
	if err != nil {
		return "", err
	}
	_, err = thisIo.Seek(int64(this.OfsName), io.SeekStart)
	if err != nil {
		return "", err
	}
	tmp32, err := thisIo.ReadBytesTerm(0, false, true, true)
	if err != nil {
		return "", err
	}
	this.name = string(tmp32)
	_, err = thisIo.Seek(_pos, io.SeekStart)
	if err != nil {
		return "", err
	}
	return this.name, nil
}
type Dtb_MemoryBlock struct {
	Entries []*Dtb_MemoryBlockEntry
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb
}
func NewDtb_MemoryBlock() *Dtb_MemoryBlock {
	return &Dtb_MemoryBlock{
	}
}

func (this Dtb_MemoryBlock) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_MemoryBlock) Read(io *kaitai.Stream, parent *Dtb, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0;; i++ {
		tmp33, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp33 {
			break
		}
		tmp34 := NewDtb_MemoryBlockEntry()
		err = tmp34.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Entries = append(this.Entries, tmp34)
	}
	return err
}
type Dtb_MemoryBlockEntry struct {
	Address uint64
	Size uint64
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb_MemoryBlock
}
func NewDtb_MemoryBlockEntry() *Dtb_MemoryBlockEntry {
	return &Dtb_MemoryBlockEntry{
	}
}

func (this Dtb_MemoryBlockEntry) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_MemoryBlockEntry) Read(io *kaitai.Stream, parent *Dtb_MemoryBlock, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp35, err := this._io.ReadU8be()
	if err != nil {
		return err
	}
	this.Address = uint64(tmp35)
	tmp36, err := this._io.ReadU8be()
	if err != nil {
		return err
	}
	this.Size = uint64(tmp36)
	return err
}

/**
 * physical address of a reserved memory region
 */

/**
 * size of a reserved memory region
 */
type Dtb_Strings struct {
	Strings []string
	_io *kaitai.Stream
	_root *Dtb
	_parent *Dtb
}
func NewDtb_Strings() *Dtb_Strings {
	return &Dtb_Strings{
	}
}

func (this Dtb_Strings) IO_() *kaitai.Stream {
	return this._io
}

func (this *Dtb_Strings) Read(io *kaitai.Stream, parent *Dtb, root *Dtb) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0;; i++ {
		tmp37, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp37 {
			break
		}
		tmp38, err := this._io.ReadBytesTerm(0, false, true, true)
		if err != nil {
			return err
		}
		this.Strings = append(this.Strings, string(tmp38))
	}
	return err
}