command to dump whole GP2040-CE image from USB

This commit is contained in:
Brian S. Stephan 2023-07-11 16:25:45 -05:00
parent ef842032f1
commit b7bb437ae8
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
4 changed files with 66 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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)