dtb.py
ofrak.core.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).
DeviceTreeBlobPacker (Packer)
Repacks the modified Device Tree structure back into binary DTB (Device Tree Blob) format suitable for bootloaders and Linux kernels. The packer regenerates all internal offsets, updates the strings block and data block, and creates a valid DTB that can be used for booting. Use after modifying device tree nodes, properties, or hardware configurations to generate a working DTB file.
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 |
ComponentConfig |
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/core/dtb.py
async def pack(self, resource: Resource, config: ComponentConfig = 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), posixpath.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)
Parses Device Tree Blob (DTB/FDT) files into their hierarchical structure of nodes and properties that describe hardware configurations for embedded Linux systems. The unpacker extracts the header with version and size information, the structure block containing the node hierarchy, the strings block with property names, and the data block with property values. Use when analyzing embedded Linux device trees, understanding hardware peripheral configurations, examining memory maps, or preparing to modify device configurations. Device trees are critical for ARM-based embedded systems and describe CPUs, memory, buses, and peripheral devices.
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 |
ComponentConfig |
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/core/dtb.py
async def unpack(self, resource: Resource, config: ComponentConfig = 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)
Parses Device Tree Blob (DTB) header to extract version number, total DTB size, structure block offset and size, strings block offset and size, memory reservation block offset and size, and various format version fields. The header describes the layout of all DTB sections. Use to understand DTB file structure before full unpacking, validate DTB integrity by checking sizes and offsets, determine DTB version for compatibility, or navigate to specific DTB sections. Essential first step in device tree analysis.
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 |
None |
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/core/dtb.py
async def analyze(self, resource: Resource, config: None) -> 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/core/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 posixpath.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/core/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.core.dtb.DtbPropertyType, bytes] |
Source code in ofrak/core/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