concatenate command: combine firmware and storage into one file
This commit is contained in:
parent
c9b443f993
commit
85d84144fc
17
README.md
17
README.md
@ -25,6 +25,22 @@ As above, plus also `pip install -Ur requirements/requirements-dev.txt` to get l
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
|
In all cases, online help can be retrieved by providing the `-h` or ``--help`` flags to the below programs.
|
||||||
|
|
||||||
|
### concatenate
|
||||||
|
|
||||||
|
**concatenate** combines a GP2040-CE firmware .bin file (such as from a fresh build) and a GP2040-CE board's storage
|
||||||
|
section .bin, and produces a properly-offset .bin file suitable for flashing to a board. This may be useful to ensure
|
||||||
|
the board is flashed with a particular configuration, for instances such as producing a binary to flash many boards with
|
||||||
|
a particular configuration (specific customizations, etc.), or keeping documented backups of what you're testing with
|
||||||
|
during development.
|
||||||
|
|
||||||
|
Sample usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
% concatenate build/GP2040-CE_foo_bar.bin storage-dump.bin new-firmware-with-config.bin
|
||||||
|
```
|
||||||
|
|
||||||
### visualize-storage
|
### visualize-storage
|
||||||
|
|
||||||
**visualize-storage** reads a dump of a GP2040-CE board's flash storage section, where the configuration lives, and
|
**visualize-storage** reads a dump of a GP2040-CE board's flash storage section, where the configuration lives, and
|
||||||
@ -121,6 +137,7 @@ context. The storage section of a GP2040-CE board is a reserved 8 KB starting at
|
|||||||
```
|
```
|
||||||
|
|
||||||
And to dump your whole board:
|
And to dump your whole board:
|
||||||
|
|
||||||
```
|
```
|
||||||
% picotool save -a whole-board.bin
|
% picotool save -a whole-board.bin
|
||||||
```
|
```
|
||||||
|
69
gp2040ce_bintools/builder.py
Normal file
69
gp2040ce_bintools/builder.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Build binary files for a GP2040-CE board."""
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from gp2040ce_bintools import core_parser
|
||||||
|
from gp2040ce_bintools.storage import STORAGE_LOCATION
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# LIBRARY ITEMS #
|
||||||
|
#################
|
||||||
|
|
||||||
|
|
||||||
|
class FirmwareLengthError(ValueError):
|
||||||
|
"""Exception raised when the firmware is too large to fit the known storage location."""
|
||||||
|
|
||||||
|
|
||||||
|
def concatenate_firmware_and_storage_files(firmware_filename: str, storage_filename: str, combined_filename: str):
|
||||||
|
"""Open the provided binary files and combine them into one combined GP2040-CE with config file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
firmware_filename: filename of the firmware binary to read
|
||||||
|
storage_filename: filename of the storage section to read
|
||||||
|
combined_filename: filename of where to write the combine binary
|
||||||
|
"""
|
||||||
|
with open(firmware_filename, 'rb') as firmware, open(storage_filename, 'rb') as storage:
|
||||||
|
new_binary = pad_firmware_up_to_storage(firmware.read()) + bytearray(storage.read())
|
||||||
|
with open(combined_filename, 'wb') as combined:
|
||||||
|
combined.write(new_binary)
|
||||||
|
|
||||||
|
|
||||||
|
def pad_firmware_up_to_storage(firmware: bytes) -> bytearray:
|
||||||
|
"""Provide a copy of the firmware padded with zero bytes up to the provided position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
firmware: the read-in binary file to process
|
||||||
|
Returns:
|
||||||
|
the resulting padded binary as a bytearray
|
||||||
|
Raises:
|
||||||
|
FirmwareLengthError: if the firmware is larger than the storage location
|
||||||
|
"""
|
||||||
|
bytes_to_pad = STORAGE_LOCATION - len(firmware)
|
||||||
|
logger.debug("firmware is length %s, padding %s bytes", len(firmware), bytes_to_pad)
|
||||||
|
if bytes_to_pad < 0:
|
||||||
|
raise FirmwareLengthError(f"provided firmware binary is larger than the start of "
|
||||||
|
f"storage at {STORAGE_LOCATION}!")
|
||||||
|
|
||||||
|
return bytes(bytearray(firmware) + bytearray(b'\x00' * bytes_to_pad))
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# COMMANDS #
|
||||||
|
############
|
||||||
|
|
||||||
|
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 .bin into one file "
|
||||||
|
"suitable for flashing onto a board.",
|
||||||
|
parents=[core_parser],
|
||||||
|
)
|
||||||
|
parser.add_argument('firmware_filename', help=".bin file of a GP2040-CE firmware, probably from a build")
|
||||||
|
parser.add_argument('storage_filename', help=".bin file of a GP2040-CE board's storage section")
|
||||||
|
parser.add_argument('new_binary_filename', help="output .bin file of the resulting firmware + storage")
|
||||||
|
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
concatenate_firmware_and_storage_files(args.firmware_filename, args.storage_filename, args.new_binary_filename)
|
@ -19,6 +19,7 @@ dev = ["bandit", "decorator", "flake8", "flake8-blind-except", "flake8-builtins"
|
|||||||
"flake8-pyproject", "mypy", "pip-tools", "pytest", "pytest-cov", "setuptools-scm", "tox"]
|
"flake8-pyproject", "mypy", "pip-tools", "pytest", "pytest-cov", "setuptools-scm", "tox"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
concatenate = "gp2040ce_bintools.builder:concatenate"
|
||||||
visualize-storage = "gp2040ce_bintools.storage:visualize"
|
visualize-storage = "gp2040ce_bintools.storage:visualize"
|
||||||
|
|
||||||
[tool.flake8]
|
[tool.flake8]
|
||||||
|
@ -6,6 +6,16 @@ import pytest
|
|||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def firmware_binary():
|
||||||
|
"""Read in a test GP2040-CE firmware binary file."""
|
||||||
|
filename = os.path.join(HERE, 'test-files', 'test-firmware.bin')
|
||||||
|
with open(filename, 'rb') as file:
|
||||||
|
content = file.read()
|
||||||
|
|
||||||
|
yield content
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def storage_dump():
|
def storage_dump():
|
||||||
"""Read in a test storage dump file (101FE000-10200000) of a GP2040-CE board."""
|
"""Read in a test storage dump file (101FE000-10200000) of a GP2040-CE board."""
|
||||||
|
BIN
tests/test-files/test-firmware.bin
Normal file
BIN
tests/test-files/test-firmware.bin
Normal file
Binary file not shown.
16
tests/test_builder.py
Normal file
16
tests/test_builder.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Tests for the image builder module."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from gp2040ce_bintools.builder import FirmwareLengthError, pad_firmware_up_to_storage
|
||||||
|
|
||||||
|
|
||||||
|
def test_padding_firmware(firmware_binary):
|
||||||
|
"""Test that firmware is padded to the expected size."""
|
||||||
|
padded = pad_firmware_up_to_storage(firmware_binary)
|
||||||
|
assert len(padded) == 2088960
|
||||||
|
|
||||||
|
|
||||||
|
def test_padding_firmware_too_big(firmware_binary):
|
||||||
|
"""Test that firmware is padded to the expected size."""
|
||||||
|
with pytest.raises(FirmwareLengthError):
|
||||||
|
_ = pad_firmware_up_to_storage(firmware_binary + firmware_binary + firmware_binary)
|
@ -1,5 +1,6 @@
|
|||||||
"""Test our tools themselves to make sure they adhere to certain flags."""
|
"""Test our tools themselves to make sure they adhere to certain flags."""
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
|
|
||||||
from gp2040ce_bintools import __version__
|
from gp2040ce_bintools import __version__
|
||||||
@ -18,6 +19,17 @@ def test_help_flag():
|
|||||||
assert 'Read the configuration section from a dump of a GP2040-CE board' in result.stdout
|
assert 'Read the configuration section from a dump of a GP2040-CE board' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_concatenate_invocation(tmpdir):
|
||||||
|
"""Test that a normal invocation against a dump works."""
|
||||||
|
out_filename = os.path.join(tmpdir, 'out.bin')
|
||||||
|
_ = run(['concatenate', 'tests/test-files/test-firmware.bin', 'tests/test-files/test-storage-area.bin',
|
||||||
|
out_filename])
|
||||||
|
with open(out_filename, 'rb') as out_file, open('tests/test-files/test-storage-area.bin', 'rb') as storage_file:
|
||||||
|
out = out_file.read()
|
||||||
|
storage = storage_file.read()
|
||||||
|
assert out[2088960:2097152] == storage
|
||||||
|
|
||||||
|
|
||||||
def test_storage_dump_invocation():
|
def test_storage_dump_invocation():
|
||||||
"""Test that a normal invocation against a dump works."""
|
"""Test that a normal invocation against a dump works."""
|
||||||
result = run(['visualize-storage', '-P', 'tests/test-files/proto-files', 'tests/test-files/test-storage-area.bin'],
|
result = run(['visualize-storage', '-P', 'tests/test-files/proto-files', 'tests/test-files/test-storage-area.bin'],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user