[PATCH v4 4/8] tools: qcom: add mkmbn.py
Casey Connolly
casey.connolly at linaro.org
Fri Jun 13 19:23:08 CEST 2025
Adjust the elf class to support creating ELF files from scratch so that
mkmbn can build an MBN file from the U-Boot binary image and fix some
imports to work correctly in the U-Boot build system.
Since we already need to process the ELF file to produce a valid "MBN"
which sbl1 will accept, mkmbn additionally inspects the U-Boot binary,
finding the DTB and then checking for known compatible strings to
identify the board or platform.
Signed-off-by: Casey Connolly <casey.connolly at linaro.org>
---
tools/mkmbn | 1 +
tools/qcom/mkmbn/elf.py | 36 +++++++++++
tools/qcom/mkmbn/hashseg.py | 4 +-
tools/qcom/mkmbn/mkmbn.py | 154 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 193 insertions(+), 2 deletions(-)
diff --git a/tools/mkmbn b/tools/mkmbn
new file mode 120000
index 0000000000000000000000000000000000000000..a7b2096756f76c07ca21e73c63f3b8be28a4cf59
--- /dev/null
+++ b/tools/mkmbn
@@ -0,0 +1 @@
+qcom/mkmbn/mkmbn.py
\ No newline at end of file
diff --git a/tools/qcom/mkmbn/elf.py b/tools/qcom/mkmbn/elf.py
index 9fac35cfd5d272a07ee56c3a2cebe36e6f9fbe4a..86f12d1f4e5deb102dd97d1616f64b7e3215b96e 100644
--- a/tools/qcom/mkmbn/elf.py
+++ b/tools/qcom/mkmbn/elf.py
@@ -44,8 +44,24 @@ class Ehdr:
CLASS32 = 1
CLASS64 = 2
+ # Init a qcom XBL style ELF header
+ def __init__(self):
+ self.ei_magic = b"\x7fELF"
+ self.ei_class = 2
+ self.ei_data = 1
+ self.ei_version = 1
+ self.ei_os_abi = 0
+ self.ei_abi_version = 0
+ self.e_type = 2
+ self.e_machine = 183
+ self.e_version = 1
+
+ self.e_ehsize = 64
+ self.e_phoff = 64
+ self.e_phentsize = 56
+
@staticmethod
def parse(b: bytes) -> Ehdr:
hdr_unpack = Ehdr.START_FORMAT.unpack_from(b)
hdr = Ehdr(*hdr_unpack)
@@ -106,8 +122,24 @@ class Phdr:
unpack.insert(-1, flags)
return Phdr(*unpack)
+ @staticmethod
+ def from_bin(b: bytes, loadaddr: int) -> Phdr:
+ # p_offset is fixed later
+ phdr = Phdr(
+ p_type=1,
+ p_offset=0xFFFFFFFF,
+ p_vaddr=loadaddr,
+ p_paddr=loadaddr,
+ p_filesz=len(b),
+ p_memsz=len(b),
+ p_flags=7,
+ p_align=0x1000,
+ )
+ phdr.data = memoryview(b)
+ return phdr
+
def save(self, f: BinaryIO, ei_class: int) -> int:
unpack = dataclasses.astuple(self)
if ei_class == Ehdr.CLASS32:
@@ -139,8 +171,12 @@ def _align(i: int, alignment: int) -> int:
class Elf:
ehdr: Ehdr
phdrs: List[Phdr]
+ def __init__(self):
+ self.ehdr = Ehdr()
+ self.phdrs: List[Phdr] = []
+
def total_header_size(self):
return self.ehdr.e_phoff + len(self.phdrs) * self.ehdr.e_phentsize
@staticmethod
diff --git a/tools/qcom/mkmbn/hashseg.py b/tools/qcom/mkmbn/hashseg.py
index e73f6e94e163dd1e45cc341f76cfebf55685ded8..11e6a7b39f0fefeb0dd4ea86dd279e6dd4f96532 100644
--- a/tools/qcom/mkmbn/hashseg.py
+++ b/tools/qcom/mkmbn/hashseg.py
@@ -14,10 +14,10 @@ import hashlib
from dataclasses import dataclass
from io import BytesIO
from struct import Struct
-from . import cert
-from . import elf
+import cert
+import elf
# A typical Qualcomm firmware might have the following program headers:
# LOAD off 0x00000800 vaddr 0x86400000 paddr 0x86400000 align 2**11
# filesz 0x00001000 memsz 0x00001000 flags rwx
diff --git a/tools/qcom/mkmbn/mkmbn.py b/tools/qcom/mkmbn/mkmbn.py
new file mode 100755
index 0000000000000000000000000000000000000000..94d85275c19c01c1e0aa64a61b615d14c10c0468
--- /dev/null
+++ b/tools/qcom/mkmbn/mkmbn.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright (C) 2024 Stephan Gerhold
+# Copyright (C) 2025 Casey Connolly
+from __future__ import annotations
+
+import argparse
+from pathlib import Path
+
+from elf import Elf, Phdr
+import hashseg
+import sys
+from enum import Enum
+
+verbose = False
+
+def log(*args, **kwargs):
+ if verbose:
+ print(args, kwargs, file=sys.stderr)
+
+def error(*args, **kwargs):
+ print("mkmbn: ", file=sys.stderr, end='')
+ print(*args, *kwargs, file=sys.stderr)
+
+
+
+class SwId(Enum):
+ sbl1 = 0x00
+ mba = 0x01
+ modem = 0x02
+ prog = 0x03
+ adsp = 0x04
+ devcfg = 0x05
+ tz = 0x07
+ aboot = 0x09
+ rpm = 0x0A
+ tz_app = 0x0C
+ wcnss = 0x0D
+ venus = 0x0E
+ wlanmdsp = 0x12
+ gpu = 0x14
+ hyp = 0x15
+ cdsp = 0x17
+ slpi = 0x18
+ abl = 0x1C
+ cmnlib = 0x1F
+ aop = 0x21
+ qup = 0x24
+ xbl_config = 0x25
+
+class MbnData:
+
+ # sw_id 0x9 is aboot/uefi, the most common
+ def __init__(self, loadaddr: int, version: int, sw_id: SwId = SwId.aboot):
+ self.loadaddr = loadaddr
+ self.version = version
+ self.sw_id = sw_id
+
+
+"""
+This dictionary is used to map a board or platform to the appropriate load address and
+other MBN metadata. When adding support for a new platform to U-Boot, the appropriate
+data should be filled out here. The load address can typically be determined by looking
+at the uefi.elf or xbl.elf for the platform. For the uefi.elf it is the load address, and
+for xbl.elf it is typically the RWX section in the middle, just BEFORE the section loaded
+at 0x1495xxxx or similar. Looking at similar platforms in the table below may help.
+"""
+boards: dict[bytes, MbnData] = {
+ # Exact matches for boards, these are preferred
+ b"qcom,qcs6490-rb3gen2\0": MbnData(0x9FC00000, 6, SwId.aboot),
+ b"qcom,qcs9100-ride-r3\0": MbnData(0xAF000000, 6, SwId.aboot), # Dragonwing IQ9
+ b"qcom,qcs8300-ride\0": MbnData(0xAF000000, 6, SwId.aboot), # Dragonwing IQ8
+ b"qcom,qcs615-ride\0": MbnData(0x9FC00000, 6, SwId.aboot), # Dragonwing IQ6
+ # Fallback/generic matches since most boards for a platform will
+ # use the same load address
+ b"qcom,qcm6490\0": MbnData(0x9FC00000, 6, SwId.aboot), # rb3gen2, rubikpi3
+ b"qcom,qcs9100\0": MbnData(0xAF000000, 6, SwId.aboot), # Dragonwing IQ9
+ b"qcom,qcs8300\0": MbnData(0xAF000000, 6, SwId.aboot), # Dragonwing IQ8
+ b"qcom,qcs615\0": MbnData(0x9FC00000, 6, SwId.aboot), # Dragonwing IQ6
+ b"qcom,ipq9574\0": MbnData(0x4A240000, 6, SwId.aboot),
+
+ # msm8916/apq8016 has an "aboot" partition but the process is the same
+ # They use header version 3.
+ b"qcom,apq8016\0": MbnData(0x8f600000, 3, SwId.aboot),
+ b"qcom,msm8916\0": MbnData(0x8f600000, 3, SwId.aboot),
+}
+
+parser = argparse.ArgumentParser(
+ description="""
+ Create a signed Qualcomm "uefi" ELF image
+"""
+)
+parser.register("type", "hex", lambda s: int(s, 16))
+parser.add_argument(
+ "-o", "--output", type=Path, default="u-boot.mbn", help="Output file"
+)
+parser.add_argument(
+ "-v", dest="verbose", action="store_true", default=False, help="Verbose"
+)
+parser.add_argument(
+ "bin", type=argparse.FileType("rb"), help="Binary to embed (e.g. u-boot.bin)"
+)
+args = parser.parse_args()
+
+elf = Elf()
+
+data: bytes = args.bin.read()
+
+# dtb is at the end, so find the last match
+dtb_off = 0
+off = 0
+while True:
+ off = data.find(b"\xd0\x0d\xfe\xed", dtb_off + 1)
+ if off == -1:
+ break
+ dtb_off = off
+
+if not dtb_off:
+ print("Couldn't find DTB in provided binary!")
+ exit(1)
+
+log(f"Found FDT at {dtb_off:#x}")
+
+mbn: MbnData|None = None
+
+for match, mbndata in boards.items():
+ if data.find(match, dtb_off) != -1:
+ mbn = mbndata
+ break
+
+if not mbn:
+ error(
+ "Not building an MBN file for this board, see tools/qcom/mkmbn/mkmbn.py for details"
+ )
+ # Bailing out would fail the build, and it's not possible to know if an MBN
+ # is actually needed for the board we're building for. Minimise confusion by removing
+ # any file that might exist from a previous build and exit with a known code.
+ args.output.unlink(missing_ok=True)
+ exit(61)
+
+log(f"Detected board {match.decode('UTF-8')} with load address {mbn.loadaddr:#x}")
+
+elf.phdrs.append(Phdr.from_bin(data, mbn.loadaddr))
+elf.ehdr.e_entry = mbn.loadaddr
+elf.update()
+
+# QLI boards use v6 sw_id is "aboot"
+hashseg.generate(elf, mbn.version, mbn.sw_id.value)
+# print(f"after: {elf}")
+
+with open(args.output, "wb") as f:
+ elf.save(f)
+
+log(f"Built signed MBN: {args.output.resolve()}")
--
2.49.0
More information about the U-Boot
mailing list