323 lines
10 KiB
Python
323 lines
10 KiB
Python
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2024 Realtek Semiconductor Corp.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import io
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
from .errno import *
|
|
from .rtk_utils import *
|
|
|
|
STX = 0x02 # Transfer data
|
|
EOT = 0x04 # End of transfer
|
|
BAUDSET = 0x05 # For handshake
|
|
BAUDCHK = 0x07 # For handshake, only for UART
|
|
ACK = 0x06 # Acknowledge
|
|
NAK = 0x15 # Negativ acknowledge
|
|
CAN = 0x18 # Cancel transfer, only for UART
|
|
ESC = 0x1B # Cancel transfer
|
|
|
|
STX_BASE_LEN = 8
|
|
STX_UART_RAM_DATA_LEN = 1024
|
|
STX_USB_RAM_DATA_LEN = 2048
|
|
STX_MAX_LEN = STX_BASE_LEN + STX_USB_RAM_DATA_LEN
|
|
FloaderSizeLimit = 1024 * 1024 # 1MB
|
|
|
|
StxBaseLen = 1024
|
|
StxUartDataLen = 1024
|
|
StxUsbDataLen = 2048
|
|
StxMaxDataLen = StxBaseLen + StxUsbDataLen
|
|
DEFAULT_TIMEOUT = 0.5
|
|
STX_TIMEOUT = 1
|
|
|
|
FloaderDictionary = "Devices/Floaders"
|
|
|
|
|
|
class RomHandler(object):
|
|
def __init__(self, ameba_obj, padding="FF"):
|
|
self.ameba = ameba_obj
|
|
self.serial_port = ameba_obj.serial_port
|
|
self.logger = ameba_obj.logger
|
|
self.profile = ameba_obj.profile_info
|
|
self.baudrate = ameba_obj.baudrate
|
|
self.is_usb = ameba_obj.is_usb
|
|
self.stx_packet_no = 1
|
|
self.padding = padding
|
|
self.setting = ameba_obj.setting
|
|
|
|
def get_baudrate_idx(self, rate):
|
|
''' rom built-in rate table '''
|
|
return {
|
|
110: 0,
|
|
300: 1,
|
|
600: 2,
|
|
1200: 3,
|
|
2400: 4,
|
|
4800: 5,
|
|
9600: 6,
|
|
14400: 7,
|
|
19200: 8,
|
|
28800: 9,
|
|
38400: 10,
|
|
57600: 11,
|
|
76800: 12,
|
|
115200: 13,
|
|
128000: 14,
|
|
153600: 15,
|
|
230400: 16,
|
|
380400: 17,
|
|
460800: 18,
|
|
500000: 19,
|
|
921600: 20,
|
|
1000000: 21,
|
|
1382400: 22,
|
|
1444400: 23,
|
|
1500000: 24,
|
|
1843200: 25,
|
|
2000000: 26,
|
|
2100000: 27,
|
|
2764800: 28,
|
|
3000000: 29,
|
|
3250000: 30,
|
|
3692300: 31,
|
|
3750000: 32,
|
|
4000000: 33,
|
|
6000000: 34
|
|
}.get(rate, 13)
|
|
|
|
def send_request(self, request, length, timeout):
|
|
ret = ErrType.SYS_UNKNOWN
|
|
response = []
|
|
|
|
try:
|
|
for retry in range(2):
|
|
if retry > 0:
|
|
self.logger.debug(f"Request retry {retry}#: len={length}, payload={request.hex()}")
|
|
else:
|
|
self.logger.debug(f"Request: len={length}, payload={request.hex()}")
|
|
|
|
self.serial_port.flushInput()
|
|
self.serial_port.flushOutput()
|
|
|
|
self.ameba.write_bytes(request)
|
|
|
|
for resp_retry in range(3):
|
|
ret, ch = self.ameba.read_bytes(timeout)
|
|
if ret != ErrType.OK:
|
|
self.logger.debug(f"Reponse error: {ret}")
|
|
break
|
|
|
|
if ch[0] == ACK:
|
|
self.logger.debug(f"Response ACK")
|
|
break
|
|
elif ch[0] == NAK:
|
|
ret = ErrType.SYS_NAK
|
|
self.logger.debug(f"Response NAK")
|
|
continue
|
|
elif ch[0] == CAN:
|
|
ret = ErrType.SYS_CANCEL
|
|
self.logger.debug(f"Response CAN")
|
|
break
|
|
else:
|
|
ret = ErrType.SYS_PROTO
|
|
self.logger.debug(f"Unexpected reponse: 0x{ch.hex()}")
|
|
continue
|
|
|
|
if ret == ErrType.OK or ret == ErrType.SYS_CANCEL:
|
|
break
|
|
else:
|
|
time.sleep(self.setting.request_retry_interval_second)
|
|
except Exception as err:
|
|
self.logger.error(f"Send request exception: {err}")
|
|
ret = ErrType.SYS_IO
|
|
|
|
return ret
|
|
|
|
def reset(self):
|
|
self.stx_packet_no = 1
|
|
|
|
def handshake(self):
|
|
ret = ErrType.OK
|
|
self.logger.debug(f"Handshake:{'USB' if self.is_usb else 'UART'}")
|
|
index = self.get_baudrate_idx(self.baudrate)
|
|
|
|
if not self.is_usb:
|
|
ret = self.check_alive()
|
|
if ret != ErrType.OK:
|
|
self.logger.debug(f"Handshake fail: device not alive")
|
|
return ret
|
|
|
|
retry = 0
|
|
while retry < 2:
|
|
if retry > 0:
|
|
self.logger.debug(f"Handshake retry {retry}#")
|
|
|
|
retry += 1
|
|
|
|
if self.setting.switch_baudrate_at_floader == 0:
|
|
self.logger.debug(f"BAUDSET: {index} ({self.baudrate})")
|
|
_bytes = [BAUDSET]
|
|
_bytes.append(index)
|
|
cmd = bytearray(_bytes)
|
|
|
|
ret = self.send_request(cmd, 2, DEFAULT_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
self.logger.debug(f"BAUDSET ok")
|
|
else:
|
|
self.logger.debug(f"BAUDSET fail: {ret}")
|
|
continue
|
|
|
|
if self.is_usb:
|
|
break
|
|
|
|
ret = self.ameba.switch_baudrate(self.baudrate, self.setting.baudrate_switch_delay_in_second)
|
|
if ret != ErrType.OK:
|
|
continue
|
|
|
|
self.logger.debug(f"BAUDCHK")
|
|
ret = self.send_request(BAUDCHK.to_bytes(1, byteorder="little"), 1, DEFAULT_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
self.logger.debug("BAUDCHK ok")
|
|
break
|
|
else:
|
|
self.logger.error(f"BAUDCHK fail: {ret}")
|
|
else:
|
|
if self.is_usb:
|
|
self.logger.debug(f"BAUDSET")
|
|
_bytes = [BAUDSET]
|
|
_bytes.append(index)
|
|
cmd = bytearray(_bytes)
|
|
ret = self.send_request(cmd, 2, DEFAULT_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
self.logger.debug(f"Baudset ok")
|
|
break
|
|
else:
|
|
self.logger.debug(f"Baudset fail: {ret}")
|
|
else:
|
|
self.logger.debug("BAUDCHK")
|
|
ret = self.send_request(BAUDCHK.to_bytes(1, byteorder="little"), 1, DEFAULT_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
self.logger.debug(f"Baudchk ok")
|
|
break
|
|
else:
|
|
self.logger.debug(f"Baudchk fail: {ret}")
|
|
|
|
if ret == ErrType.OK:
|
|
self.logger.debug("Handshake done")
|
|
else:
|
|
self.logger.debug(f"Handshake fail: {ret}")
|
|
|
|
return ret
|
|
|
|
def check_alive(self):
|
|
ret, ch = self.ameba.read_bytes(DEFAULT_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
if ch[0] != NAK:
|
|
self.logger.debug(f"Check alive error,expect NAK, get 0x{ch.hex()}")
|
|
else:
|
|
self.logger.debug(f"Check alive error: {ret}")
|
|
|
|
return ret
|
|
|
|
def transfer(self, address, data_bytes):
|
|
self.logger.debug(f"STX {self.stx_packet_no}#: addr={hex(address)}")
|
|
stx_data = [STX]
|
|
stx_data.append(self.stx_packet_no & 0xFF)
|
|
stx_data.append((~self.stx_packet_no) & 0xFF)
|
|
|
|
stx_data.extend(list(address.to_bytes(4, byteorder='little')))
|
|
|
|
stx_bytes = bytearray(stx_data)
|
|
stx_bytes += data_bytes
|
|
|
|
checksum = sum(stx_bytes[3:]) % 256
|
|
stx_bytes += checksum.to_bytes(1, byteorder="little")
|
|
|
|
ret = self.send_request(stx_bytes, len(stx_bytes), STX_TIMEOUT)
|
|
if ret == ErrType.OK:
|
|
self.logger.debug(f"STX {self.stx_packet_no}# done")
|
|
self.stx_packet_no += 1
|
|
else:
|
|
self.logger.debug(f"STX {self.stx_packet_no}# fail: {ret}")
|
|
|
|
return ret
|
|
|
|
def end_transfer(self):
|
|
self.logger.debug(f"EOT")
|
|
return self.send_request(EOT.to_bytes(1, byteorder="little"), 1, DEFAULT_TIMEOUT)
|
|
|
|
def abort(self):
|
|
self.logger.debug(f"ESC")
|
|
self.send_request(ESC.to_bytes(1, byteorder="little"), 1, DEFAULT_TIMEOUT)
|
|
|
|
def get_page_aligned_size(self, size, page_size):
|
|
result = size
|
|
|
|
if (size % page_size) != 0:
|
|
result = (size // page_size + 1) * page_size
|
|
|
|
return result
|
|
|
|
def get_floader_path(self):
|
|
floader_path = None
|
|
|
|
if self.profile.floader:
|
|
floader_path = os.path.realpath(os.path.join(RtkUtils.get_executable_root_path(), FloaderDictionary, self.profile.floader))
|
|
|
|
if floader_path is None:
|
|
self.logger.error("Flashloader not specified in device profile")
|
|
elif not os.path.exists(floader_path):
|
|
self.logger.error(f"Flashloader not exists: {floader_path}")
|
|
|
|
return floader_path
|
|
|
|
def download_floader(self):
|
|
ret = ErrType.OK
|
|
page_size = StxUsbDataLen if self.is_usb else StxUartDataLen
|
|
self.reset()
|
|
|
|
floader = self.get_floader_path()
|
|
if floader is None:
|
|
return ErrType.SYS_PARAMETER
|
|
with open(floader, 'rb') as stream:
|
|
floader_content = stream.read()
|
|
|
|
floader_size = len(floader_content)
|
|
if floader_size > FloaderSizeLimit:
|
|
self.logger.error(f"Invalid floader {floader} with too large size: {floader_size}")
|
|
return ErrType.SYS_OVERRANGE
|
|
|
|
_baud_bytes = self.serial_port.baudrate.to_bytes(4, byteorder='little')
|
|
|
|
floader_aligned_size = self.get_page_aligned_size(floader_size, page_size)
|
|
datas = io.BytesIO(floader_content)
|
|
data_bytes = datas.read(floader_size)
|
|
|
|
data_bytes += _baud_bytes
|
|
|
|
data_bytes = data_bytes.ljust(floader_aligned_size, b"\x00")
|
|
|
|
idx = 0
|
|
floader_addr = self.profile.floader_address
|
|
while idx < floader_aligned_size:
|
|
trans_data = data_bytes[idx:idx+page_size]
|
|
ret = self.transfer(floader_addr, trans_data)
|
|
if ret != ErrType.OK:
|
|
return ret
|
|
idx += page_size
|
|
floader_addr += page_size
|
|
|
|
ret = self.end_transfer()
|
|
if ret != ErrType.OK:
|
|
if self.profile.is_amebaz():
|
|
# AmebaZ would tx ACK after rx EOT but would not wait tx ACK done, so in low baudrate(115200...),
|
|
# ACK will not be received
|
|
# This issue has been fixed after AmebaD
|
|
ret = ErrType.OK
|
|
|
|
return ret
|