Files
templates/python/set_devices/tms_flash.py

120 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Планирование дампа внешней памяти TMS и сборка ответов CMD_UPLOAD. Адреса XINTF считаются
# 16-битными словами, размер передачи — байтами; прогресс и смещение рассчитываются с явным
# переводом единиц.
"""Legacy BALZAM/TMS parallel-memory dump protocol.
The TMS320F2812 addresses external XINTF memory in 16-bit words, while
``CMD_UPLOAD`` transfers a byte count. This module keeps those units explicit
so a complete dump cannot silently become half or twice the requested size.
"""
from __future__ import annotations
from dataclasses import dataclass
from protocan import tms2812 as shared_tms2812
CMD_UPLOAD = 52
DEFAULT_CONTROLLER_ADDRESS = 5
PARALLEL_NVM_START_WORD = 0x100000
PARALLEL_NVM_WORDS = 0x80000
PARALLEL_NVM_BYTES = PARALLEL_NVM_WORDS * 2
MT29F1G08_WORDS = 0x4000000
MT29F1G08_BYTES = MT29F1G08_WORDS * 2
DEFAULT_CHUNK_BYTES = 4096
NAND_CHUNK_BYTES = 2048
UPLOAD_TRAILER_BYTES = 6 # CRC16 + four legacy padding bytes
class TmsFlashError(ValueError):
"""Malformed command, reply, or dump range."""
def build_upload_request(address: int, word_address: int, byte_count: int) -> bytes:
"""Build the 12-byte legacy ``CMD_UPLOAD`` request."""
if not 0 <= address <= 0xFF:
raise TmsFlashError("адрес контроллера вне диапазона 0..255")
if not 0 <= word_address <= 0xFFFFFFFF:
raise TmsFlashError("адрес слова вне диапазона uint32")
if not 1 <= byte_count <= 0xFFFFFFFF:
raise TmsFlashError("размер блока вне диапазона 1..0xFFFFFFFF")
try:
return shared_tms2812.build_upload_request(
address, word_address, byte_count
)
except ValueError as error:
raise TmsFlashError(str(error)) from error
def decode_upload_reply(raw: bytes, address: int, byte_count: int) -> bytes:
"""Validate one complete upload reply and return its memory bytes."""
expected = 2 + byte_count + UPLOAD_TRAILER_BYTES
if len(raw) != expected:
raise TmsFlashError(
f"ответ CMD_UPLOAD: ожидалось {expected} байт, получено {len(raw)}"
)
try:
return shared_tms2812.decode_upload_reply(raw, address, byte_count)
except ValueError as error:
raise TmsFlashError(str(error)) from error
@dataclass(frozen=True)
class DumpChunk:
"""One request in a word-addressed, byte-transferred dump."""
word_address: int
byte_count: int
class FlashDumpPlan:
"""Deterministic chunk plan used by the GUI and host tests."""
def __init__(self, start_word: int, word_count: int, chunk_bytes: int) -> None:
if start_word < 0 or start_word > 0xFFFFFFFF:
raise TmsFlashError("начальный адрес слова вне диапазона uint32")
if word_count <= 0:
raise TmsFlashError("число слов должно быть положительным")
if start_word + word_count > 0x100000000:
raise TmsFlashError("конец диапазона выходит за uint32")
if chunk_bytes < 2 or chunk_bytes > 0xFFFF or chunk_bytes % 2:
raise TmsFlashError("блок должен быть чётным числом байт 2..65534")
self.start_word = start_word
self.word_count = word_count
self.chunk_bytes = chunk_bytes
self.total_bytes = word_count * 2
def chunks(self) -> list[DumpChunk]:
result = []
offset = 0
while offset < self.total_bytes:
size = min(self.chunk_bytes, self.total_bytes - offset)
result.append(DumpChunk(self.start_word + offset // 2, size))
offset += size
return result
class UploadReplyBuffer:
"""Collect arbitrary serial fragments for exactly one upload reply."""
def __init__(self, address: int, byte_count: int) -> None:
self.address = address
self.byte_count = byte_count
self.expected_size = 2 + byte_count + UPLOAD_TRAILER_BYTES
self._buffer = bytearray()
@property
def received_size(self) -> int:
return len(self._buffer)
def feed(self, data: bytes) -> tuple[bytes | None, bytes]:
"""Return ``(payload, trailing_data)`` when the reply is complete."""
self._buffer.extend(data)
if len(self._buffer) < self.expected_size:
return None, b""
raw = bytes(self._buffer[: self.expected_size])
trailing = bytes(self._buffer[self.expected_size :])
self._buffer.clear()
return decode_upload_reply(raw, self.address, self.byte_count), trailing