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>
This commit is contained in:
parent
610e1a2801
commit
a3f9f12e74
@ -6,7 +6,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
import argparse
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import struct
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from google.protobuf.message import Message
|
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 import core_parser
|
||||||
from gp2040ce_bintools.rp2040 import get_bootsel_endpoints, read, write
|
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,
|
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,
|
USER_CONFIG_BOOTSEL_ADDRESS, convert_binary_to_uf2, get_config_from_json,
|
||||||
serialize_config_with_footer)
|
pad_config_to_storage_size, serialize_config_with_footer)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GP2040CE_START_ADDRESS = 0x10000000
|
GP2040CE_START_ADDRESS = 0x10000000
|
||||||
GP2040CE_SIZE = 2 * 1024 * 1024
|
GP2040CE_SIZE = 2 * 1024 * 1024
|
||||||
|
|
||||||
UF2_FAMILY_ID = 0xE48BFF56
|
|
||||||
UF2_MAGIC_FIRST = 0x0A324655
|
|
||||||
UF2_MAGIC_SECOND = 0x9E5D5157
|
|
||||||
UF2_MAGIC_FINAL = 0x0AB16F30
|
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# LIBRARY ITEMS #
|
# 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))
|
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]:
|
def get_gp2040ce_from_usb() -> tuple[bytes, object, object]:
|
||||||
"""Read the firmware + config sections from a USB device.
|
"""Read the firmware + config sections from a USB device.
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
import argparse
|
import argparse
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
|
import struct
|
||||||
|
|
||||||
from google.protobuf.json_format import MessageToJson
|
from google.protobuf.json_format import MessageToJson
|
||||||
from google.protobuf.json_format import Parse as JsonParse
|
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_SIZE = 12
|
||||||
FOOTER_MAGIC = b'\x65\xe3\xf1\xd2'
|
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 #
|
# LIBRARY ITEMS #
|
||||||
@ -47,6 +53,39 @@ class ConfigMagicError(ConfigReadError):
|
|||||||
"""Exception raised when the config section does not have the magic value in its footer."""
|
"""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:
|
def get_config(content: bytes) -> Message:
|
||||||
"""Read the config from a GP2040-CE storage section.
|
"""Read the config from a GP2040-CE storage section.
|
||||||
|
|
||||||
|
@ -12,10 +12,9 @@ from decorator import decorator
|
|||||||
|
|
||||||
from gp2040ce_bintools import get_config_pb2
|
from gp2040ce_bintools import get_config_pb2
|
||||||
from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and_config,
|
from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and_config,
|
||||||
concatenate_firmware_and_storage_files, convert_binary_to_uf2,
|
concatenate_firmware_and_storage_files, get_gp2040ce_from_usb,
|
||||||
get_gp2040ce_from_usb, pad_binary_up_to_board_config,
|
pad_binary_up_to_board_config, pad_binary_up_to_user_config,
|
||||||
pad_binary_up_to_user_config, replace_config_in_binary,
|
replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb)
|
||||||
write_new_config_to_filename, write_new_config_to_usb)
|
|
||||||
from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer,
|
from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer,
|
||||||
get_user_storage_section, serialize_config_with_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
|
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
|
@with_pb2s
|
||||||
def test_concatenate_user_json_to_file(tmp_path):
|
def test_concatenate_user_json_to_file(tmp_path):
|
||||||
"""Test that we write a file with firmware + JSON user config as expected."""
|
"""Test that we write a file with firmware + JSON user config as expected."""
|
||||||
|
@ -130,6 +130,24 @@ def test_config_from_whole_board_parses(whole_board_dump):
|
|||||||
assert config.hotkeyOptions.hotkey02.dpadMask == 1
|
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
|
@with_pb2s
|
||||||
def test_serialize_config_with_footer(storage_dump, config_binary):
|
def test_serialize_config_with_footer(storage_dump, config_binary):
|
||||||
"""Test that reserializing a read in config matches the original.
|
"""Test that reserializing a read in config matches the original.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user