Skip to content

Instantly share code, notes, and snippets.

@loopj
Last active August 20, 2025 05:58
Show Gist options
  • Select an option

  • Save loopj/0f2cf01cb88f8796dd05dc6a04732dc9 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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