From 310a464acff99304511085ee23a7556c7f4152b7 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 31 Dec 2020 16:51:52 -0500 Subject: [PATCH] cmake: Add UF2 as an additional build output option. Add ability to build a UF2 (https://github.com/Microsoft/uf2) image as an additional output type. This leverages the code partition offset for the UF2 base address, and a configurable UF2 family ID. Includes an unmodified (except for headers for licensing, pylit disabling) version of the uf2conv.py script copied the UF2 format specification repository, used to convert the bin to UF2. Origin: UF2 file format specification reference utilies License: MIT URL: https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py commit: 587abb8b909266e9b468d6284f2fbd425235d1b5 Signed-off-by: Pete Johanson --- CMakeLists.txt | 15 ++ CODEOWNERS | 1 + Kconfig.zephyr | 18 ++ cmake/app/boilerplate.cmake | 1 + scripts/uf2conv.py | 323 ++++++++++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100755 scripts/uf2conv.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 325618ee62d..6bca34054ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1200,6 +1200,21 @@ if(CONFIG_BUILD_OUTPUT_BIN) endif() endif() +if(CONFIG_BUILD_OUTPUT_BIN AND CONFIG_BUILD_OUTPUT_UF2) + list(APPEND + post_build_commands + COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/uf2conv.py + -c + -f ${CONFIG_BUILD_OUTPUT_UF2_FAMILY_ID} + -b ${CONFIG_FLASH_LOAD_OFFSET} + -o ${KERNEL_UF2_NAME} + ${KERNEL_BIN_NAME} + ) + list(APPEND + post_build_byproducts + ${KERNEL_UF2_NAME} + ) +endif() # Cleanup intermediate files if(CONFIG_CLEANUP_INTERMEDIATE_FILES) diff --git a/CODEOWNERS b/CODEOWNERS index 52728223921..2e44bc30ba2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -528,6 +528,7 @@ /scripts/west_commands/ @mbolivar-nordic /scripts/west-commands.yml @mbolivar-nordic /scripts/zephyr_module.py @tejlmand +/scripts/uf2conv.py @petejohanson /scripts/user_wordsize.py @cfriedt /scripts/valgrind.supp @aescolar @daor-oti /share/zephyr-package/ @tejlmand diff --git a/Kconfig.zephyr b/Kconfig.zephyr index 1fc726d83b0..1648f1f1929 100644 --- a/Kconfig.zephyr +++ b/Kconfig.zephyr @@ -377,6 +377,24 @@ config BUILD_OUTPUT_S19 Build an S19 binary zephyr/zephyr.s19 in the build directory. The name of this file can be customized with CONFIG_KERNEL_BIN_NAME. +config BUILD_OUTPUT_UF2 + bool "Build a binary in UF2 format" + depends on BUILD_OUTPUT_BIN + help + Build a UF2 binary zephyr/zephyr.uf2 in the build directory. + The name of this file can be customized with CONFIG_KERNEL_BIN_NAME. + +if BUILD_OUTPUT_UF2 + +config BUILD_OUTPUT_UF2_FAMILY_ID + string "UF2 Device Family" + help + UF2 bootloaders only accept UF2 files with a matching family ID. + This can be either a hex, e.g. 0x68ed2b88, or well-known family + name string. + +endif # BUILD_OUTPUT_UF2 + config BUILD_OUTPUT_STRIPPED bool "Build a stripped binary" help diff --git a/cmake/app/boilerplate.cmake b/cmake/app/boilerplate.cmake index 1f03b0f8f0c..3432157d7fc 100644 --- a/cmake/app/boilerplate.cmake +++ b/cmake/app/boilerplate.cmake @@ -585,6 +585,7 @@ set(KERNEL_NAME ${CONFIG_KERNEL_BIN_NAME}) set(KERNEL_ELF_NAME ${KERNEL_NAME}.elf) set(KERNEL_BIN_NAME ${KERNEL_NAME}.bin) set(KERNEL_HEX_NAME ${KERNEL_NAME}.hex) +set(KERNEL_UF2_NAME ${KERNEL_NAME}.uf2) set(KERNEL_MAP_NAME ${KERNEL_NAME}.map) set(KERNEL_LST_NAME ${KERNEL_NAME}.lst) set(KERNEL_S19_NAME ${KERNEL_NAME}.s19) diff --git a/scripts/uf2conv.py b/scripts/uf2conv.py new file mode 100755 index 00000000000..e92c2ec3ab8 --- /dev/null +++ b/scripts/uf2conv.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation +# SPDX-License-Identifier: MIT +# Copied from 7a9e1f4 of https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py +# pylint: skip-file +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +families = { + 'SAMD21': 0x68ed2b88, + 'SAML21': 0x1851780a, + 'SAMD51': 0x55114460, + 'NRF52': 0x1b57745f, + 'STM32F0': 0x647824b6, + 'STM32F1': 0x5ee21072, + 'STM32F2': 0x5d1a0a2e, + 'STM32F3': 0x6b846188, + 'STM32F4': 0x57755a57, + 'STM32F7': 0x53b80f00, + 'STM32G0': 0x300f5633, + 'STM32G4': 0x4c71240a, + 'STM32H7': 0x6db66082, + 'STM32L0': 0x202e3a91, + 'STM32L1': 0x1e1f432d, + 'STM32L4': 0x00ff6919, + 'STM32L5': 0x04240bdf, + 'STM32WB': 0x70d16653, + 'STM32WL': 0x21460ff0, + 'ATMEGA32': 0x16573617, + 'MIMXRT10XX': 0x4FB2D5BD, + 'LPC55': 0x2abc77ec, + 'GD32F350': 0x31D228C6, + 'ESP32S2': 0xbfdd4eee, + 'RP2040': 0xe48bff56 +} + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + ptr + newaddr = hd[3] + if curraddr == None: + appstartaddr = newaddr + curraddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + ptr + if padding > 10*1024*1024: + assert False, "More than 10M of padding needed at " + ptr + if padding % 4 != 0: + assert False, "Non-word padding size at " + ptr + while padding > 0: + padding -= 4 + outp += b"\x00\x00\x00\x00" + outp.append(block[32 : 32 + datalen]) + curraddr = newaddr + datalen + return b"".join(outp) + +def convert_to_carray(file_content): + outp = "const unsigned long bindata_len = %d;\n" % len(file_content) + outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr:ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": + drives.append(words[0]) + else: + rootpath = "/media" + if sys.platform == "darwin": + rootpath = "/Volumes" + elif sys.platform == "linux": + tmp = rootpath + "/" + os.environ["USER"] + if os.path.isdir(tmp): + rootpath = tmp + for d in os.listdir(rootpath): + drives.append(os.path.join(rootpath, d)) + + + def has_info(d): + try: + return os.path.isfile(d + INFO_FILE) + except: + return False + + return list(filter(has_info, drives)) + + +def board_id(path): + with open(path + INFO_FILE, mode='r') as file: + file_content = file.read() + return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) + + +def list_drives(): + for d in get_drives(): + print(d, board_id(d)) + + +def write_file(name, buf): + with open(name, "wb") as f: + f.write(buf) + print("Wrote %d bytes to %s" % (len(buf), name)) + + +def main(): + global appstartaddr, familyid + def error(msg): + print(msg) + sys.exit(1) + parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') + parser.add_argument('input', metavar='INPUT', type=str, nargs='?', + help='input file (HEX, BIN or UF2)') + parser.add_argument('-b' , '--base', dest='base', type=str, + default="0x2000", + help='set base address of application for BIN format (default: 0x2000)') + parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') + parser.add_argument('-d' , '--device', dest="device_path", + help='select a device path to flash') + parser.add_argument('-l' , '--list', action='store_true', + help='list connected devices') + parser.add_argument('-c' , '--convert', action='store_true', + help='do not flash, just convert') + parser.add_argument('-D' , '--deploy', action='store_true', + help='just flash, do not convert') + parser.add_argument('-f' , '--family', dest='family', type=str, + default="0x0", + help='specify familyID - number or name (default: 0x0)') + parser.add_argument('-C' , '--carray', action='store_true', + help='convert binary file to a C array, not UF2') + args = parser.parse_args() + appstartaddr = int(args.base, 0) + + if args.family.upper() in families: + familyid = families[args.family.upper()] + else: + try: + familyid = int(args.family, 0) + except ValueError: + error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + + if args.list: + list_drives() + else: + if not args.input: + error("Need input file") + with open(args.input, mode='rb') as f: + inpbuf = f.read() + from_uf2 = is_uf2(inpbuf) + ext = "uf2" + if args.deploy: + outbuf = inpbuf + elif from_uf2: + outbuf = convert_from_uf2(inpbuf) + ext = "bin" + elif is_hex(inpbuf): + outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) + elif args.carray: + outbuf = convert_to_carray(inpbuf) + ext = "h" + else: + outbuf = convert_to_uf2(inpbuf) + print("Converting to %s, output size: %d, start address: 0x%x" % + (ext, len(outbuf), appstartaddr)) + if args.convert or ext != "uf2": + drives = [] + if args.output == None: + args.output = "flash." + ext + else: + drives = get_drives() + + if args.output: + write_file(args.output, outbuf) + else: + if len(drives) == 0: + error("No drive to deploy.") + for d in drives: + print("Flashing %s (%s)" % (d, board_id(d))) + write_file(d + "/NEW.UF2", outbuf) + + +if __name__ == "__main__": + main()