95 lines
2.9 KiB
Python
95 lines
2.9 KiB
Python
# SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Code was originally licensed under Apache 2.0 before the release of ESP-IDF v5.2
|
|
|
|
import hashlib
|
|
import os
|
|
import struct
|
|
from typing import List
|
|
|
|
from esptool.util import div_roundup
|
|
|
|
|
|
class UF2Writer(object):
|
|
# The UF2 format is described here: https://github.com/microsoft/uf2
|
|
UF2_BLOCK_SIZE = 512
|
|
# max value of CHUNK_SIZE reduced by optional parts. Currently, MD5_PART only.
|
|
UF2_DATA_SIZE = 476
|
|
UF2_MD5_PART_SIZE = 24
|
|
UF2_FIRST_MAGIC = 0x0A324655
|
|
UF2_SECOND_MAGIC = 0x9E5D5157
|
|
UF2_FINAL_MAGIC = 0x0AB16F30
|
|
UF2_FLAG_FAMILYID_PRESENT = 0x00002000
|
|
UF2_FLAG_MD5_PRESENT = 0x00004000
|
|
|
|
def __init__(
|
|
self,
|
|
chip_id: int,
|
|
output_file: os.PathLike,
|
|
chunk_size: int,
|
|
md5_enabled: bool = True,
|
|
) -> None:
|
|
if not md5_enabled:
|
|
self.UF2_MD5_PART_SIZE = 0
|
|
self.UF2_FLAG_MD5_PRESENT = 0x00000000
|
|
self.md5_enabled = md5_enabled
|
|
self.chip_id = chip_id
|
|
self.CHUNK_SIZE = (
|
|
self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE
|
|
if chunk_size is None
|
|
else chunk_size
|
|
)
|
|
self.f = open(output_file, "wb")
|
|
|
|
def __enter__(self) -> "UF2Writer":
|
|
return self
|
|
|
|
def __exit__(self, exc_type: str, exc_val: int, exc_tb: List) -> None:
|
|
if self.f:
|
|
self.f.close()
|
|
|
|
@staticmethod
|
|
def _to_uint32(num: int) -> bytes:
|
|
return struct.pack("<I", num)
|
|
|
|
def _write_block(
|
|
self, addr: int, chunk: bytes, len_chunk: int, block_no: int, blocks: int
|
|
) -> None:
|
|
assert len_chunk > 0
|
|
assert len_chunk <= self.CHUNK_SIZE
|
|
assert block_no < blocks
|
|
block = struct.pack(
|
|
"<IIIIIIII",
|
|
self.UF2_FIRST_MAGIC,
|
|
self.UF2_SECOND_MAGIC,
|
|
self.UF2_FLAG_FAMILYID_PRESENT | self.UF2_FLAG_MD5_PRESENT,
|
|
addr,
|
|
len_chunk,
|
|
block_no,
|
|
blocks,
|
|
self.chip_id,
|
|
)
|
|
block += chunk
|
|
|
|
if self.md5_enabled:
|
|
md5_part = struct.pack("<II", addr, len_chunk)
|
|
md5_part += hashlib.md5(chunk).digest()
|
|
assert len(md5_part) == self.UF2_MD5_PART_SIZE
|
|
|
|
block += md5_part
|
|
block += b"\x00" * (self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE - len_chunk)
|
|
block += self._to_uint32(self.UF2_FINAL_MAGIC)
|
|
assert len(block) == self.UF2_BLOCK_SIZE
|
|
self.f.write(block)
|
|
|
|
def add_file(self, addr: int, image: bytes) -> None:
|
|
blocks = div_roundup(len(image), self.CHUNK_SIZE)
|
|
chunks = [
|
|
image[i : i + self.CHUNK_SIZE]
|
|
for i in range(0, len(image), self.CHUNK_SIZE)
|
|
]
|
|
for i, chunk in enumerate(chunks):
|
|
len_chunk = len(chunk)
|
|
self._write_block(addr, chunk, len_chunk, i, blocks)
|
|
addr += len_chunk
|