SummerCart64/sw/update/update.py
Mateusz Faderewski ff69030643
[SC64][FW][HW][SW] New version based on LCMXO2 FPGA (#19)
* isv support + usb/dd improvements

* make room for saves

* update offset

* fixed debug address

* idk

* exception

* ironed out all broken stuff

* cleanup

* return epc fix

* better

* more cleanup

* even more cleanup

* mooore cleanup

* fixed printf

* no assert

* improved docker build, pyft232 instead of pyserial

* fixed displaying long message strings

description test

* just straight cleanup

* smallest cleanup

* PAL

* cpu buffer

* n64 bootloader done

* super slow usb storage reading implemented

* reduced buffer size

* usb gets fast

* little cleanup

* double buffered reads

* removed separate event id

* ISV in hardware finally

* small exception changes

* mac testing

* py spacing

* fsd write, rtc, isv and reset fixes

* fixxx

* good stopping point

* usb fixed?

* pretend we have 128 MB sdram

* backup

* chmod

* test

* test done

* more tests

* user rm

* help

* final fix

* updated component values

* nice asset names

* cic 64dd support

* ddipl enable separation

* pre DMA rewrite, created dedicated buffer memory space, simplified code

* dma rewrite, needs testing

* moved xml

* dd basics

* timing

* 64dd working yet again, isv brought back, dma fixes, usb path rewrite, pc code rewrite

* added usb read functionality, general cleanup

* changed mem addressing

* added fpga flash update access

* added mcu update

* chmod

* little cleanup

* update format and stuff

* fixes

* uninitialized fix

* small fixes

* update fixes

* update stuff done

* fpga update tested

* build time fix

* boot fix

* test timing

* readme test

* test 2

* reports

* testseet

* final

* build test

* forgot

* button and naming

* General cleanup

And multiline commit message test

* Exception screen UI touch ups

* display separation and tests beginning

* pc software update

* pc software done

* timing test

* delete launch.json

* sw fixes

* fixed button hole diameter in shell

* small cleanup, rpi testing

* shell fillet fix, pc rtc printing

* added cfg lock mechanism

* moved lock to cfg address space

* extended ROM and ISV fixes

* preliminary sd card support

* little sd card cleanup

* sd menu fixes

* 5 second limit

* reduced shell thickness

* basic led act blinking

* faster sd menu loading

* inst cache invalidate

* sd card writing is working

* SD card CSD and CID registers

* wait for previous command

* led error codes

* fixed cfg_translate_address use

* 64dd from sd card working

* 64dd speedup and button handling

* delayed address latching cycle - might break other builds, needs testing

* bootloader improvements

* small fixes

* return previous cfg when setting new

* cache stuff

* unfloader debug protocol support

* UNFLoader style debug command line support

* requirements.txt

* shell groove fillet

* reset state inside controller

* fixed fast PI read, added PI R/W fifo debug info

* PI access prioritize

* SD clock stop when RX FIFO is more than half full

* flash erase method change

* CFG error handling, TLOZ MM debug ISV support

* CIC5167 support

* general fixes

* USB unplugged cable handling

* turn off led when changing between error/act modes

* rtc 2 bit clock stop support

* line endings

* Revert "line endings"

This reverts commit d0ddfe5ec7.

* PI address debug

* readme test

* diagram update

* diagram background

* diagram background

* diagram background

* updated readme
2022-11-10 11:46:54 +01:00

229 lines
7.5 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import math
import os
import platform
import sys
from binascii import crc32
from datetime import datetime
from io import BufferedRandom
class JedecError(Exception):
pass
class JedecFile:
__fuse_length: int = 0
__fuse_offset: int = 0
__fuse_data: bytes = b''
__byte_buffer: int = 0
def __handle_q_field(self, f: BufferedRandom) -> None:
type = f.read(1)
if (type == b'F'):
value = b''
while (True):
data = f.read(1)
if (data == b'*'):
value = value.decode('ascii', errors='backslashreplace')
if (not value.isdecimal()):
raise JedecError('Invalid Q field data')
self.__fuse_length = int(value)
break
else:
value += data
else:
self.__ignore_field(f)
def __handle_l_field(self, f: BufferedRandom) -> None:
if (self.__fuse_length <= 0):
raise JedecError('Found fuse data before declaring fuse count')
offset = b''
while (True):
data = f.read(1)
if (data >= b'0' and data <= b'9'):
offset += data
elif (data == b'\r' or data == b'\n'):
offset = offset.decode('ascii', errors='backslashreplace')
if (not offset.isdecimal()):
raise JedecError('Invalid L field offset data')
offset = int(offset)
if (offset != self.__fuse_offset):
raise JedecError('Fuse data is not continuous')
break
else:
raise JedecError('Unexpected byte inside L field offset data')
data = b''
while (True):
data = f.read(1)
if (data == b'0' or data == b'1'):
shift = (7 - (self.__fuse_offset % 8))
self.__byte_buffer |= (1 if data == b'1' else 0) << shift
if (((self.__fuse_offset % 8) == 7) or (self.__fuse_offset == (self.__fuse_length - 1))):
self.__fuse_data += int.to_bytes(self.__byte_buffer, 1, byteorder='little')
self.__byte_buffer = 0
self.__fuse_offset += 1
elif (data == b'\r' or data == b'\n'):
pass
elif (data == b'*'):
break
elif (data == b''):
raise JedecError('Unexpected end of file')
else:
raise JedecError('Unexpected byte inside L field fuse data')
def __ignore_field(self, f: BufferedRandom) -> None:
data = None
while (data != b'*'):
data = f.read(1)
if (data == b''):
raise JedecError('Unexpected end of file')
def parse(self, path: str) -> bytes:
self.__fuse_length = 0
self.__fuse_offset = 0
self.__fuse_data = b''
self.__byte_buffer = 0
field = None
with open(path, 'rb+') as f:
while (True):
field = f.read(1)
if (field == b'\x02'):
f.seek(-1, os.SEEK_CUR)
break
elif (field == b''):
raise JedecError('Unexpected end of file')
while (True):
field = f.read(1)
if (field == b'Q'):
self.__handle_q_field(f)
elif (field == b'L'):
self.__handle_l_field(f)
elif (field == b'\r' or field == b'\n'):
pass
elif (field == b'\x03'):
break
elif (field == b''):
raise JedecError('Unexpected end of file')
else:
self.__ignore_field(f)
if (self.__fuse_length <= 0):
raise JedecError('No fuse data found')
if (self.__fuse_offset != self.__fuse_length):
raise JedecError('Missing fuse data inside JEDEC file')
if (len(self.__fuse_data) != math.ceil(self.__fuse_length / 8)):
raise JedecError('Missing fuse data inside JEDEC file')
return self.__fuse_data
class SC64UpdateData:
__UPDATE_TOKEN = b'SC64 Update v2.0'
__CHUNK_ID_UPDATE_INFO = 1
__CHUNK_ID_MCU_DATA = 2
__CHUNK_ID_FPGA_DATA = 3
__CHUNK_ID_BOOTLOADER_DATA = 4
__data = b''
def __int_to_bytes(self, value: int) -> bytes:
return value.to_bytes(4, byteorder='little')
def __align(self, value: int) -> int:
if (value % 16 != 0):
value += (16 - (value % 16))
return value
def __add_chunk(self, id: int, data: bytes) -> None:
chunk = b''
chunk_length = (16 + len(data))
aligned_length = self.__align(chunk_length)
chunk += self.__int_to_bytes(id)
chunk += self.__int_to_bytes(aligned_length - 8)
chunk += self.__int_to_bytes(crc32(data))
chunk += self.__int_to_bytes(len(data))
chunk += data
chunk += bytes([0] * (aligned_length - chunk_length))
self.__data += chunk
def create_update_data(self) -> None:
self.__data = self.__UPDATE_TOKEN
def add_update_info(self, data: bytes) -> None:
self.__add_chunk(self.__CHUNK_ID_UPDATE_INFO, data)
def add_mcu_data(self, data: bytes) -> None:
self.__add_chunk(self.__CHUNK_ID_MCU_DATA, data)
def add_fpga_data(self, data: bytes) -> None:
self.__add_chunk(self.__CHUNK_ID_FPGA_DATA, data)
def add_bootloader_data(self, data: bytes) -> None:
self.__add_chunk(self.__CHUNK_ID_BOOTLOADER_DATA, data)
def get_update_data(self) -> bytes:
return self.__data
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='SC64 update file generator')
parser.add_argument('--git', metavar='git', required=False, help='git text to embed in update info')
parser.add_argument('--mcu', metavar='mcu_path', required=False, help='path to MCU update data')
parser.add_argument('--fpga', metavar='fpga_path', required=False, help='path to FPGA update data')
parser.add_argument('--boot', metavar='bootloader_path', required=False, help='path to N64 bootloader update data')
parser.add_argument('output', metavar='output_path', help='path to final update data')
if (len(sys.argv) <= 1):
parser.print_help()
parser.exit()
args = parser.parse_args()
try:
update = SC64UpdateData()
update.create_update_data()
hostname = platform.node()
creation_datetime = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
info = [
f'build system: [{hostname}]',
f'creation datetime: [{creation_datetime}]',
]
if (args.git):
info.append(args.git)
update_info = ' '.join(info)
print(update_info)
update.add_update_info(update_info.encode())
if (args.mcu):
with open(args.mcu, 'rb+') as f:
update.add_mcu_data(f.read())
if (args.fpga):
update.add_fpga_data(JedecFile().parse(args.fpga))
if (args.boot):
with open(args.boot, 'rb+') as f:
update.add_bootloader_data(f.read())
with open(args.output, 'wb+') as f:
f.write(update.get_update_data())
except JedecError as e:
print(f'Error while parsing FPGA update data: {e}')
exit(-1)
except IOError as e:
print(f'IOError: {e}')
exit(-1)