Last active
August 20, 2025 05:58
-
-
Save loopj/0f2cf01cb88f8796dd05dc6a04732dc9 to your computer and use it in GitHub Desktop.
Create a simple (non encrypted, non compressed) Gecko Bootloader .gbl file from a .bin file.
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
| #!/usr/bin/env python3 | |
| """ | |
| Create a simple (non encrypted, non compressed) Gecko Bootloader .gbl file from a .bin file. | |
| Documented in Silicon Labs UG266: | |
| https://www.silabs.com/documents/public/user-guides/ug266-gecko-bootloader-user-guide.pdf | |
| """ | |
| import sys | |
| import struct | |
| import zlib | |
| from argparse import ArgumentParser, ArgumentTypeError | |
| from pathlib import Path | |
| from typing import ByteString | |
| # GBL format version | |
| GBL_VERSION = 0x03000000 | |
| # Tag IDs | |
| GBL_TAG_ID_HEADER = 0x03A617EB | |
| GBL_TAG_ID_VERSION_DEPENDENCY = 0x76A617EB | |
| GBL_TAG_ID_APPLICATION_INFO = 0xF40A0AF4 | |
| GBL_TAG_ID_SE_UPGRADE = 0x5EA617EB | |
| GBL_TAG_ID_BOOTLOADER = 0xF50909F5 | |
| GBL_TAG_ID_PROGRAM_DATA = 0xFD0303FD | |
| GBL_TAG_ID_DELTA = 0xF80A0AF8 | |
| GBL_TAG_ID_PROGRAM_LZ4 = 0xFD0505FD | |
| GBL_TAG_ID_DELTA_LZ4 = 0xF80B0BF8 | |
| GBL_TAG_ID_PROGRAM_LZMA = 0xFD0707FD | |
| GBL_TAG_ID_DELTA_LZMA = 0xF80C0CF8 | |
| GBL_TAG_ID_METADATA = 0xF60808F6 | |
| GBL_TAG_ID_CERTIFICATE = 0xF30B0BF3 | |
| GBL_TAG_ID_SIGNATURE = 0xF70A0AF7 | |
| GBL_TAG_ID_END = 0xFC0404FC | |
| # Header tag types | |
| GBL_TYPE_NONE = 0x00000000 | |
| GBL_TYPE_ENCRYPTION_AESCCM = 0x00000001 | |
| GBL_TYPE_SIGNATURE_ECDSA = 0x00000100 | |
| # Application types | |
| APPLICATION_TYPE_ZIGBEE = 1 << 0 | |
| APPLICATION_TYPE_THREAD = 1 << 1 | |
| APPLICATION_TYPE_FLEX = 1 << 2 | |
| APPLICATION_TYPE_BLUETOOTH = 1 << 3 | |
| APPLICATION_TYPE_MCU = 1 << 4 | |
| APPLICATION_TYPE_BLUETOOTH_APP = 1 << 5 | |
| APPLICATION_TYPE_BOOTLOADER = 1 << 6 | |
| APPLICATION_TYPE_ZWAVE = 1 << 7 | |
| def _tag(tag_id: int, payload: bytes, payload_len: int | None = None) -> bytes: | |
| length = payload_len if payload_len else len(payload) | |
| return struct.pack("<II", tag_id, length) + payload | |
| def header_tag(version: int = GBL_VERSION, type_flags: int = GBL_TYPE_NONE) -> bytes: | |
| payload = struct.pack("<II", version, type_flags) | |
| return _tag(GBL_TAG_ID_HEADER, payload) | |
| def application_info_tag( | |
| type: int, version: int, capabilities: int, product_id: bytes | |
| ) -> bytes: | |
| if len(product_id) != 16: | |
| raise ValueError("product_id must be exactly 16 bytes") | |
| payload = struct.pack("<III16s", type, version, capabilities, product_id) | |
| return _tag(GBL_TAG_ID_APPLICATION_INFO, payload) | |
| def program_data_tag(flash_start_addr: int, data: ByteString) -> bytes: | |
| payload = struct.pack("<I", flash_start_addr) + bytes(data) | |
| return _tag(GBL_TAG_ID_PROGRAM_DATA, payload) | |
| def end_tag() -> bytes: | |
| return _tag(GBL_TAG_ID_END, b"", payload_len=4) | |
| def product_id_type(s: str) -> bytes: | |
| try: | |
| b = bytes.fromhex(s) | |
| except ValueError: | |
| raise ArgumentTypeError("must be a valid 32-character hex string") | |
| if len(b) != 16: | |
| raise ArgumentTypeError("must be exactly 32 hex characters (16 bytes)") | |
| return b | |
| def create_gbl( | |
| in_path: Path, | |
| out_path: Path, | |
| *, | |
| app_type: int, | |
| app_version: int, | |
| app_capabilities: int, | |
| app_product_id: bytes, | |
| flash_start_addr: int, | |
| ) -> None: | |
| # Read the input program binary | |
| program = in_path.read_bytes() | |
| if len(program) == 0: | |
| raise ValueError("Input file is empty.") | |
| # Create GBL tags | |
| tags = [ | |
| header_tag(), | |
| application_info_tag(app_type, app_version, app_capabilities, app_product_id), | |
| program_data_tag(flash_start_addr, program), | |
| end_tag(), | |
| ] | |
| # Open the output file | |
| with out_path.open("wb") as f: | |
| crc = 0 | |
| # Write the tags | |
| for tag in tags: | |
| f.write(tag) | |
| crc = zlib.crc32(tag, crc) | |
| # Append the crc | |
| f.write(struct.pack("<I", crc & 0xFFFFFFFF)) | |
| def main(): | |
| parser = ArgumentParser(description="Create a GBL file from an input binary.") | |
| parser.add_argument("bin", help="Path to the ELF binary input file") | |
| parser.add_argument("gbl", help="Path to output GBL file") | |
| parser.add_argument( | |
| "--app-version", | |
| type=lambda x: int(x, 0), | |
| required=True, | |
| help="Application version", | |
| ) | |
| parser.add_argument( | |
| "--app-type", | |
| type=lambda x: int(x, 0), | |
| default=APPLICATION_TYPE_MCU, | |
| help="Application type", | |
| ) | |
| parser.add_argument( | |
| "--app-capabilities", | |
| type=lambda x: int(x, 0), | |
| default=0x00000000, | |
| help="Application capabilities", | |
| ) | |
| parser.add_argument( | |
| "--app-product-id", | |
| type=product_id_type, | |
| required=True, | |
| help="Application product ID (32 hex chars)", | |
| ) | |
| parser.add_argument( | |
| "--flash-start-addr", | |
| type=lambda x: int(x, 0), | |
| required=True, | |
| help="Flash start address (hex or int, required)", | |
| ) | |
| parser.add_argument( | |
| "-f", "--force", action="store_true", help="Overwrite output file if it exists" | |
| ) | |
| args = parser.parse_args() | |
| # Input file validation | |
| in_path = Path(args.bin) | |
| if not in_path.is_file(): | |
| print(f"Error: Input file '{in_path}' does not exist.", file=sys.stderr) | |
| sys.exit(1) | |
| # Output file overwrite protection | |
| out_path = Path(args.gbl) | |
| if out_path.exists() and not args.force: | |
| print( | |
| f"Error: Output file '{out_path}' already exists. Use --force to overwrite.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| try: | |
| create_gbl( | |
| in_path, | |
| out_path, | |
| app_type=args.app_type, | |
| app_version=args.app_version, | |
| app_capabilities=args.app_capabilities, | |
| app_product_id=args.app_product_id, | |
| flash_start_addr=args.flash_start_addr, | |
| ) | |
| except Exception as e: | |
| print(f"Error: {e}", file=sys.stderr) | |
| sys.exit(1) | |
| print(f"Successfully wrote output to {out_path}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment