Skip to content

dtb.py

ofrak_components.dtb

Device Tree Blob (or Flattened Device Tree) OFRAK Utilities For more information see: https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html

DeviceTreeBlob (GenericBinary)

A Device Tree Blob (DTB).

DeviceTreeBlobIdentifier (Identifier)

Identify Device Tree Blob files.

identify(self, resource, config) async

Identify DTB files based on the first four bytes being "d00dfeed".

Source code in ofrak_components/dtb.py
async def identify(self, resource: Resource, config: None) -> None:
    """
    Identify DTB files based on the first four bytes being "d00dfeed".
    """
    data = await resource.get_data(Range(0, 4))
    if data == struct.pack("<I", DTB_MAGIC_SIGNATURE):
        resource.add_tag(DeviceTreeBlob)

DeviceTreeBlobPacker (Packer)

Device Tree Blob Packer

Repacks the Device Tree Blob tree structure into the binary format and patches the original resource.

pack(self, resource, config=None) async

Pack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.pack.

Parameters:

Name Type Description Default
resource Resource required
config ~CC

Optional config for packing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_components/dtb.py
async def pack(self, resource: Resource, config: CC = None):
    header = fdt.Header()
    header_view = await resource.get_only_descendant_as_view(
        v_type=DtbHeader, r_filter=ResourceFilter(tags=[DtbHeader])
    )

    header.version = header_view.version
    header.total_size = header_view.totalsize
    header.off_dt_struct = header_view.off_dt_struct
    header.last_comp_version = header_view.last_comp_version
    header.boot_cpuid_phys = header_view.boot_cpuid_phys

    dtb = fdt.FDT(header=header)

    dtb.entries = [
        {"address": entry.address, "size": entry.size}
        for entry in await resource.get_descendants_as_view(
            v_type=DtbEntry, r_filter=ResourceFilter(tags=[DtbEntry])
        )
    ]

    root_node_view = await resource.get_only_child_as_view(
        DtbNode, r_filter=ResourceFilter(tags=[DtbNode])
    )
    dtb.root = fdt.Node(name=await root_node_view.get_path())
    for prop in await root_node_view.resource.get_children_as_view(
        v_type=DtbProperty,
        r_filter=ResourceFilter(tags=[DtbProperty]),
        r_sort=ResourceSort(DtbProperty.DtbPropertyName),
    ):
        dtb.add_item(await _prop_to_fdt(prop), await root_node_view.get_path())
    for node in await root_node_view.resource.get_descendants_as_view(
        v_type=DtbNode,
        r_filter=ResourceFilter(tags=[DtbNode]),
        r_sort=ResourceSort(DtbNode.DtbNodeName),
    ):
        # By default, add_item adds the missing nodes to complete the path of a previous node
        if not dtb.exist_node(await node.get_path()):
            dtb.add_item(fdt.Node(node.name), os.path.dirname(await node.get_path()))
        for prop in await node.resource.get_children_as_view(
            v_type=DtbProperty,
            r_filter=ResourceFilter(tags=[DtbProperty]),
            r_sort=ResourceSort(DtbProperty.DtbPropertyName),
        ):
            dtb.add_item(await _prop_to_fdt(prop), await node.get_path())
    original_size = await resource.get_data_length()
    resource.queue_patch(Range(0, original_size), dtb.to_dtb())

DeviceTreeBlobUnpacker (Unpacker)

Unpacks a DeviceTreeBlob:

A DeviceTreeBlob consists of: - 1 DtbHeader - 0 or more DtbEntry instances - 1 root DtbNode which can contain 0 or more DtbNode and DtbProperty children - Each DtbNode can have 0 or more DtbProperty children and 0 or more further nested DtbNode children in it

unpack(self, resource, config=None) async

Unpack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.unpack.

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config ~CC

Optional config for unpacking. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_components/dtb.py
async def unpack(self, resource: Resource, config: CC = None):
    dtb_data = await resource.get_data()
    dtb_view = await resource.view_as(DeviceTreeBlob)
    dtb = fdt.parse_dtb(dtb_data)

    # Create DtbHeader
    await resource.create_child(
        tags=(DtbHeader,),
        data=dtb.header.export(),
    )

    # Create DtbEntry instances
    for dtb_entry in dtb.entries:
        await resource.create_child_from_view(
            DtbEntry(
                address=dtb_entry["address"],
                size=dtb_entry["size"],
            ),
            data=b"",
        )

    # Create root node
    await resource.create_child_from_view(
        DtbNode(name=dtb.root.name),
        data=b"",
    )

    # Create DtbNode and DtbProperty instances and structure by walking the DeviceTreeBlob
    for path, nodes, props in dtb.walk():
        # Get parent
        parent_node = await dtb_view.get_node_by_path(path)
        for node in nodes:
            await parent_node.resource.create_child_from_view(DtbNode(name=node.name), data=b"")
        for prop in props:
            p_type, p_data = _prop_from_fdt(prop)

            await parent_node.resource.create_child_from_view(
                DtbProperty(
                    name=prop.name,
                    p_type=p_type,
                ),
                data=p_data,
            )

DtbEntry (GenericBinary) dataclass

Device Tree Entry

DtbHeader (GenericBinary) dataclass

Device Tree Header

DtbHeaderAnalyzer (Analyzer)

Analyze Device Tree Blob header information and return a DtbHeader

analyze(self, resource, config) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config ~CC

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

required

Returns:

Type Description
DtbHeader

The analysis results

Source code in ofrak_components/dtb.py
async def analyze(self, resource: Resource, config: CC) -> DtbHeader:
    header_data = await resource.get_data()
    (
        dtb_magic,
        totalsize,
        off_dt_struct,
        off_dt_strings,
        off_mem_rsvmap,
        version,
        last_comp_version,
    ) = struct.unpack(">IIIIIII", header_data[:28])
    assert dtb_magic == DTB_MAGIC_SIGNATURE, (
        f"DTB Magic bytes not matching."
        f"Expected: {DTB_MAGIC_SIGNATURE} "
        f"Unpacked: {dtb_magic}"
    )
    boot_cpuid_phys = 0
    dtb_strings_size = 0
    dtb_struct_size = 0
    if version >= 2:
        boot_cpuid_phys = struct.unpack(">I", header_data[28:32])[0]
    if version >= 3:
        dtb_strings_size = struct.unpack(">I", header_data[32:36])[0]
    if version >= 17:
        dtb_struct_size = struct.unpack(">I", header_data[36:40])[0]

    return DtbHeader(
        dtb_magic,
        totalsize,
        off_dt_struct,
        off_dt_strings,
        off_mem_rsvmap,
        version,
        last_comp_version,
        boot_cpuid_phys,
        dtb_strings_size,
        dtb_struct_size,
    )

DtbNode (GenericBinary) dataclass

Device Tree Node

get_path(self) async

Get the path of a DtbNode within a DeviceTreeBlob. Root node is always "/" per DTB specifications.

Source code in ofrak_components/dtb.py
async def get_path(self) -> str:
    """
    Get the path of a DtbNode within a DeviceTreeBlob.
    Root node is always "/" per DTB specifications.
    """
    if self.name == "/":
        return self.name

    parent_node = await self.resource.get_parent_as_view(v_type=DtbNode)
    return os.path.join(await parent_node.get_path(), self.name)

DtbProperty (GenericBinary) dataclass

DTB Property

DtbPropertyType (Enum)

An enumeration.

_prop_to_fdt(p) async private

Generates an fdt.items.property corresponding to a DtbProperty.

Parameters:

Name Type Description Default
p DtbProperty required

Returns:

Type Description
Property
Source code in ofrak_components/dtb.py
async def _prop_to_fdt(p: DtbProperty) -> fdt.items.Property:
    """
    Generates an fdt.items.property corresponding to a DtbProperty.
    :param p:
    :return:
    """
    value = await p.get_value()
    if p.p_type is DtbPropertyType.DtbPropNoValue:
        return fdt.items.Property(name=p.name)
    elif p.p_type is DtbPropertyType.DtbBytes:
        return fdt.items.PropBytes(name=p.name, data=await p.resource.get_data())
    elif p.p_type is DtbPropertyType.DtbInt:
        return fdt.items.PropWords(p.name, value)
    elif p.p_type is DtbPropertyType.DtbIntList:
        return fdt.items.PropWords(p.name, *value)
    elif p.p_type is DtbPropertyType.DtbStr:
        return fdt.items.PropStrings(p.name, value)
    elif p.p_type is DtbPropertyType.DtbStrList:
        return fdt.items.PropStrings(p.name, *value)
    else:
        raise TypeError(f"Unsupported type {p.p_type} for property {p.name}")

_prop_from_fdt(p) private

Converts an fdt.items.property to its p_type and p_data values.

Parameters:

Name Type Description Default
p Property required

Returns:

Type Description
Tuple[ofrak_components.dtb.DtbPropertyType, bytes]
Source code in ofrak_components/dtb.py
def _prop_from_fdt(p: fdt.items.Property) -> Tuple[DtbPropertyType, bytes]:
    """
    Converts an fdt.items.property to its p_type and p_data values.
    :param p:
    :return:
    """
    if type(p) is fdt.items.Property or len(p.data) == 0:
        _p_type = DtbPropertyType.DtbPropNoValue
        _p_data = b""
    elif type(p) is fdt.items.PropBytes:
        _p_type = DtbPropertyType.DtbBytes
        _p_data = bytes(p.data)
    elif isinstance(p.value, int):
        if len(p.data) == 1:
            _p_type = DtbPropertyType.DtbInt
        else:
            _p_type = DtbPropertyType.DtbIntList
        _p_data = b"".join([struct.pack(">I", i) for i in p.data])
    elif isinstance(p.value, str):
        if len(p.data) == 1:
            _p_type = DtbPropertyType.DtbStr
            _p_data = b"".join([s.encode("ascii") for s in p.data])
        else:
            _p_type = DtbPropertyType.DtbStrList
            _p_data = b"\0".join([s.encode("ascii") for s in p.data])

    else:
        raise TypeError(f"Unknown type for DTB Property: {p}")
    return _p_type, _p_data