Compare commits

...

2 Commits

Author SHA1 Message Date
Brian S. Stephan 449812f1df
optionally dump config to a .uf2 file
this allows for creating a UF2 that writes the user config to the whole
user config section of flash, allowing for easy copying/juggling of
configurations by just maintaining a library of .uf2 files you like to
apply

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-03-25 10:03:54 -05:00
Brian S. Stephan a3f9f12e74
move UF2 converter to storage, allow specifying start address
the former is to avoid an upcoming circular dependency, the latter is to
allow for creating an e.g. user-config-only .uf2 by specifying the
proper offset to start the UF2 addressing at.

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-03-25 09:19:58 -05:00
4 changed files with 73 additions and 57 deletions

View File

@ -6,7 +6,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
import argparse
import copy
import logging
import struct
from typing import Optional
from google.protobuf.message import Message
@ -14,19 +13,14 @@ from google.protobuf.message import Message
from gp2040ce_bintools import core_parser
from gp2040ce_bintools.rp2040 import get_bootsel_endpoints, read, write
from gp2040ce_bintools.storage import (BOARD_CONFIG_BINARY_LOCATION, STORAGE_SIZE, USER_CONFIG_BINARY_LOCATION,
USER_CONFIG_BOOTSEL_ADDRESS, get_config_from_json, pad_config_to_storage_size,
serialize_config_with_footer)
USER_CONFIG_BOOTSEL_ADDRESS, convert_binary_to_uf2, get_config_from_json,
pad_config_to_storage_size, serialize_config_with_footer)
logger = logging.getLogger(__name__)
GP2040CE_START_ADDRESS = 0x10000000
GP2040CE_SIZE = 2 * 1024 * 1024
UF2_FAMILY_ID = 0xE48BFF56
UF2_MAGIC_FIRST = 0x0A324655
UF2_MAGIC_SECOND = 0x9E5D5157
UF2_MAGIC_FINAL = 0x0AB16F30
#################
# LIBRARY ITEMS #
@ -115,38 +109,6 @@ def concatenate_firmware_and_storage_files(firmware_filename: str,
write(endpoint_out, endpoint_in, GP2040CE_START_ADDRESS, bytes(new_binary))
def convert_binary_to_uf2(binary: bytearray) -> bytearray:
"""Convert a GP2040-CE binary payload to Microsoft's UF2 format.
https://github.com/microsoft/uf2/tree/master#overview
Args:
binary: bytearray content to convert to a UF2 payload
Returns:
the content in UF2 format
"""
size = len(binary)
blocks = (len(binary) // 256) + 1 if len(binary) % 256 else len(binary) // 256
uf2 = bytearray()
index = 0
while index < size:
pad_count = 476 - len(binary[index:index+256])
uf2 += struct.pack('<LLLLLLLL',
UF2_MAGIC_FIRST, # first magic number
UF2_MAGIC_SECOND, # second magic number
0x00002000, # familyID present
0x10000000 + index, # address to write to
256, # bytes to write in this block
index // 256, # sequential block number
blocks, # total number of blocks
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
def get_gp2040ce_from_usb() -> tuple[bytes, object, object]:
"""Read the firmware + config sections from a USB device.

View File

@ -6,6 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
import argparse
import binascii
import logging
import struct
from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import Parse as JsonParse
@ -25,6 +26,11 @@ USER_CONFIG_BOOTSEL_ADDRESS = 0x10000000 + USER_CONFIG_BINARY_LOCATION
FOOTER_SIZE = 12
FOOTER_MAGIC = b'\x65\xe3\xf1\xd2'
UF2_FAMILY_ID = 0xE48BFF56
UF2_MAGIC_FIRST = 0x0A324655
UF2_MAGIC_SECOND = 0x9E5D5157
UF2_MAGIC_FINAL = 0x0AB16F30
#################
# LIBRARY ITEMS #
@ -47,6 +53,39 @@ class ConfigMagicError(ConfigReadError):
"""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:
"""Convert a GP2040-CE binary payload to Microsoft's UF2 format.
https://github.com/microsoft/uf2/tree/master#overview
Args:
binary: bytearray content to convert to a UF2 payload
start: position offset to start at rather than flash start (for creating e.g. user config UF2s)
Returns:
the content in UF2 format
"""
size = len(binary)
blocks = (len(binary) // 256) + 1 if len(binary) % 256 else len(binary) // 256
uf2 = bytearray()
index = 0
while index < size:
pad_count = 476 - len(binary[index:index+256])
uf2 += struct.pack('<LLLLLLLL',
UF2_MAGIC_FIRST, # first magic number
UF2_MAGIC_SECOND, # second magic number
0x00002000, # familyID present
0x10000000 + start + index, # address to write to
256, # bytes to write in this block
index // 256, # sequential block number
blocks, # total number of blocks
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
def get_config(content: bytes) -> Message:
"""Read the config from a GP2040-CE storage section.
@ -280,16 +319,23 @@ def serialize_config_with_footer(config: Message) -> bytearray:
def dump_config():
"""Save the GP2040-CE's configuration to a binary file."""
"""Save the GP2040-CE's user configuration to a binary or UF2 file."""
parser = argparse.ArgumentParser(
description="Read the configuration section from a USB device and save it to a binary file.",
parents=[core_parser],
)
parser.add_argument('binary_filename', help=".bin file to save the GP2040-CE board's config section to")
parser.add_argument('filename', help="file to save the GP2040-CE board's config section to --- if the "
"suffix is .uf2, it is saved in UF2 format, else it is a raw binary")
args, _ = parser.parse_known_args()
config, _, _ = get_user_config_from_usb()
with open(args.binary_filename, 'wb') as out_file:
out_file.write(serialize_config_with_footer(config))
binary_config = serialize_config_with_footer(config)
with open(args.filename, 'wb') as out_file:
if args.filename[-4:] == '.uf2':
# 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),
start=USER_CONFIG_BINARY_LOCATION))
else:
out_file.write(binary_config)
def visualize():

View File

@ -12,10 +12,9 @@ from decorator import decorator
from gp2040ce_bintools import get_config_pb2
from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and_config,
concatenate_firmware_and_storage_files, convert_binary_to_uf2,
get_gp2040ce_from_usb, pad_binary_up_to_board_config,
pad_binary_up_to_user_config, replace_config_in_binary,
write_new_config_to_filename, write_new_config_to_usb)
concatenate_firmware_and_storage_files, get_gp2040ce_from_usb,
pad_binary_up_to_board_config, pad_binary_up_to_user_config,
replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb)
from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer,
get_user_storage_section, serialize_config_with_footer)
@ -76,15 +75,6 @@ def test_concatenate_both_configs_to_file(tmp_path):
assert footer_size == 3309
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."""
uf2 = convert_binary_to_uf2(whole_board_with_board_config_dump)
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[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
assert uf2[524:528] == bytearray(b'\x00\x01\x00\x10') # address to write the second chunk
@with_pb2s
def test_concatenate_user_json_to_file(tmp_path):
"""Test that we write a file with firmware + JSON user config as expected."""

View File

@ -130,6 +130,24 @@ def test_config_from_whole_board_parses(whole_board_dump):
assert config.hotkeyOptions.hotkey02.dpadMask == 1
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."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump)
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[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
assert uf2[524:528] == bytearray(b'\x00\x01\x00\x10') # address to write the second chunk
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."""
uf2 = storage.convert_binary_to_uf2(whole_board_with_board_config_dump, start=storage.USER_CONFIG_BINARY_LOCATION)
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[8:12] == bytearray(b'\x00\x20\x00\x00') # family ID set
assert uf2[524:528] == bytearray(b'\x00\xc1\x1f\x10') # address to write the second chunk
@with_pb2s
def test_serialize_config_with_footer(storage_dump, config_binary):
"""Test that reserializing a read in config matches the original.