Last active
May 30, 2025 16:05
-
-
Save apua/dd68b7eecc6da87fb9720f0da1228f03 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ref: `Devicetree Specification - 5. Flattened Devicetree (DTB) Format | |
# <https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html>`_ | |
import collections, io, logging, os, sys | |
def bytes2int(b): return int.from_bytes(b, 'big') | |
def int2bytes(i): return int.to_bytes(i, 4, 'big') | |
logging.basicConfig(level=getattr(logging, os.getenv('LOG', 'info').upper()), format='%(levelname)s:%(funcName)s:%(message)s') | |
logger = logging.getLogger() | |
fd = open(sys.argv[1], 'rb') if sys.stdin.isatty() else io.BytesIO(sys.stdin.buffer.read()) | |
def read_bytes(n): | |
if len(buffer := fd.read(n)) < n: raise EOFError | |
else: return buffer | |
# 5.2. Header | |
# =========== | |
magic = read_bytes(4) | |
totalsize = read_bytes(4) | |
off_dt_struct = read_bytes(4) | |
off_dt_strings = read_bytes(4) | |
off_mem_rsvmap = read_bytes(4) | |
version = read_bytes(4) | |
last_comp_version = read_bytes(4) | |
boot_cpuid_phys = read_bytes(4) | |
size_dt_strings = read_bytes(4) | |
size_dt_struct = read_bytes(4) | |
logger.info('%s magic %d', magic.hex(), bytes2int(magic)) | |
logger.info('%s totalsize %d', totalsize.hex(), bytes2int(totalsize)) | |
logger.info('%s off_dt_struct %d', off_dt_struct.hex(), bytes2int(off_dt_struct)) | |
logger.info('%s off_dt_strings %d', off_dt_strings.hex(), bytes2int(off_dt_strings)) | |
logger.info('%s off_mem_rsvmap %d', off_mem_rsvmap.hex(), bytes2int(off_mem_rsvmap)) | |
logger.info('%s version %d', version.hex(), bytes2int(version)) | |
logger.info('%s last_comp_version %d', last_comp_version.hex(), bytes2int(last_comp_version)) | |
logger.info('%s boot_cpuid_phys %d', boot_cpuid_phys.hex(), bytes2int(boot_cpuid_phys)) | |
logger.info('%s size_dt_strings %d', size_dt_strings.hex(), bytes2int(size_dt_strings)) | |
logger.info('%s size_dt_struct %d', size_dt_struct.hex(), bytes2int(size_dt_struct)) | |
if not (magic.hex() == 'd00dfeed'): raise ValueError('FATAL ERROR: Blob has incorrect magic number') | |
if not (bytes2int(off_mem_rsvmap) == (size_header := 4 * 10)): raise ValueError | |
if not (bytes2int(off_dt_strings) == bytes2int(off_dt_struct) + bytes2int(size_dt_struct)): raise ValueError | |
if not (bytes2int(totalsize) == bytes2int(off_dt_strings) + bytes2int(size_dt_strings)): raise ValueError | |
logger.info('version: %d', bytes2int(version)) | |
logger.info('last compatible version: %d', bytes2int(last_comp_version)) | |
logger.info('boot CPU physical ID: %d', bytes2int(boot_cpuid_phys)) | |
# 5.3. Memory Reservation Block | |
# ============================= | |
fd.seek(bytes2int(off_mem_rsvmap)) | |
fdt_reserve_entry = collections.namedtuple('fdt_reserve_entry', 'address, size') | |
# > The list of reserved blocks shall be terminated with an entry where both address and size are equal to 0. | |
mem_rsvmap = [] | |
while (entry := fdt_reserve_entry(read_bytes(8), read_bytes(8))) and not (bytes2int(entry.address) == 0 and bytes2int(entry.size) == 0): | |
mem_rsvmap.append(entry) | |
for m in mem_rsvmap: logger.info('/memreserve/ 0x%s 0x%s;', m.address.hex(), m.size.hex()) | |
# 5.4. Structure Block | |
# ==================== | |
fd.seek(bytes2int(off_dt_struct)) | |
FDT_BEGIN_NODE = int2bytes(0x00000001) | |
FDT_END_NODE = int2bytes(0x00000002) | |
FDT_PROP = int2bytes(0x00000003) | |
FDT_NOP = int2bytes(0x00000004) | |
FDT_END = int2bytes(0x00000009) | |
Node = collections.namedtuple('Node', 'name, properties, children') | |
Property = collections.namedtuple('Property', 'name, value') | |
def get_null_terminated_string(n): | |
buffer = b'' | |
while s := read_bytes(n): | |
buffer += s | |
if s[-1] == 0: | |
return buffer | |
def get_property(): | |
length, nameoff = read_bytes(4), read_bytes(4) | |
logger.debug('get_property length=%s nameoff=%s', length, nameoff) | |
# > This value is followed by zeroed padding bytes (if necessary) to align to the next 32-bit boundary | |
_length = bytes2int(length) | |
padding = (4 - m) if (m := _length % 4) else 0 | |
value = read_bytes(_length + padding) | |
position = fd.tell() | |
fd.seek(bytes2int(off_dt_strings) + bytes2int(nameoff)) | |
# > The strings block has no alignment constraints | |
name = get_null_terminated_string(1) | |
fd.seek(position) | |
logger.debug('get_property name=%s value=%s', name, value) | |
return Property(name, value) | |
def get_node(): | |
name = get_null_terminated_string(4) | |
logger.debug('get_node name=%s', name) | |
properties = [] | |
children = [] | |
while token := read_bytes(4): | |
logger.debug('get_node token=%s', token) | |
if token == FDT_NOP: continue | |
elif token == FDT_BEGIN_NODE: children.append(get_node()) | |
elif token == FDT_PROP: properties.append(get_property()) | |
elif token == FDT_END_NODE: break | |
else: raise ValueError(token) | |
return Node(name, properties, children) | |
while (token := read_bytes(4)) and token == FDT_NOP: pass | |
if not (token == FDT_BEGIN_NODE): raise ValueError | |
root = get_node() | |
while (token := read_bytes(4)) and token == FDT_NOP: pass | |
if not (token == FDT_END): raise ValueError | |
# 6.4. File layout | |
# ================ | |
indent = ' ' * 4 | |
def property2expr(prop, nested): | |
name = prop.name.decode().rstrip('\x00') | |
try: | |
value = prop.value.decode().rstrip('\x00') | |
if not value.isprintable(): raise UnicodeError | |
except UnicodeError: | |
assert len(prop.value) % 4 == 0 | |
vs = (bytes2int(prop.value[i:i+4]) for i in range(0, len(prop.value), 4)) | |
value_repr = '<' + ' '.join(f'0x{i:02x}' for i in vs) + '>' | |
else: | |
value_repr = f'"{value}"' | |
return indent * nested + f'{name} = {value_repr};' | |
def node2expr(node, nested=0): | |
name = node.name.decode().rstrip('\x00') or '/' | |
yield indent * nested + f'{name} {{' | |
for prop in node.properties: | |
yield property2expr(prop, nested+1) | |
for child in node.children: | |
yield from node2expr(child, nested+1) | |
yield indent * nested + '};' | |
dts_lines = ['/dts-v1/;'] | |
dts_lines.extend(f'/memreserve/ 0x{entry.address.hex()} 0x{entry.size.hex()};' for entry in mem_rsvmap) | |
dts_lines.extend(node2expr(root)) | |
print(*dts_lines, sep='\n') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment