take multiple binary arguments to convert_binary_to_uf2

this is in preparation for making smaller .uf2 files buy not needing
padding, instead passing multiple location+binary combos to the
converter

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2024-04-14 22:54:05 -05:00
parent ea4d4be709
commit 2bb049c442
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
3 changed files with 37 additions and 33 deletions

View File

@ -117,9 +117,10 @@ def concatenate_firmware_and_storage_files(firmware_filename: str,
# the correct way to do the above would be to pass a list of {offset,binary_data} to convert..., # the correct way to do the above would be to pass a list of {offset,binary_data} to convert...,
# and have it calculate the total block size before starting to write, and then iterating over # and have it calculate the total block size before starting to write, and then iterating over
# the three lists. doable, just not on the top of my mind right now # the three lists. doable, just not on the top of my mind right now
new_binary = storage.convert_binary_to_uf2(combine_firmware_and_config(firmware_binary, board_config_binary, new_binary = storage.convert_binary_to_uf2([
user_config_binary, (0, combine_firmware_and_config(firmware_binary, board_config_binary, user_config_binary,
replace_extra=replace_extra)) replace_extra=replace_extra)),
])
if combined_filename: if combined_filename:
with open(combined_filename, 'wb') as combined: with open(combined_filename, 'wb') as combined:
@ -256,8 +257,9 @@ def write_new_config_to_filename(config: Message, filename: str, inject: bool =
binary = storage.serialize_config_with_footer(config) binary = storage.serialize_config_with_footer(config)
with open(filename, 'wb') as file: with open(filename, 'wb') as file:
if filename[-4:] == '.uf2': if filename[-4:] == '.uf2':
file.write(storage.convert_binary_to_uf2(storage.pad_config_to_storage_size(binary), file.write(storage.convert_binary_to_uf2([
start=storage.USER_CONFIG_BINARY_LOCATION)) (storage.USER_CONFIG_BINARY_LOCATION, storage.pad_config_to_storage_size(binary)),
]))
else: else:
file.write(binary) file.write(binary)

View File

@ -53,36 +53,37 @@ class ConfigMagicError(ConfigReadError):
"""Exception raised when the config section does not have the magic value in its footer.""" """Exception raised when the config section does not have the magic value in its footer."""
def convert_binary_to_uf2(binary: bytearray, start: int = 0) -> bytearray: def convert_binary_to_uf2(binaries: list[tuple[int, bytearray]]) -> bytearray:
"""Convert a GP2040-CE binary payload to Microsoft's UF2 format. """Convert a GP2040-CE binary payload to Microsoft's UF2 format.
https://github.com/microsoft/uf2/tree/master#overview https://github.com/microsoft/uf2/tree/master#overview
Args: Args:
binary: bytearray content to convert to a UF2 payload binaries: list of start,binary pairs of binary data to write at the specified memory offset in flash
start: position offset to start at rather than flash start (for creating e.g. user config UF2s)
Returns: Returns:
the content in UF2 format the content in UF2 format
""" """
size = len(binary) total_blocks = sum([(len(binary) // 256) + 1 if len(binary) % 256 else len(binary) // 256
blocks = (len(binary) // 256) + 1 if len(binary) % 256 else len(binary) // 256 for offset, binary in binaries])
uf2 = bytearray()
index = 0 uf2 = bytearray()
while index < size: for start, binary in binaries:
pad_count = 476 - len(binary[index:index+256]) size = len(binary)
uf2 += struct.pack('<LLLLLLLL', index = 0
UF2_MAGIC_FIRST, # first magic number while index < size:
UF2_MAGIC_SECOND, # second magic number pad_count = 476 - len(binary[index:index+256])
0x00002000, # familyID present uf2 += struct.pack('<LLLLLLLL',
0x10000000 + start + index, # address to write to UF2_MAGIC_FIRST, # first magic number
256, # bytes to write in this block UF2_MAGIC_SECOND, # second magic number
index // 256, # sequential block number 0x00002000, # familyID present
blocks, # total number of blocks 0x10000000 + start + index, # address to write to
UF2_FAMILY_ID) # family ID 256, # bytes to write in this block
uf2 += binary[index:index+256] + bytearray(b'\x00' * pad_count) # content index // 256, # sequential block number
uf2 += struct.pack('<L', UF2_MAGIC_FINAL) # final magic number total_blocks, # total number of blocks
index += 256 UF2_FAMILY_ID) # family ID
uf2 += binary[index:index+256] + bytearray(b'\x00' * pad_count) # content
uf2 += struct.pack('<L', UF2_MAGIC_FINAL) # final magic number
index += 256
return uf2 return uf2
@ -393,8 +394,9 @@ def dump_config():
with open(args.filename, 'wb') as out_file: with open(args.filename, 'wb') as out_file:
if args.filename[-4:] == '.uf2': if args.filename[-4:] == '.uf2':
# we must pad to storage start in order for the UF2 write addresses to make sense # we must pad to storage start in order for the UF2 write addresses to make sense
out_file.write(convert_binary_to_uf2(pad_config_to_storage_size(binary_config), out_file.write(convert_binary_to_uf2([
start=USER_CONFIG_BINARY_LOCATION)) (USER_CONFIG_BINARY_LOCATION, pad_config_to_storage_size(binary_config)),
]))
else: else:
out_file.write(binary_config) out_file.write(binary_config)

View File

@ -133,7 +133,7 @@ def test_config_from_whole_board_parses(whole_board_dump):
def test_convert_binary_to_uf2(whole_board_with_board_config_dump): def test_convert_binary_to_uf2(whole_board_with_board_config_dump):
"""Do some sanity checks in the attempt to convert a binary to a UF2.""" """Do some sanity checks in the attempt to convert a binary to a UF2."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump) uf2 = storage.convert_binary_to_uf2([{0, whole_board_with_board_config_dump}])
assert len(uf2) == 4194304 # binary is 8192 256 byte chunks, UF2 is 512 b per chunk assert len(uf2) == 4194304 # binary is 8192 256 byte chunks, UF2 is 512 b per chunk
assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic
assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
@ -142,7 +142,7 @@ def test_convert_binary_to_uf2(whole_board_with_board_config_dump):
def test_convert_unaligned_binary_to_uf2(firmware_binary): def test_convert_unaligned_binary_to_uf2(firmware_binary):
"""Do some sanity checks in the attempt to convert a binary to a UF2.""" """Do some sanity checks in the attempt to convert a binary to a UF2."""
uf2 = storage.convert_binary_to_uf2(firmware_binary) uf2 = storage.convert_binary_to_uf2([{0, firmware_binary}])
assert len(uf2) == math.ceil(len(firmware_binary)/256) * 512 # 256 byte complete/partial chunks -> 512 b chunks assert len(uf2) == math.ceil(len(firmware_binary)/256) * 512 # 256 byte complete/partial chunks -> 512 b chunks
assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic
assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
@ -151,7 +151,7 @@ def test_convert_unaligned_binary_to_uf2(firmware_binary):
def test_convert_binary_to_uf2_with_offsets(whole_board_with_board_config_dump): def test_convert_binary_to_uf2_with_offsets(whole_board_with_board_config_dump):
"""Do some sanity checks in the attempt to convert a binary to a UF2.""" """Do some sanity checks in the attempt to convert a binary to a UF2."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump, start=storage.USER_CONFIG_BINARY_LOCATION) uf2 = storage.convert_binary_to_uf2([{storage.USER_CONFIG_BINARY_LOCATION, whole_board_with_board_config_dump}])
assert len(uf2) == 4194304 # binary is 8192 256 byte chunks, UF2 is 512 b per chunk assert len(uf2) == 4194304 # binary is 8192 256 byte chunks, UF2 is 512 b per chunk
assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic assert uf2[0:4] == b'\x55\x46\x32\x0a' == b'UF2\n' # proper magic
assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set assert uf2[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
@ -160,7 +160,7 @@ def test_convert_binary_to_uf2_with_offsets(whole_board_with_board_config_dump):
def test_convert_binary_to_uf2_to_binary(whole_board_with_board_config_dump): def test_convert_binary_to_uf2_to_binary(whole_board_with_board_config_dump):
"""Do some sanity checks in the attempt to convert a binary to a UF2.""" """Do some sanity checks in the attempt to convert a binary to a UF2."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump) uf2 = storage.convert_binary_to_uf2([{0, whole_board_with_board_config_dump}])
binary = storage.convert_uf2_to_binary(uf2) binary = storage.convert_uf2_to_binary(uf2)
assert len(binary) == 2097152 assert len(binary) == 2097152
assert whole_board_with_board_config_dump == binary assert whole_board_with_board_config_dump == binary
@ -168,7 +168,7 @@ def test_convert_binary_to_uf2_to_binary(whole_board_with_board_config_dump):
def test_malformed_uf2(whole_board_with_board_config_dump): def test_malformed_uf2(whole_board_with_board_config_dump):
"""Check that we expect a properly-formed UF2.""" """Check that we expect a properly-formed UF2."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump) uf2 = storage.convert_binary_to_uf2([{0, whole_board_with_board_config_dump}])
# truncated UF2 --- byte mismatch # truncated UF2 --- byte mismatch
with pytest.raises(ValueError): with pytest.raises(ValueError):