diff --git a/dir2uf2 b/dir2uf2 index 1e79b5c..87e9b65 100755 --- a/dir2uf2 +++ b/dir2uf2 @@ -5,6 +5,7 @@ import argparse import struct import pathlib import littlefs +from py_decl import PyDecl, UF2Reader UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" @@ -76,8 +77,8 @@ def bin_to_uf2(sections): parser = argparse.ArgumentParser() parser.add_argument("--filename", type=pathlib.Path, default="filesystem", help="Output filename.") -parser.add_argument("--fs-start", type=int, default=FS_START_ADDR, help="Filesystem offset.") -parser.add_argument("--fs-size", type=int, default=FS_SIZE, help="Filesystem size.") +parser.add_argument("--fs-start", type=int, default=None, help="Filesystem offset.") +parser.add_argument("--fs-size", type=int, default=None, help="Filesystem size.") parser.add_argument("--fs-truncate", action="store_true", help="Truncate filesystem to used bytes.") parser.add_argument("--block-size", type=int, default=4096, help="LFS block size in Kb.") parser.add_argument("--read-size", type=int, default=256, help="LFS read size in Kb.") @@ -89,6 +90,22 @@ parser.add_argument("--verbose", action="store_true", help="Verbose output.") parser.add_argument("source_dir", type=pathlib.Path, help="Source directory.") args = parser.parse_args() +if args.fs_start is None or args.fs_size is None: + if args.append_to is None: + raise argparse.ArgumentError("Either an --append-to UF2 file or explicit --fs-start and --fs-size required!") + + if not args.append_to.is_file(): + raise RuntimeError(f"Could not find {args.append_to}") + + py_decl = PyDecl(UF2Reader(args.append_to)) + parsed = py_decl.parse() + block_devices = parsed.get("BlockDevice", []) + for block_device in block_devices: + args.fs_start = block_device.get("address") + args.fs_size = block_device.get("size") + print(f"Auto detected fs: 0x{args.fs_start:08x} ({args.fs_start}), {args.fs_size} bytes.") + break + block_count = math.ceil(args.fs_size / args.block_size) if block_count * args.block_size != args.fs_size: print("image size should be a multiple of block size") @@ -157,7 +174,7 @@ print(f"Written: {bin_filename}") uf2_filename = output_filename.with_suffix(".uf2") with open(uf2_filename, "wb") as f: - for block in bin_to_uf2([(FS_START_ADDR, lfs.context.buffer[:lfs_used_bytes])]): + for block in bin_to_uf2([(args.fs_start, lfs.context.buffer[:lfs_used_bytes])]): f.write(block) print(f"Written: {uf2_filename}") diff --git a/py_decl.py b/py_decl.py new file mode 100755 index 0000000..4aa1a64 --- /dev/null +++ b/py_decl.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python3 +import io +import json +import struct +import sys + +DEBUG = False + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto +FAMILY_ID = 0xe48bff56 # RP2040 +FS_START_ADDR = 0x1012c000 # Pico W MicroPython LFSV2 offset + +FLASH_START_ADDR = 0x10000000 + +BLOCK_SIZE = 512 +DATA_SIZE = 256 +HEADER_SIZE = 32 +FOOTER_SIZE = 4 +PADDING_SIZE = BLOCK_SIZE - DATA_SIZE - HEADER_SIZE - FOOTER_SIZE +DATA_PADDING = b"\x00" * PADDING_SIZE + +BI_MAGIC = b"\xf2\xeb\x88\x71" +BI_END = b"\x90\xa3\x1a\xe7" + +GPIO_FUNC_XIP = 0 +GPIO_FUNC_SPI = 1 +GPIO_FUNC_UART = 2 +GPIO_FUNC_I2C = 3 +GPIO_FUNC_PWM = 4 +GPIO_FUNC_SIO = 5 +GPIO_FUNC_PIO0 = 6 +GPIO_FUNC_PIO1 = 7 +GPIO_FUNC_GPCK = 8 +GPIO_FUNC_USB = 9 +GPIO_FUNC_NULL = 0xf + +TYPE_RAW_DATA = 1 +TYPE_SIZED_DATA = 2 +TYPE_LIST_ZERO_TERMINATED = 3 +TYPE_BSON = 4 +TYPE_ID_AND_INT = 5 +TYPE_ID_AND_STRING = 6 + +TYPE_BLOCK_DEVICE = 7 +TYPE_PINS_WITH_FUNC = 8 +TYPE_PINS_WITH_NAME = 9 +TYPE_NAMED_GROUP = 10 + +ID_PROGRAM_NAME = 0x02031c86 +ID_PROGRAM_VERSION_STRING = 0x11a9bc3a +ID_PROGRAM_BUILD_DATE_STRING = 0x9da22254 +ID_BINARY_END = 0x68f465de +ID_PROGRAM_URL = 0x1856239a +ID_PROGRAM_DESCRIPTION = 0xb6a07c19 +ID_PROGRAM_FEATURE = 0xa1f4b453 +ID_PROGRAM_BUILD_ATTRIBUTE = 0x4275f0d3 +ID_SDK_VERSION = 0x5360b3ab +ID_PICO_BOARD = 0xb63cffbb +ID_BOOT2_NAME = 0x7f8882e1 +ID_FILESYSTEM = 0x1009be7e + +ID_MP_BUILTIN_MODULE = 0x4a99d719 + +IDS = { + ID_PROGRAM_NAME: "Program Name", + ID_PROGRAM_VERSION_STRING: "Program Version", + ID_PROGRAM_BUILD_DATE_STRING: "Build Date", + ID_BINARY_END: "Binary End Address", + ID_PROGRAM_URL: "Program URL", + ID_PROGRAM_DESCRIPTION: "Program Description", + ID_PROGRAM_FEATURE: "Program Feature", + ID_PROGRAM_BUILD_ATTRIBUTE: "Program Build Attribute", + ID_SDK_VERSION: "SDK Version", + ID_PICO_BOARD: "Pico Board", + ID_BOOT2_NAME: "Boot Stage 2 Name" +} + +TYPES = { + TYPE_RAW_DATA: "Raw Data", + TYPE_SIZED_DATA: "Sized Data", + TYPE_LIST_ZERO_TERMINATED: "Zero Terminated List", + TYPE_BSON: "BSON", + TYPE_ID_AND_INT: "ID & Int", + TYPE_ID_AND_STRING: "ID & Str", + TYPE_BLOCK_DEVICE: "Block Device", + TYPE_PINS_WITH_FUNC: "Pins With Func", + TYPE_PINS_WITH_NAME: "Pins With Name", + TYPE_NAMED_GROUP: "Named Group" +} + +GPIO_FUNCS = { + GPIO_FUNC_XIP: "XIP", + GPIO_FUNC_SPI: "SPI", + GPIO_FUNC_UART: "UART", + GPIO_FUNC_I2C: "I2C", + GPIO_FUNC_PWM: "PWM", + GPIO_FUNC_SIO: "SIO", + GPIO_FUNC_PIO0: "PIO0", + GPIO_FUNC_PIO1: "PIO1", + GPIO_FUNC_GPCK: "GPCK", + GPIO_FUNC_USB: "USB", + GPIO_FUNC_NULL: "NULL" +} + +ALWAYS_A_LIST = ("NamedGroup", "BlockDevice", "ProgramFeature") + + +class MemoryReader(): + def __init__(self, mem, global_offset=FLASH_START_ADDR): + self.mem = mem + self.offset = 0 + self.buf_len = 16 + self.global_offset = global_offset + self.buf = bytearray(self.buf_len) + + def seek(self, offset): + self.offset = offset + + def read(self, length): + buf = bytearray(length) if length > self.buf_len else self.buf + for i in range(length): + buf[i] = self.mem[self.global_offset + self.offset] + self.offset += 1 + return bytes(buf[:length]) + + +class UF2Reader(io.BytesIO): + def __init__(self, filepath): + bin = b"".join(self.uf2_to_bin(filepath)) + io.BytesIO.__init__(self, bin) + + def uf2_to_bin(self, filepath): + file = open(filepath, "rb") + while data := file.read(BLOCK_SIZE): + # start0, start1, flags, addr, size, block_no, num_blocks, family_id = struct.unpack(b"> 3 + func_name = GPIO_FUNCS.get(func) + + pin_encoding >>= 7 + pins = [] + + if encoding_type == 0b001: # Individual pins + for _ in range(5): + pins.append(pin_encoding & 0b11111) + pin_encoding >>= 5 + elif encoding_type == 0b010: # Range of pins + pin_end = pin_encoding & 0b11111 + pin_start = (pin_encoding >> 5) & 0b11111 + pins = list(range(pin_start, pin_end + 1)) + + result = {} + for pin in pins: + result[pin] = {"function": func_name} + + return "Pins", result + + def _parse_pins_with_name(self, tag): + pin_mask, name_addr = struct.unpack("