concatenate flags to combine board and user configs

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2024-01-06 13:42:10 -06:00
parent ef71db6f5e
commit 0d54d3b805
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
3 changed files with 78 additions and 19 deletions

View File

@ -55,7 +55,10 @@ A quick demonstration of the editor is available [on asciinema.org](https://asci
`concatenate` combines a GP2040-CE firmware .bin file (such as from a fresh build) with:
* a GP2040-CE user config, in the form of
* a GP2040-CE board config, in the form of
* a config section .bin (with footer) (optionally padded) (`--binary-board-config-filename`) or
* a JSON file representing the config (`--json-board-config-filename`)
* and/or a GP2040-CE user config, in the form of
* a config section .bin (with footer) (optionally padded) (`--binary-user-config-filename`) or
* a JSON file representing the config (`--json-user-config-filename`)
@ -64,6 +67,10 @@ flashed with a particular configuration, for instances such as producing a binar
configuration (specific customizations, etc.), or keeping documented backups of what you're testing with during
development.
The `--...-board-config-filename` flags allow for shipping a default configuration as part of the binary, replacing
the need for generating these board configurations at compile time. This allows for more custom builds and less
dependency on the build jobs, and is a feature in progress in the core firmware.
The produced binary can be written to a file with `--new-binary-filename FILENAME` or straight to a RP2040 in BOOTSEL
mode with `--usb`.

View File

@ -56,7 +56,10 @@ def combine_firmware_and_config(firmware_binary: bytearray, board_config_binary:
return combined
def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_config_filename: Optional[str] = None,
def concatenate_firmware_and_storage_files(firmware_filename: str,
binary_board_config_filename: Optional[str] = None,
json_board_config_filename: Optional[str] = None,
binary_user_config_filename: Optional[str] = None,
json_user_config_filename: Optional[str] = None,
combined_filename: str = '', usb: bool = False,
replace_extra: bool = False) -> None:
@ -64,25 +67,36 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_c
Args:
firmware_filename: filename of the firmware binary to read
binary_board_config_filename: filename of the board config section to read, in binary format
json_board_config_filename: filename of the board config section to read, in JSON format
binary_user_config_filename: filename of the user config section to read, in binary format
json_user_config_filename: filename of the user config section to read, in JSON format
combined_filename: filename of where to write the combine binary
replace_extra: if larger than normal firmware files should have their overage replaced
"""
new_binary = None
if binary_user_config_filename:
with open(firmware_filename, 'rb') as firmware, open(binary_user_config_filename, 'rb') as storage:
new_binary = combine_firmware_and_config(bytearray(firmware.read()), None, bytearray(storage.read()),
replace_extra=replace_extra)
elif json_user_config_filename:
with open(firmware_filename, 'rb') as firmware, open(json_user_config_filename, 'r') as json_file:
config = get_config_from_json(json_file.read())
serialized_config = serialize_config_with_footer(config)
new_binary = combine_firmware_and_config(bytearray(firmware.read()), None, serialized_config,
replace_extra=replace_extra)
board_config_binary = None
user_config_binary = None
if not new_binary:
raise ValueError("no means to create a binary was provided")
if binary_board_config_filename:
with open(binary_board_config_filename, 'rb') as storage:
board_config_binary = storage.read()
elif json_board_config_filename:
with open(json_board_config_filename, 'r') as json_file:
config = get_config_from_json(json_file.read())
board_config_binary = serialize_config_with_footer(config)
if binary_user_config_filename:
with open(binary_user_config_filename, 'rb') as storage:
user_config_binary = storage.read()
elif json_user_config_filename:
with open(json_user_config_filename, 'r') as json_file:
config = get_config_from_json(json_file.read())
user_config_binary = serialize_config_with_footer(config)
with open(firmware_filename, 'rb') as firmware:
new_binary = combine_firmware_and_config(bytearray(firmware.read()), board_config_binary, user_config_binary,
replace_extra=replace_extra)
if combined_filename:
with open(combined_filename, 'wb') as combined:
@ -234,15 +248,19 @@ def write_new_config_to_usb(config: Message, endpoint_out: object, endpoint_in:
def concatenate():
"""Combine a built firmware .bin and a storage .bin."""
parser = argparse.ArgumentParser(
description="Combine a compiled GP2040-CE firmware-only .bin and an existing storage area or config .bin "
"into one file suitable for flashing onto a board.",
description="Combine a compiled GP2040-CE firmware-only .bin and existing user and/or board storage area(s) "
"or config .bin(s) into one file suitable for flashing onto a board.",
parents=[core_parser],
)
parser.add_argument('--replace-extra', action='store_true',
help="if the firmware file is larger than the location of storage, perhaps because it's "
"actually a full board dump, overwrite its config section with the config binary")
parser.add_argument('firmware_filename', help=".bin file of a GP2040-CE firmware, probably from a build")
user_config_group = parser.add_mutually_exclusive_group(required=True)
board_config_group = parser.add_mutually_exclusive_group(required=False)
board_config_group.add_argument('--binary-board-config-filename',
help=".bin file of a GP2040-CE board config w/footer")
board_config_group.add_argument('--json-board-config-filename', help=".json file of a GP2040-CE board config")
user_config_group = parser.add_mutually_exclusive_group(required=False)
user_config_group.add_argument('--binary-user-config-filename',
help=".bin file of a GP2040-CE user config w/footer")
user_config_group.add_argument('--json-user-config-filename', help=".json file of a GP2040-CE user config")
@ -252,6 +270,8 @@ def concatenate():
args, _ = parser.parse_known_args()
concatenate_firmware_and_storage_files(args.firmware_filename,
binary_board_config_filename=args.binary_board_config_filename,
json_board_config_filename=args.json_board_config_filename,
binary_user_config_filename=args.binary_user_config_filename,
json_user_config_filename=args.json_user_config_filename,
combined_filename=args.new_binary_filename, usb=args.usb,

View File

@ -38,12 +38,43 @@ def test_concatenate_to_file(tmp_path):
tmp_file = os.path.join(tmp_path, 'concat.bin')
firmware_file = os.path.join(HERE, 'test-files', 'test-firmware.bin')
config_file = os.path.join(HERE, 'test-files', 'test-config.bin')
concatenate_firmware_and_storage_files(firmware_file, config_file, combined_filename=tmp_file)
concatenate_firmware_and_storage_files(firmware_file, binary_user_config_filename=config_file,
combined_filename=tmp_file)
with open(tmp_file, 'rb') as file:
content = file.read()
assert len(content) == 2 * 1024 * 1024
def test_concatenate_board_config_to_file(tmp_path):
"""Test that we write a file with firmware + binary board config as expected."""
tmp_file = os.path.join(tmp_path, 'concat.bin')
firmware_file = os.path.join(HERE, 'test-files', 'test-firmware.bin')
config_file = os.path.join(HERE, 'test-files', 'test-config.bin')
concatenate_firmware_and_storage_files(firmware_file, binary_board_config_filename=config_file,
combined_filename=tmp_file)
with open(tmp_file, 'rb') as file:
content = file.read()
assert len(content) == (2 * 1024 * 1024) - (16 * 1024)
def test_concatenate_both_configs_to_file(tmp_path):
"""Test that we write a file with firmware + binary board + binary user config as expected."""
tmp_file = os.path.join(tmp_path, 'concat.bin')
firmware_file = os.path.join(HERE, 'test-files', 'test-firmware.bin')
config_file = os.path.join(HERE, 'test-files', 'test-config.bin')
concatenate_firmware_and_storage_files(firmware_file, binary_board_config_filename=config_file,
binary_user_config_filename=config_file, combined_filename=tmp_file)
with open(tmp_file, 'rb') as file:
content = file.read()
assert len(content) == 2 * 1024 * 1024
storage = get_board_storage_section(content)
footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309
storage = get_user_storage_section(content)
footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309
@with_pb2s
def test_concatenate_user_json_to_file(tmp_path):
"""Test that we write a file with firmware + JSON user config as expected."""
@ -72,7 +103,8 @@ def test_concatenate_to_usb(tmp_path):
end_out, end_in = mock.MagicMock(), mock.MagicMock()
with mock.patch('gp2040ce_bintools.builder.get_bootsel_endpoints', return_value=(end_out, end_in)):
with mock.patch('gp2040ce_bintools.builder.write') as mock_write:
concatenate_firmware_and_storage_files(firmware_file, config_file, usb=True)
concatenate_firmware_and_storage_files(firmware_file, binary_user_config_filename=config_file,
usb=True)
assert mock_write.call_args.args[2] == 0x10000000
assert len(mock_write.call_args.args[3]) == 2 * 1024 * 1024