From b7bb437ae8d43a43260a8365ee8441f1b71b30f7 Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Tue, 11 Jul 2023 16:25:45 -0500 Subject: [PATCH] command to dump whole GP2040-CE image from USB --- README.md | 11 +++++++++++ gp2040ce_bintools/builder.py | 36 +++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/test_builder.py | 21 +++++++++++++++++++-- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c02a201..417d3e4 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,17 @@ Sample usage: % dump-config -P ~/proj/GP2040-CE/proto -P ~/proj/GP2040-CE/lib/nanopb/generator/proto --filename `date +%Y%m%d`-config-backup.bin ``` +### dump-gp2040ce + +`dump-gp2040ce` replaces the need for picotool in order to make a copy of a board's full GP2040-CE image as a binary file. +This could be used with the other tools, or just to keep a backup. + +Sample usage: + +``` +% dump-gp2040ce `date +%Y%m%d`-backup.bin +``` + ### visualize-storage `visualize-storage` reads a GP2040-CE board's configuration, either over USB or from a dump of the board's flash diff --git a/gp2040ce_bintools/builder.py b/gp2040ce_bintools/builder.py index 3c51d5c..392f35b 100644 --- a/gp2040ce_bintools/builder.py +++ b/gp2040ce_bintools/builder.py @@ -6,12 +6,15 @@ import logging from google.protobuf.message import Message from gp2040ce_bintools import core_parser -from gp2040ce_bintools.pico import write +from gp2040ce_bintools.pico import get_bootsel_endpoints, read, write from gp2040ce_bintools.storage import (STORAGE_BINARY_LOCATION, STORAGE_MEMORY_ADDRESS, STORAGE_SIZE, pad_config_to_storage_size, serialize_config_with_footer) logger = logging.getLogger(__name__) +GP2040CE_START_ADDRESS = 0x10000000 +GP2040CE_SIZE = 2 * 1024 * 1024 + ################# # LIBRARY ITEMS # @@ -48,6 +51,20 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, storage_filen combined.write(new_binary) +def get_gp2040ce_from_usb() -> tuple[bytes, object, object]: + """Read the firmware + config sections from a USB device. + + Returns: + the bytes from the board, along with the USB out and in endpoints for reference + """ + # open the USB device and get the config + endpoint_out, endpoint_in = get_bootsel_endpoints() + logger.debug("reading DEVICE ID %s:%s, bus %s, address %s", hex(endpoint_out.device.idVendor), + hex(endpoint_out.device.idProduct), endpoint_out.device.bus, endpoint_out.device.address) + content = read(endpoint_out, endpoint_in, GP2040CE_START_ADDRESS, GP2040CE_SIZE) + return content, endpoint_out, endpoint_in + + def pad_firmware_up_to_storage(firmware: bytes) -> bytearray: """Provide a copy of the firmware padded with zero bytes up to the provided position. @@ -125,8 +142,11 @@ def write_new_config_to_usb(config: Message, endpoint_out: object, endpoint_in: serialized = serialize_config_with_footer(config) # we don't write the whole area, just the minimum from the end of the storage section # nevertheless, the USB device needs writes to start at 256 byte boundaries + logger.debug("serialized: %s", serialized) padding = 256 - (len(serialized) % 256) + logger.debug("length: %s with %s bytes of padding", len(serialized), padding) binary = bytearray(b'\x00' * padding) + serialized + logger.debug("binary for writing: %s", binary) write(endpoint_out, endpoint_in, STORAGE_MEMORY_ADDRESS + (STORAGE_SIZE - len(binary)), binary) @@ -148,3 +168,17 @@ def concatenate(): args, _ = parser.parse_known_args() concatenate_firmware_and_storage_files(args.firmware_filename, args.config_filename, args.new_binary_filename) + + +def dump_gp2040ce(): + """Copy the whole GP2040-CE section off of a BOOTSEL mode board.""" + parser = argparse.ArgumentParser( + description="Read the GP2040-CE firmware + storage section off of a connected USB RP2040 in BOOTSEL mode.", + parents=[core_parser], + ) + parser.add_argument('binary_filename', help="output .bin file of the resulting firmware + storage") + + args, _ = parser.parse_known_args() + content, _, _ = get_gp2040ce_from_usb() + with open(args.binary_filename, 'wb') as out_file: + out_file.write(content) diff --git a/pyproject.toml b/pyproject.toml index 605cbbe..8dfb0c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dev = ["bandit", "decorator", "flake8", "flake8-blind-except", "flake8-builtins" [project.scripts] concatenate = "gp2040ce_bintools.builder:concatenate" dump-config = "gp2040ce_bintools.storage:dump_config" +dump-gp2040ce = "gp2040ce_bintools.builder:dump_gp2040ce" edit-config = "gp2040ce_bintools.gui:edit_config" visualize-storage = "gp2040ce_bintools.storage:visualize" diff --git a/tests/test_builder.py b/tests/test_builder.py index b5e0fc4..d8d85df 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -7,8 +7,9 @@ import pytest from decorator import decorator from gp2040ce_bintools import get_config_pb2 -from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and_config, pad_firmware_up_to_storage, - replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb) +from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and_config, get_gp2040ce_from_usb, + pad_firmware_up_to_storage, replace_config_in_binary, + write_new_config_to_filename, write_new_config_to_usb) from gp2040ce_bintools.storage import get_config, get_config_footer, get_storage_section, serialize_config_with_footer HERE = os.path.dirname(os.path.abspath(__file__)) @@ -150,3 +151,19 @@ def test_write_new_config_to_usb(config_binary): padded_serialized = bytearray(b'\x00' * 4) + serialized assert mock_write.call_args.args[2] % 256 == 0 assert mock_write.call_args.args[3] == padded_serialized + + +def test_get_gp2040ce_from_usb(): + """Test we attempt to read from the proper location over USB.""" + mock_out = mock.MagicMock() + mock_out.device.idVendor = 0xbeef + mock_out.device.idProduct = 0xcafe + mock_out.device.bus = 1 + mock_out.device.address = 2 + mock_in = mock.MagicMock() + with mock.patch('gp2040ce_bintools.builder.get_bootsel_endpoints', return_value=(mock_out, mock_in)) as mock_get: + with mock.patch('gp2040ce_bintools.builder.read') as mock_read: + config, _, _ = get_gp2040ce_from_usb() + + mock_get.assert_called_once() + mock_read.assert_called_with(mock_out, mock_in, 0x10000000, 2 * 1024 * 1024)