diff --git a/sw/bootloader/src/fatfs/diskio.c b/sw/bootloader/src/fatfs/diskio.c index 95cd235..c2df333 100644 --- a/sw/bootloader/src/fatfs/diskio.c +++ b/sw/bootloader/src/fatfs/diskio.c @@ -3,12 +3,10 @@ #include "diskio.h" #include "../io.h" #include "../sc64.h" -#include "../error.h" #define SD_SECTOR_SIZE (512) #define BUFFER_BLOCKS_MAX (sizeof(SC64_BUFFERS->BUFFER) / SD_SECTOR_SIZE) -#define FROM_BCD(x) ((((x >> 4) & 0x0F) * 10) + (x & 0x0F)) DSTATUS disk_status (BYTE pdrv) { @@ -17,7 +15,7 @@ DSTATUS disk_status (BYTE pdrv) { } DSTATUS status = 0; - sd_card_status_t sd_card_status = sc64_sd_card_get_status(); + sc64_sd_card_status_t sd_card_status = sc64_sd_card_get_status(); if (!(sd_card_status & SD_CARD_STATUS_INSERTED)) { status |= STA_NODISK; @@ -114,7 +112,7 @@ DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void *buff) { } DWORD get_fattime(void) { - rtc_time_t t; + sc64_rtc_time_t t; sc64_get_time(&t); return ( ((FROM_BCD(t.year) + 20) << 25) | diff --git a/sw/bootloader/src/init.c b/sw/bootloader/src/init.c index a2e38dc..50cfef1 100644 --- a/sw/bootloader/src/init.c +++ b/sw/bootloader/src/init.c @@ -6,8 +6,8 @@ void init (void) { - uint32_t pifram = si_io_read((io32_t *) (&PIFRAM[0x3C])); - si_io_write((io32_t *) (&PIFRAM[0x3C]), pifram | 0x08); + uint32_t pifram = si_io_read((io32_t *) (PIFRAM_STATUS)); + si_io_write((io32_t *) (PIFRAM_STATUS), pifram | PIFRAM_TERMINATE_BOOT); exception_install(); @@ -29,7 +29,7 @@ void init (void) { } void deinit (void) { + sc64_lock(); exception_disable_interrupts(); exception_disable_watchdog(); - sc64_lock(); } diff --git a/sw/bootloader/src/io.h b/sw/bootloader/src/io.h index 5d08d61..bd51656 100644 --- a/sw/bootloader/src/io.h +++ b/sw/bootloader/src/io.h @@ -17,6 +17,8 @@ typedef volatile uint32_t io32_t; #define N64_RAM_SIZE (0x00800000UL) +#define FROM_BCD(x) ((((x >> 4) & 0x0F) * 10) + (x & 0x0F)) + typedef struct { io32_t DMEM[1024]; @@ -227,6 +229,10 @@ typedef struct { #define PIFRAM_BASE (0x1FC007C0UL) #define PIFRAM ((io8_t *) PIFRAM_BASE) +#define PIFRAM_STATUS (&PIFRAM[0x3C]) + +#define PIFRAM_TERMINATE_BOOT (1 << 3) + typedef struct { uint32_t tv_type; @@ -244,6 +250,9 @@ typedef struct { #define OS_INFO_BASE (0x80000300UL) #define OS_INFO ((os_info_t *) OS_INFO_BASE) +#define OS_INFO_RESET_TYPE_COLD (0) +#define OS_INFO_RESET_TYPE_NMI (1) + uint32_t cpu_io_read (io32_t *address); void cpu_io_write (io32_t *address, uint32_t value); diff --git a/sw/bootloader/src/menu.c b/sw/bootloader/src/menu.c index 3fdc6e1..de75ea9 100644 --- a/sw/bootloader/src/menu.c +++ b/sw/bootloader/src/menu.c @@ -81,13 +81,13 @@ void menu_load_and_run (void) { size = (size_t) (f_size(&fil) - ROM_CODE_OFFSET); } menu_check_load_address(menu, size); + cache_data_hit_writeback_invalidate(menu, size); + cache_inst_hit_invalidate(menu, size); FF_CHECK(f_read(&fil, menu, size, &br), "Couldn't read menu file"); FF_CHECK((br != size) ? FR_INT_ERR : FR_OK, "Read size is different than expected"); FF_CHECK(f_close(&fil), "Couldn't close menu file"); FF_CHECK(f_unmount(""), "Couldn't unmount drive"); - cache_inst_hit_invalidate(menu, size); - deinit(); menu(); diff --git a/sw/bootloader/src/sc64.c b/sw/bootloader/src/sc64.c index e73b1a4..c2c96ed 100644 --- a/sw/bootloader/src/sc64.c +++ b/sw/bootloader/src/sc64.c @@ -52,60 +52,50 @@ typedef enum { SD_CARD_OP_GET_INFO = 3, } sd_card_op_t; -static sc64_pi_io_t pi_io = { - .read = pi_io_read, - .write = pi_io_write -}; - static bool sc64_wait_cpu_busy (void) { uint32_t sr; do { - sr = pi_io.read(&SC64_REGS->SR_CMD); + sr = pi_io_read(&SC64_REGS->SR_CMD); } while (sr & SC64_SR_CPU_BUSY); return (sr & SC64_SR_CMD_ERROR); } static bool sc64_execute_cmd (uint8_t cmd, uint32_t *args, uint32_t *result) { if (args != NULL) { - pi_io.write(&SC64_REGS->DATA[0], args[0]); - pi_io.write(&SC64_REGS->DATA[1], args[1]); + pi_io_write(&SC64_REGS->DATA[0], args[0]); + pi_io_write(&SC64_REGS->DATA[1], args[1]); } - pi_io.write(&SC64_REGS->SR_CMD, ((uint32_t) (cmd)) & 0xFF); + pi_io_write(&SC64_REGS->SR_CMD, ((uint32_t) (cmd)) & 0xFF); bool error = sc64_wait_cpu_busy(); if (result != NULL) { - result[0] = pi_io.read(&SC64_REGS->DATA[0]); - result[1] = pi_io.read(&SC64_REGS->DATA[1]); + result[0] = pi_io_read(&SC64_REGS->DATA[0]); + result[1] = pi_io_read(&SC64_REGS->DATA[1]); } return error; } -void sc64_set_pi_io_functions (sc64_pi_io_t functions) { - pi_io.read = functions.read; - pi_io.write = functions.write; -} - sc64_error_t sc64_get_error (void) { - if (pi_io.read(&SC64_REGS->SR_CMD) & SC64_SR_CMD_ERROR) { - return (sc64_error_t) (pi_io.read(&SC64_REGS->DATA[0])); + if (pi_io_read(&SC64_REGS->SR_CMD) & SC64_SR_CMD_ERROR) { + return (sc64_error_t) (pi_io_read(&SC64_REGS->DATA[0])); } return SC64_OK; } void sc64_unlock (void) { - pi_io.write(&SC64_REGS->KEY, SC64_KEY_RESET); - pi_io.write(&SC64_REGS->KEY, SC64_KEY_UNLOCK_1); - pi_io.write(&SC64_REGS->KEY, SC64_KEY_UNLOCK_2); + pi_io_write(&SC64_REGS->KEY, SC64_KEY_RESET); + pi_io_write(&SC64_REGS->KEY, SC64_KEY_UNLOCK_1); + pi_io_write(&SC64_REGS->KEY, SC64_KEY_UNLOCK_2); } void sc64_lock (void) { - pi_io.write(&SC64_REGS->KEY, SC64_KEY_RESET); - pi_io.write(&SC64_REGS->KEY, SC64_KEY_LOCK); + pi_io_write(&SC64_REGS->KEY, SC64_KEY_RESET); + pi_io_write(&SC64_REGS->KEY, SC64_KEY_LOCK); } bool sc64_check_presence (void) { - uint32_t version = pi_io.read(&SC64_REGS->VERSION); + uint32_t version = pi_io_read(&SC64_REGS->VERSION); if (version == SC64_VERSION_2) { sc64_wait_cpu_busy(); return true; @@ -114,35 +104,35 @@ bool sc64_check_presence (void) { } bool sc64_irq_pending (void) { - if (pi_io.read(&SC64_REGS->SR_CMD) & SC64_SR_IRQ_PENDING) { + if (pi_io_read(&SC64_REGS->SR_CMD) & SC64_SR_IRQ_PENDING) { return true; } return false; } void sc64_irq_clear (void) { - pi_io.write(&SC64_REGS->VERSION, 0); + pi_io_write(&SC64_REGS->VERSION, 0); } -uint32_t sc64_get_config (cfg_id_t id) { +uint32_t sc64_get_config (sc64_cfg_id_t id) { uint32_t args[2] = { id, 0 }; uint32_t result[2]; sc64_execute_cmd(SC64_CMD_CONFIG_GET, args, result); return result[1]; } -void sc64_set_config (cfg_id_t id, uint32_t value) { +void sc64_set_config (sc64_cfg_id_t id, uint32_t value) { uint32_t args[2] = { id, value }; sc64_execute_cmd(SC64_CMD_CONFIG_SET, args, NULL); } void sc64_get_boot_info (sc64_boot_info_t *info) { - info->cic_seed = (uint16_t) sc64_get_config(CFG_ID_CIC_SEED); - info->tv_type = (tv_type_t) sc64_get_config(CFG_ID_TV_TYPE); - info->boot_mode = (boot_mode_t) sc64_get_config(CFG_ID_BOOT_MODE); + info->boot_mode = (sc64_boot_mode_t) sc64_get_config(CFG_ID_BOOT_MODE); + info->cic_seed = (sc64_cic_seed_t) sc64_get_config(CFG_ID_CIC_SEED); + info->tv_type = (sc64_tv_type_t) sc64_get_config(CFG_ID_TV_TYPE); } -void sc64_get_time (rtc_time_t *t) { +void sc64_get_time (sc64_rtc_time_t *t) { uint32_t result[2]; sc64_execute_cmd(SC64_CMD_TIME_GET, NULL, result); t->second = (result[0] & 0xFF); @@ -154,7 +144,7 @@ void sc64_get_time (rtc_time_t *t) { t->year = ((result[1] >> 16) & 0xFF); } -void sc64_set_time (rtc_time_t *t) { +void sc64_set_time (sc64_rtc_time_t *t) { uint32_t args[2] = { ((t->hour << 16) | (t->minute << 8) | t->second), ((t->weekday << 24) | (t->year << 16) | (t->month << 8) | t->day), @@ -214,13 +204,13 @@ bool sc64_sd_card_deinit (void) { return false; } -sd_card_status_t sc64_sd_card_get_status (void) { +sc64_sd_card_status_t sc64_sd_card_get_status (void) { uint32_t args[2] = { (uint32_t) (NULL), SD_CARD_OP_GET_STATUS }; uint32_t result[2]; if (sc64_execute_cmd(SC64_CMD_SD_CARD_OP, args, result)) { return false; } - return (sd_card_status_t) (result[1]); + return (sc64_sd_card_status_t) (result[1]); } bool sc64_sd_card_get_info (void *address) { diff --git a/sw/bootloader/src/sc64.h b/sw/bootloader/src/sc64.h index 41d68b4..b8bdb9d 100644 --- a/sw/bootloader/src/sc64.h +++ b/sw/bootloader/src/sc64.h @@ -32,21 +32,21 @@ typedef enum { CFG_ID_BUTTON_STATE, CFG_ID_BUTTON_MODE, CFG_ID_ROM_EXTENDED_ENABLE, -} cfg_id_t; +} sc64_cfg_id_t; typedef enum { DD_MODE_DISABLED = 0, DD_MODE_REGS = 1, DD_MODE_IPL = 2, DD_MODE_FULL = 3 -} dd_mode_t; +} sc64_dd_mode_t; typedef enum { BOOT_MODE_MENU = 0, BOOT_MODE_ROM = 1, BOOT_MODE_DDIPL = 2, BOOT_MODE_DIRECT = 3 -} boot_mode_t; +} sc64_boot_mode_t; typedef enum { SAVE_TYPE_NONE = 0, @@ -55,37 +55,37 @@ typedef enum { SAVE_TYPE_SRAM = 3, SAVE_TYPE_FLASHRAM = 4, SAVE_TYPE_SRAM_BANKED = 5 -} save_type_t; +} sc64_save_type_t; typedef enum { CIC_SEED_UNKNOWN = 0xFFFF -} cic_seed_t; +} sc64_cic_seed_t; typedef enum { TV_TYPE_PAL = 0, TV_TYPE_NTSC = 1, TV_TYPE_MPAL = 2, TV_TYPE_UNKNOWN = 3 -} tv_type_t; +} sc64_tv_type_t; typedef enum { BUTTON_MODE_NONE, BUTTON_MODE_N64_IRQ, BUTTON_MODE_USB_PACKET, BUTTON_MODE_DD_DISK_SWAP, -} button_mode_t; +} sc64_button_mode_t; typedef enum { SD_CARD_STATUS_INSERTED = (1 << 0), SD_CARD_STATUS_INITIALIZED = (1 << 1), SD_CARD_STATUS_TYPE_BLOCK = (1 << 2), SD_CARD_STATUS_50MHZ_MODE = (1 << 3), -} sd_card_status_t; +} sc64_sd_card_status_t; typedef struct { - boot_mode_t boot_mode; - uint16_t cic_seed; - tv_type_t tv_type; + sc64_boot_mode_t boot_mode; + sc64_cic_seed_t cic_seed; + sc64_tv_type_t tv_type; } sc64_boot_info_t; typedef struct { @@ -96,7 +96,7 @@ typedef struct { uint8_t day; uint8_t month; uint8_t year; -} rtc_time_t; +} sc64_rtc_time_t; typedef struct { @@ -109,13 +109,6 @@ typedef struct { #define SC64_BUFFERS_BASE (0x1FFE0000UL) #define SC64_BUFFERS ((sc64_buffers_t *) SC64_BUFFERS_BASE) -typedef struct { - uint32_t (*read)(volatile uint32_t *address); - void (*write)(volatile uint32_t *address, uint32_t value); -} sc64_pi_io_t; - - -void sc64_set_pi_io_functions (sc64_pi_io_t functions); sc64_error_t sc64_get_error (void); @@ -126,12 +119,12 @@ bool sc64_check_presence (void); bool sc64_irq_pending (void); void sc64_irq_clear (void); -uint32_t sc64_get_config (cfg_id_t id); -void sc64_set_config (cfg_id_t id, uint32_t value); +uint32_t sc64_get_config (sc64_cfg_id_t id); +void sc64_set_config (sc64_cfg_id_t id, uint32_t value); void sc64_get_boot_info (sc64_boot_info_t *info); -void sc64_get_time (rtc_time_t *t); -void sc64_set_time (rtc_time_t *t); +void sc64_get_time (sc64_rtc_time_t *t); +void sc64_set_time (sc64_rtc_time_t *t); bool sc64_usb_read_ready (uint8_t *type, uint32_t *length); bool sc64_usb_read (void *address, uint32_t length); @@ -140,7 +133,7 @@ bool sc64_usb_write (void *address, uint8_t type, uint32_t length); bool sc64_sd_card_init (void); bool sc64_sd_card_deinit (void); -sd_card_status_t sc64_sd_card_get_status (void); +sc64_sd_card_status_t sc64_sd_card_get_status (void); bool sc64_sd_card_get_info (void *address); bool sc64_sd_write_sectors (void *address, uint32_t sector, uint32_t count); bool sc64_sd_read_sectors (void *address, uint32_t sector, uint32_t count); diff --git a/sw/bootloader/src/startup.S b/sw/bootloader/src/startup.S index 2b2fe03..638e067 100644 --- a/sw/bootloader/src/startup.S +++ b/sw/bootloader/src/startup.S @@ -1,7 +1,10 @@ #include "vr4300.h" -.section .text.rom_header +.section .text.rom_header, "a", %progbits +.type rom_header, %object +rom_header: + header_pi_config: .word 0x80371240 @@ -23,12 +26,14 @@ header_text_info: .org 0x40, 0x00 -.section .text.ipl3 +.section .text.ipl3, "a", %progbits +.type ipl3, %object ipl3: .incbin "header", 0x40 -.section .text.entry_handler +.section .text.entry_handler, "ax", %progbits +.type entry_handler, %function entry_handler: .global entry_handler diff --git a/sw/bootloader/src/test.c b/sw/bootloader/src/test.c index 6fb4a67..a9ce44e 100644 --- a/sw/bootloader/src/test.c +++ b/sw/bootloader/src/test.c @@ -5,21 +5,23 @@ #include "test.h" -bool test_check (void) { - if (sc64_get_config(CFG_ID_BUTTON_STATE)) { - return true; - } - return false; +static void test_rtc (void) { + sc64_rtc_time_t t; + const char *weekdays[8] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + + sc64_get_time(&t); + + display_printf("RTC current time:\n"); + display_printf(" %02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second)); + display_printf(" %s ", weekdays[FROM_BCD(t.weekday)]); + display_printf("%d.%02d.%04d", FROM_BCD(t.day), FROM_BCD(t.month), 2000 + FROM_BCD(t.year)); + display_printf("\n"); } -void test_execute (void) { - sd_card_status_t card_status; +static void test_sd_card (void) { + sc64_sd_card_status_t card_status; uint8_t card_info[32] __attribute__((aligned(8))); - display_init(NULL); - - display_printf("SC64 Test suite\n\n"); - card_status = sc64_sd_card_get_status(); if (card_status & SD_CARD_STATUS_INSERTED) { @@ -30,7 +32,7 @@ void test_execute (void) { if (sc64_sd_card_init()) { display_printf("SD card init error!\n"); - while (1); + return; } card_status = sc64_sd_card_get_status(); @@ -47,7 +49,7 @@ void test_execute (void) { if (sc64_sd_card_get_info((uint32_t *) (SC64_BUFFERS->BUFFER))) { display_printf("SD card get info error!\n"); - while (1); + return; } pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), card_info, sizeof(card_info)); @@ -64,6 +66,27 @@ void test_execute (void) { for (int i = 16; i < 32; i++) { display_printf("%c ", card_info[i] >= ' ' ? card_info[i] : 0xFF); } +} + + +bool test_check (void) { + if (OS_INFO->reset_type != OS_INFO_RESET_TYPE_COLD) { + return false; + } + return sc64_get_config(CFG_ID_BUTTON_STATE); +} + +void test_execute (void) { + display_init(NULL); + display_printf("SC64 Test suite\n\n"); + + display_printf("[ RTC tests ]\n"); + test_rtc(); + display_printf("\n"); + + display_printf("[ SD card tests ]\n"); + test_sd_card(); + display_printf("\n"); while (1); } diff --git a/sw/pc/primer.py b/sw/pc/primer.py index 7c6003c..05f6e0a 100644 --- a/sw/pc/primer.py +++ b/sw/pc/primer.py @@ -1,520 +1,520 @@ -#!/usr/bin/env python3 - -import io -import os -import serial -import signal -import sys -import time -from binascii import crc32 -from sc64 import SC64 -from typing import Callable, Optional - - - -class Utils: - __progress_active = False - - @staticmethod - def log(message: str='') -> None: - print(message) - - @staticmethod - def log_no_end(message: str='') -> None: - print(message, end='', flush=True) - - @staticmethod - def info(message: str='') -> None: - print(f'\033[92m{message}\033[0m') - - @staticmethod - def warning(message: str='') -> None: - print(f'\033[93m{message}\033[0m') - - @staticmethod - def die(reason: str) -> None: - print(f'\033[91m{reason}\033[0m') - exit(-1) - - @property - def get_progress_active(self): - return self.__progress_active - - def progress(self, length: int, position: int, description: str) -> None: - value = ((position / length) * 100.0) - if (position == 0): - self.__progress_active = True - Utils.log_no_end(f'\r{value:5.1f}%: [{description}]') - if (position == length): - Utils.log() - self.__progress_active = False - - def exit_warning(self): - if (self.__progress_active): - Utils.log() - Utils.warning('Ctrl-C is prohibited during bring-up procedure') - - -class SC64UpdateDataException(Exception): - pass - -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 - __CHUNK_ID_PRIMER_DATA = 5 - - __update_info: Optional[str] - __mcu_data: Optional[bytes] - __fpga_data: Optional[bytes] - __bootloader_data: Optional[bytes] - __primer_data: Optional[bytes] - - def __load_int(self, f: io.BufferedReader) -> int: - try: - data = f.read(4) - if (len(data) != 4): - raise ValueError('Read size did not match requested amount') - value = int.from_bytes(data, byteorder='little') - except ValueError as e: - raise SC64UpdateDataException(f'Error while reading chunk header: {e}') - return value - - def __load_chunk(self, f: io.BufferedReader) -> tuple[int, bytes]: - id = self.__load_int(f) - aligned_length = self.__load_int(f) - checksum = self.__load_int(f) - data_length = self.__load_int(f) - - data = f.read(data_length) - - align = (aligned_length - 4 - 4 - data_length) - f.seek(align, io.SEEK_CUR) - - if (crc32(data) != checksum): - raise SC64UpdateDataException(f'Invalid checksum for chunk id [{id}] inside update file') - - return (id, data) - - def load(self, path: str, require_all: bool=False) -> None: - self.__update_info = None - self.__mcu_data = None - self.__fpga_data = None - self.__bootloader_data = None - self.__primer_data = None - - try: - with open(path, 'rb') as f: - if (f.read(len(self.__UPDATE_TOKEN)) != self.__UPDATE_TOKEN): - raise SC64UpdateDataException('Invalid update file header') - - while (f.peek(1) != b''): - (id, data) = self.__load_chunk(f) - if (id == self.__CHUNK_ID_UPDATE_INFO): - self.__update_info = data.decode('ascii') - elif (id == self.__CHUNK_ID_MCU_DATA): - self.__mcu_data = data - elif (id == self.__CHUNK_ID_FPGA_DATA): - self.__fpga_data = data - elif (id == self.__CHUNK_ID_BOOTLOADER_DATA): - self.__bootloader_data = data - elif (id == self.__CHUNK_ID_PRIMER_DATA): - self.__primer_data = data - else: - raise SC64UpdateDataException('Unknown chunk inside update file') - - if (require_all): - if (not self.__update_info): - raise SC64UpdateDataException('No update info inside update file') - if (not self.__mcu_data): - raise SC64UpdateDataException('No MCU data inside update file') - if (not self.__fpga_data): - raise SC64UpdateDataException('No FPGA data inside update file') - if (not self.__bootloader_data): - raise SC64UpdateDataException('No bootloader data inside update file') - if (not self.__primer_data): - raise SC64UpdateDataException('No primer data inside update file') - - except IOError as e: - raise SC64UpdateDataException(f'IO error while loading update data: {e}') - - def get_update_info(self) -> Optional[str]: - return self.__update_info - - def get_mcu_data(self) -> Optional[bytes]: - return self.__mcu_data - - def get_fpga_data(self) -> Optional[bytes]: - return self.__fpga_data - - def get_bootloader_data(self) -> Optional[bytes]: - return self.__bootloader_data - - def get_primer_data(self) -> Optional[bytes]: - return self.__primer_data - - -class STM32BootloaderException(Exception): - pass - -class STM32Bootloader: - __INIT = b'\x7F' - __ACK = b'\x79' - __NACK = b'\x1F' - - __MEMORY_RW_MAX_SIZE = 256 - __FLASH_LOAD_ADDRESS = 0x08000000 - __FLASH_MAX_LOAD_SIZE = 0x8000 - __RAM_LOAD_ADDRESS = 0x20001000 - __RAM_MAX_LOAD_SIZE = 0x1000 - - DEV_ID_STM32G030XX = b'\x04\x66' - - __connected = False - - def __init__(self, write: Callable[[bytes], None], read: Callable[[int], bytes], progress: Callable[[int, int, str], None]): - self.__write = write - self.__read = read - self.__progress = progress - - def __append_xor(self, data: bytes) -> bytes: - xor = (0xFF if (len(data) == 1) else 0x00) - for b in data: - xor ^= b - return bytes([*data, xor]) - - def __check_ack(self) -> None: - response = self.__read(1) - if (response == None): - raise STM32BootloaderException('No ACK/NACK byte received') - if (response == self.__NACK): - raise STM32BootloaderException('NACK byte received') - if (response != self.__ACK): - raise STM32BootloaderException('Unknown ACK/NACK byte received') - - def __cmd_send(self, cmd: bytes) -> None: - if (len(cmd) != 1): - raise ValueError('Command must contain only one byte') - self.__write(self.__append_xor(cmd)) - self.__check_ack() - - def __data_write(self, data: bytes) -> None: - self.__write(self.__append_xor(data)) - self.__check_ack() - - def __data_read(self) -> bytes: - length = self.__read(1) - if (len(length) != 1): - raise STM32BootloaderException('Did not receive length byte') - length = length[0] - data = self.__read(length + 1) - self.__check_ack() - return data - - def __get_id(self) -> bytes: - self.__cmd_send(b'\x02') - return self.__data_read() - - def __read_memory(self, address: int, length: int) -> bytes: - if (length == 0 or length > self.__MEMORY_RW_MAX_SIZE): - raise ValueError('Wrong data size for read memory command') - self.__cmd_send(b'\x11') - self.__data_write(address.to_bytes(4, byteorder='big')) - self.__data_write(bytes([length - 1])) - return self.__read(length) - - def __go(self, address: int) -> None: - self.__cmd_send(b'\x21') - self.__data_write(address.to_bytes(4, byteorder='big')) - self.__connected = False - - def __write_memory(self, address: int, data: bytes) -> None: - length = len(data) - if (length == 0 or length > self.__MEMORY_RW_MAX_SIZE): - raise ValueError('Wrong data size for write memory command') - if (((address % 4) != 0) or ((length % 4) != 0)): - raise ValueError('Write memory command requires 4 byte alignment') - self.__cmd_send(b'\x31') - self.__data_write(address.to_bytes(4, byteorder='big')) - self.__data_write(bytes([length - 1, *data])) - - def __mass_erase(self) -> None: - self.__cmd_send(b'\x44') - self.__data_write(b'\xFF\xFF') - - def __load_memory(self, address: int, data: bytes, description: str='') -> None: - length = len(data) - self.__progress(length, 0, description) - for offset in range(0, length, self.__MEMORY_RW_MAX_SIZE): - chunk = data[offset:offset + self.__MEMORY_RW_MAX_SIZE] - self.__write_memory(address + offset, chunk) - verify = self.__read_memory(address + offset, len(chunk)) - if (chunk != verify): - raise STM32BootloaderException('Memory verify failed') - self.__progress(length, offset, description) - self.__progress(length, length, description) - - def connect(self, id: int) -> None: - if (not self.__connected): - self.__write(self.__INIT) - self.__check_ack() - self.__connected = True - dev_id = self.__get_id() - if (dev_id != id): - raise STM32BootloaderException('Unknown chip detected') - - def load_ram_and_run(self, data: bytes, description: str='') -> None: - if (len(data) > self.__RAM_MAX_LOAD_SIZE): - raise STM32BootloaderException('RAM image too big') - self.__load_memory(self.__RAM_LOAD_ADDRESS, data, description) - self.__go(self.__RAM_LOAD_ADDRESS) - - def load_flash_and_run(self, data: bytes, description: str='') -> None: - if (len(data) > self.__FLASH_MAX_LOAD_SIZE): - raise STM32BootloaderException('Flash image too big') - self.__mass_erase() - try: - self.__load_memory(self.__FLASH_LOAD_ADDRESS, data, description) - self.__go(self.__FLASH_LOAD_ADDRESS) - except STM32BootloaderException as e: - self.__mass_erase() - raise STM32BootloaderException(e) - - -class LCMXO2PrimerException(Exception): - pass - -class LCMXO2Primer: - __PRIMER_ID_LCMXO2 = b'MXO2' - - __CMD_GET_PRIMER_ID = b'?' - __CMD_PROBE_FPGA = b'#' - __CMD_RESTART = b'$' - __CMD_GET_DEVICE_ID = b'I' - __CMD_ENABLE_FLASH = b'E' - __CMD_ERASE_FLASH = b'X' - __CMD_RESET_ADDRESS = b'A' - __CMD_WRITE_PAGE = b'W' - __CMD_READ_PAGE = b'R' - __CMD_PROGRAM_DONE = b'F' - __CMD_INIT_FEATBITS = b'Q' - __CMD_REFRESH = b'B' - - __FLASH_PAGE_SIZE = 16 - __FLASH_NUM_PAGES = 11260 - - __FPGA_PROBE_VALUE = b'\x64' - - DEV_ID_LCMXO2_7000HC = b'\x01\x2B\xD0\x43' - - def __init__(self, write: Callable[[bytes], None], read: Callable[[int], bytes], progress: Callable[[int, int, str], None]): - self.__write = write - self.__read = read - self.__progress = progress - - def __cmd_execute(self, cmd: bytes, data: bytes=b'') -> bytes: - if (len(cmd) != 1): - raise ValueError('Command must contain only one byte') - if (len(data) >= 256): - raise ValueError('Data size too big') - - packet = b'CMD' + cmd - packet += len(data).to_bytes(1, byteorder='little') - packet += data - packet += crc32(packet).to_bytes(4, byteorder='little') - self.__write(packet) - - response = self.__read(5) - if (len(response) != 5): - raise LCMXO2PrimerException(f'No response received [{cmd}]') - length = int.from_bytes(response[4:5], byteorder='little') - response += self.__read(length) - calculated_checksum = crc32(response) - received_checksum = int.from_bytes(self.__read(4), byteorder='little') - - if (response[0:3] != b'RSP'): - raise LCMXO2PrimerException(f'Invalid response token [{response[0:3]} / {cmd}]') - if (response[3:4] != cmd): - raise LCMXO2PrimerException(f'Invalid response command [{cmd} / {response[3]}]') - if (calculated_checksum != received_checksum): - raise LCMXO2PrimerException(f'Invalid response checksum [{cmd}]') - - return response[5:] - - def connect(self, id: bytes) -> None: - primer_id = self.__cmd_execute(self.__CMD_GET_PRIMER_ID) - if (primer_id != self.__PRIMER_ID_LCMXO2): - raise LCMXO2PrimerException('Invalid primer ID received') - - dev_id = self.__cmd_execute(self.__CMD_GET_DEVICE_ID) - if (dev_id != id): - raise LCMXO2PrimerException('Invalid FPGA device id received') - - def load_flash_and_run(self, data: bytes, description: str) -> None: - erase_description = f'{description} / Erase' - program_description = f'{description} / Program' - verify_description = f'{description} / Verify' - - length = len(data) - if (length > (self.__FLASH_PAGE_SIZE * self.__FLASH_NUM_PAGES)): - raise LCMXO2PrimerException('FPGA data size too big') - if ((length % self.__FLASH_PAGE_SIZE) != 0): - raise LCMXO2PrimerException('FPGA data size not aligned to page size') - - self.__cmd_execute(self.__CMD_ENABLE_FLASH) - - self.__progress(length, 0, erase_description) - self.__cmd_execute(self.__CMD_ERASE_FLASH) - self.__progress(length, length, erase_description) - - try: - self.__cmd_execute(self.__CMD_RESET_ADDRESS) - self.__progress(length, 0, program_description) - for offset in range(0, length, self.__FLASH_PAGE_SIZE): - page_data = data[offset:(offset + self.__FLASH_PAGE_SIZE)] - self.__cmd_execute(self.__CMD_WRITE_PAGE, page_data) - self.__progress(length, offset, program_description) - self.__progress(length, length, program_description) - - self.__cmd_execute(self.__CMD_RESET_ADDRESS) - self.__progress(length, 0, verify_description) - for offset in range(0, length, self.__FLASH_PAGE_SIZE): - page_data = data[offset:(offset + self.__FLASH_PAGE_SIZE)] - verify_data = self.__cmd_execute(self.__CMD_READ_PAGE) - self.__progress(length, offset, verify_description) - if (page_data != verify_data): - raise LCMXO2PrimerException('FPGA verification error') - self.__progress(length, length, verify_description) - - self.__cmd_execute(self.__CMD_INIT_FEATBITS) - - self.__cmd_execute(self.__CMD_PROGRAM_DONE) - - self.__cmd_execute(self.__CMD_REFRESH) - - if (self.__cmd_execute(self.__CMD_PROBE_FPGA) != self.__FPGA_PROBE_VALUE): - raise LCMXO2PrimerException('Invalid FPGA ID value received') - - except LCMXO2PrimerException as e: - self.__cmd_execute(self.__CMD_ENABLE_FLASH) - self.__cmd_execute(self.__CMD_ERASE_FLASH) - self.__cmd_execute(self.__CMD_REFRESH) - self.__cmd_execute(self.__CMD_RESTART) - raise LCMXO2PrimerException(e) - - self.__cmd_execute(self.__CMD_RESTART) - - -class SC64BringUp: - __SERIAL_BAUD: int = 115200 - __SERIAL_TIMEOUT: float = 6.0 - __INTERVAL_TIME: float = 0.5 - - def __init__(self, progress: Callable[[int, int, str], None]) -> None: - self.__progress = progress - - def load_update_data(self, path: str) -> None: - self.__sc64_update_data = SC64UpdateData() - self.__sc64_update_data.load(path, require_all=True) - - def get_update_info(self) -> str: - return self.__sc64_update_data.get_update_info() - - def start_bring_up(self, port: str) -> None: - link = None - try: - link = serial.Serial( - port, - baudrate=self.__SERIAL_BAUD, - parity=serial.PARITY_EVEN, - timeout=self.__SERIAL_TIMEOUT, - write_timeout=self.__SERIAL_TIMEOUT - ) - - stm32_bootloader = STM32Bootloader(link.write, link.read, self.__progress) - lcmxo2_primer = LCMXO2Primer(link.write, link.read, self.__progress) - - stm32_bootloader.connect(stm32_bootloader.DEV_ID_STM32G030XX) - stm32_bootloader.load_ram_and_run(self.__sc64_update_data.get_primer_data(), 'FPGA primer -> STM32 RAM') - time.sleep(self.__INTERVAL_TIME) - link.read_all() - - lcmxo2_primer.connect(lcmxo2_primer.DEV_ID_LCMXO2_7000HC) - lcmxo2_primer.load_flash_and_run(self.__sc64_update_data.get_fpga_data(), 'FPGA configuration -> LCMXO2 FLASH') - time.sleep(self.__INTERVAL_TIME) - link.read_all() - - stm32_bootloader.connect(stm32_bootloader.DEV_ID_STM32G030XX) - stm32_bootloader.load_flash_and_run(self.__sc64_update_data.get_mcu_data(), 'MCU software -> STM32 FLASH') - time.sleep(self.__INTERVAL_TIME) - link.read_all() - - sc64 = SC64() - bootloader_description = 'Bootloader -> SC64 FLASH' - bootloader_data = self.__sc64_update_data.get_bootloader_data() - bootloader_length = len(bootloader_data) - self.__progress(bootloader_length, 0, bootloader_description) - sc64.upload_bootloader(bootloader_data) - self.__progress(bootloader_length, bootloader_length, bootloader_description) - finally: - if (link and link.is_open): - link.close() - - -if __name__ == '__main__': - if (len(sys.argv) != 3): - Utils.die(f'Usage: {sys.argv[0]} serial_port update_file') - - port = sys.argv[1] - update_data_path = sys.argv[2] - utils = Utils() - sc64_bring_up = SC64BringUp(progress=utils.progress) - - Utils.log() - Utils.info('[ Welcome to SC64 flashcart board bring-up! ]') - Utils.log() - - Utils.log(f'Serial port: {port}') - Utils.log(f'Update data path: {os.path.abspath(update_data_path)}') - try: - sc64_bring_up.load_update_data(update_data_path) - except SC64UpdateDataException as e: - Utils.die(f'Provided \'{update_data_path}\' file is invalid: {e}') - Utils.log_no_end('Update info: ') - Utils.log(sc64_bring_up.get_update_info()) - Utils.log() - - Utils.warning('[ CAUTION ]') - Utils.warning('Configure FTDI chip with provided ft232h_config.xml before continuing') - Utils.warning('Connect SC64 USB port to the same computer you\'re running this script') - Utils.warning('Make sure SC64 USB port is recognized in system before continuing') - Utils.log() - - Utils.warning('[ IMPORTANT ]') - Utils.warning('Unplug board from power and reconnect it before proceeding') - Utils.log() - - try: - if (input('Type YES to continue: ') != 'YES'): - Utils.die('No confirmation received. Exiting') - Utils.log() - except KeyboardInterrupt: - Utils.log() - Utils.die('Aborted') - - original_sigint_handler = signal.getsignal(signal.SIGINT) - try: - signal.signal(signal.SIGINT, lambda *kwargs: utils.exit_warning()) - sc64_bring_up.start_bring_up(port) - except (serial.SerialException, STM32BootloaderException, LCMXO2PrimerException) as e: - if (utils.get_progress_active): - Utils.log() - Utils.die(f'Error while running bring-up: {e}') - finally: - signal.signal(signal.SIGINT, original_sigint_handler) - - Utils.log() - Utils.info('[ SC64 flashcart board bring-up finished successfully! ]') - Utils.log() +#!/usr/bin/env python3 + +import io +import os +import serial +import signal +import sys +import time +from binascii import crc32 +from sc64 import SC64 +from typing import Callable, Optional + + + +class Utils: + __progress_active = False + + @staticmethod + def log(message: str='') -> None: + print(message) + + @staticmethod + def log_no_end(message: str='') -> None: + print(message, end='', flush=True) + + @staticmethod + def info(message: str='') -> None: + print(f'\033[92m{message}\033[0m') + + @staticmethod + def warning(message: str='') -> None: + print(f'\033[93m{message}\033[0m') + + @staticmethod + def die(reason: str) -> None: + print(f'\033[91m{reason}\033[0m') + exit(-1) + + @property + def get_progress_active(self): + return self.__progress_active + + def progress(self, length: int, position: int, description: str) -> None: + value = ((position / length) * 100.0) + if (position == 0): + self.__progress_active = True + Utils.log_no_end(f'\r{value:5.1f}%: [{description}]') + if (position == length): + Utils.log() + self.__progress_active = False + + def exit_warning(self): + if (self.__progress_active): + Utils.log() + Utils.warning('Ctrl-C is prohibited during bring-up procedure') + + +class SC64UpdateDataException(Exception): + pass + +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 + __CHUNK_ID_PRIMER_DATA = 5 + + __update_info: Optional[str] + __mcu_data: Optional[bytes] + __fpga_data: Optional[bytes] + __bootloader_data: Optional[bytes] + __primer_data: Optional[bytes] + + def __load_int(self, f: io.BufferedReader) -> int: + try: + data = f.read(4) + if (len(data) != 4): + raise ValueError('Read size did not match requested amount') + value = int.from_bytes(data, byteorder='little') + except ValueError as e: + raise SC64UpdateDataException(f'Error while reading chunk header: {e}') + return value + + def __load_chunk(self, f: io.BufferedReader) -> tuple[int, bytes]: + id = self.__load_int(f) + aligned_length = self.__load_int(f) + checksum = self.__load_int(f) + data_length = self.__load_int(f) + + data = f.read(data_length) + + align = (aligned_length - 4 - 4 - data_length) + f.seek(align, io.SEEK_CUR) + + if (crc32(data) != checksum): + raise SC64UpdateDataException(f'Invalid checksum for chunk id [{id}] inside update file') + + return (id, data) + + def load(self, path: str, require_all: bool=False) -> None: + self.__update_info = None + self.__mcu_data = None + self.__fpga_data = None + self.__bootloader_data = None + self.__primer_data = None + + try: + with open(path, 'rb') as f: + if (f.read(len(self.__UPDATE_TOKEN)) != self.__UPDATE_TOKEN): + raise SC64UpdateDataException('Invalid update file header') + + while (f.peek(1) != b''): + (id, data) = self.__load_chunk(f) + if (id == self.__CHUNK_ID_UPDATE_INFO): + self.__update_info = data.decode('ascii') + elif (id == self.__CHUNK_ID_MCU_DATA): + self.__mcu_data = data + elif (id == self.__CHUNK_ID_FPGA_DATA): + self.__fpga_data = data + elif (id == self.__CHUNK_ID_BOOTLOADER_DATA): + self.__bootloader_data = data + elif (id == self.__CHUNK_ID_PRIMER_DATA): + self.__primer_data = data + else: + raise SC64UpdateDataException('Unknown chunk inside update file') + + if (require_all): + if (not self.__update_info): + raise SC64UpdateDataException('No update info inside update file') + if (not self.__mcu_data): + raise SC64UpdateDataException('No MCU data inside update file') + if (not self.__fpga_data): + raise SC64UpdateDataException('No FPGA data inside update file') + if (not self.__bootloader_data): + raise SC64UpdateDataException('No bootloader data inside update file') + if (not self.__primer_data): + raise SC64UpdateDataException('No primer data inside update file') + + except IOError as e: + raise SC64UpdateDataException(f'IO error while loading update data: {e}') + + def get_update_info(self) -> Optional[str]: + return self.__update_info + + def get_mcu_data(self) -> Optional[bytes]: + return self.__mcu_data + + def get_fpga_data(self) -> Optional[bytes]: + return self.__fpga_data + + def get_bootloader_data(self) -> Optional[bytes]: + return self.__bootloader_data + + def get_primer_data(self) -> Optional[bytes]: + return self.__primer_data + + +class STM32BootloaderException(Exception): + pass + +class STM32Bootloader: + __INIT = b'\x7F' + __ACK = b'\x79' + __NACK = b'\x1F' + + __MEMORY_RW_MAX_SIZE = 256 + __FLASH_LOAD_ADDRESS = 0x08000000 + __FLASH_MAX_LOAD_SIZE = 0x8000 + __RAM_LOAD_ADDRESS = 0x20001000 + __RAM_MAX_LOAD_SIZE = 0x1000 + + DEV_ID_STM32G030XX = b'\x04\x66' + + __connected = False + + def __init__(self, write: Callable[[bytes], None], read: Callable[[int], bytes], progress: Callable[[int, int, str], None]): + self.__write = write + self.__read = read + self.__progress = progress + + def __append_xor(self, data: bytes) -> bytes: + xor = (0xFF if (len(data) == 1) else 0x00) + for b in data: + xor ^= b + return bytes([*data, xor]) + + def __check_ack(self) -> None: + response = self.__read(1) + if (response == None): + raise STM32BootloaderException('No ACK/NACK byte received') + if (response == self.__NACK): + raise STM32BootloaderException('NACK byte received') + if (response != self.__ACK): + raise STM32BootloaderException('Unknown ACK/NACK byte received') + + def __cmd_send(self, cmd: bytes) -> None: + if (len(cmd) != 1): + raise ValueError('Command must contain only one byte') + self.__write(self.__append_xor(cmd)) + self.__check_ack() + + def __data_write(self, data: bytes) -> None: + self.__write(self.__append_xor(data)) + self.__check_ack() + + def __data_read(self) -> bytes: + length = self.__read(1) + if (len(length) != 1): + raise STM32BootloaderException('Did not receive length byte') + length = length[0] + data = self.__read(length + 1) + self.__check_ack() + return data + + def __get_id(self) -> bytes: + self.__cmd_send(b'\x02') + return self.__data_read() + + def __read_memory(self, address: int, length: int) -> bytes: + if (length == 0 or length > self.__MEMORY_RW_MAX_SIZE): + raise ValueError('Wrong data size for read memory command') + self.__cmd_send(b'\x11') + self.__data_write(address.to_bytes(4, byteorder='big')) + self.__data_write(bytes([length - 1])) + return self.__read(length) + + def __go(self, address: int) -> None: + self.__cmd_send(b'\x21') + self.__data_write(address.to_bytes(4, byteorder='big')) + self.__connected = False + + def __write_memory(self, address: int, data: bytes) -> None: + length = len(data) + if (length == 0 or length > self.__MEMORY_RW_MAX_SIZE): + raise ValueError('Wrong data size for write memory command') + if (((address % 4) != 0) or ((length % 4) != 0)): + raise ValueError('Write memory command requires 4 byte alignment') + self.__cmd_send(b'\x31') + self.__data_write(address.to_bytes(4, byteorder='big')) + self.__data_write(bytes([length - 1, *data])) + + def __mass_erase(self) -> None: + self.__cmd_send(b'\x44') + self.__data_write(b'\xFF\xFF') + + def __load_memory(self, address: int, data: bytes, description: str='') -> None: + length = len(data) + self.__progress(length, 0, description) + for offset in range(0, length, self.__MEMORY_RW_MAX_SIZE): + chunk = data[offset:offset + self.__MEMORY_RW_MAX_SIZE] + self.__write_memory(address + offset, chunk) + verify = self.__read_memory(address + offset, len(chunk)) + if (chunk != verify): + raise STM32BootloaderException('Memory verify failed') + self.__progress(length, offset, description) + self.__progress(length, length, description) + + def connect(self, id: int) -> None: + if (not self.__connected): + self.__write(self.__INIT) + self.__check_ack() + self.__connected = True + dev_id = self.__get_id() + if (dev_id != id): + raise STM32BootloaderException('Unknown chip detected') + + def load_ram_and_run(self, data: bytes, description: str='') -> None: + if (len(data) > self.__RAM_MAX_LOAD_SIZE): + raise STM32BootloaderException('RAM image too big') + self.__load_memory(self.__RAM_LOAD_ADDRESS, data, description) + self.__go(self.__RAM_LOAD_ADDRESS) + + def load_flash_and_run(self, data: bytes, description: str='') -> None: + if (len(data) > self.__FLASH_MAX_LOAD_SIZE): + raise STM32BootloaderException('Flash image too big') + self.__mass_erase() + try: + self.__load_memory(self.__FLASH_LOAD_ADDRESS, data, description) + self.__go(self.__FLASH_LOAD_ADDRESS) + except STM32BootloaderException as e: + self.__mass_erase() + raise STM32BootloaderException(e) + + +class LCMXO2PrimerException(Exception): + pass + +class LCMXO2Primer: + __PRIMER_ID_LCMXO2 = b'MXO2' + + __CMD_GET_PRIMER_ID = b'?' + __CMD_PROBE_FPGA = b'#' + __CMD_RESTART = b'$' + __CMD_GET_DEVICE_ID = b'I' + __CMD_ENABLE_FLASH = b'E' + __CMD_ERASE_FLASH = b'X' + __CMD_RESET_ADDRESS = b'A' + __CMD_WRITE_PAGE = b'W' + __CMD_READ_PAGE = b'R' + __CMD_PROGRAM_DONE = b'F' + __CMD_INIT_FEATBITS = b'Q' + __CMD_REFRESH = b'B' + + __FLASH_PAGE_SIZE = 16 + __FLASH_NUM_PAGES = 11260 + + __FPGA_PROBE_VALUE = b'\x64' + + DEV_ID_LCMXO2_7000HC = b'\x01\x2B\xD0\x43' + + def __init__(self, write: Callable[[bytes], None], read: Callable[[int], bytes], progress: Callable[[int, int, str], None]): + self.__write = write + self.__read = read + self.__progress = progress + + def __cmd_execute(self, cmd: bytes, data: bytes=b'') -> bytes: + if (len(cmd) != 1): + raise ValueError('Command must contain only one byte') + if (len(data) >= 256): + raise ValueError('Data size too big') + + packet = b'CMD' + cmd + packet += len(data).to_bytes(1, byteorder='little') + packet += data + packet += crc32(packet).to_bytes(4, byteorder='little') + self.__write(packet) + + response = self.__read(5) + if (len(response) != 5): + raise LCMXO2PrimerException(f'No response received [{cmd}]') + length = int.from_bytes(response[4:5], byteorder='little') + response += self.__read(length) + calculated_checksum = crc32(response) + received_checksum = int.from_bytes(self.__read(4), byteorder='little') + + if (response[0:3] != b'RSP'): + raise LCMXO2PrimerException(f'Invalid response token [{response[0:3]} / {cmd}]') + if (response[3:4] != cmd): + raise LCMXO2PrimerException(f'Invalid response command [{cmd} / {response[3]}]') + if (calculated_checksum != received_checksum): + raise LCMXO2PrimerException(f'Invalid response checksum [{cmd}]') + + return response[5:] + + def connect(self, id: bytes) -> None: + primer_id = self.__cmd_execute(self.__CMD_GET_PRIMER_ID) + if (primer_id != self.__PRIMER_ID_LCMXO2): + raise LCMXO2PrimerException('Invalid primer ID received') + + dev_id = self.__cmd_execute(self.__CMD_GET_DEVICE_ID) + if (dev_id != id): + raise LCMXO2PrimerException('Invalid FPGA device id received') + + def load_flash_and_run(self, data: bytes, description: str) -> None: + erase_description = f'{description} / Erase' + program_description = f'{description} / Program' + verify_description = f'{description} / Verify' + + length = len(data) + if (length > (self.__FLASH_PAGE_SIZE * self.__FLASH_NUM_PAGES)): + raise LCMXO2PrimerException('FPGA data size too big') + if ((length % self.__FLASH_PAGE_SIZE) != 0): + raise LCMXO2PrimerException('FPGA data size not aligned to page size') + + self.__cmd_execute(self.__CMD_ENABLE_FLASH) + + self.__progress(length, 0, erase_description) + self.__cmd_execute(self.__CMD_ERASE_FLASH) + self.__progress(length, length, erase_description) + + try: + self.__cmd_execute(self.__CMD_RESET_ADDRESS) + self.__progress(length, 0, program_description) + for offset in range(0, length, self.__FLASH_PAGE_SIZE): + page_data = data[offset:(offset + self.__FLASH_PAGE_SIZE)] + self.__cmd_execute(self.__CMD_WRITE_PAGE, page_data) + self.__progress(length, offset, program_description) + self.__progress(length, length, program_description) + + self.__cmd_execute(self.__CMD_RESET_ADDRESS) + self.__progress(length, 0, verify_description) + for offset in range(0, length, self.__FLASH_PAGE_SIZE): + page_data = data[offset:(offset + self.__FLASH_PAGE_SIZE)] + verify_data = self.__cmd_execute(self.__CMD_READ_PAGE) + self.__progress(length, offset, verify_description) + if (page_data != verify_data): + raise LCMXO2PrimerException('FPGA verification error') + self.__progress(length, length, verify_description) + + self.__cmd_execute(self.__CMD_INIT_FEATBITS) + + self.__cmd_execute(self.__CMD_PROGRAM_DONE) + + self.__cmd_execute(self.__CMD_REFRESH) + + if (self.__cmd_execute(self.__CMD_PROBE_FPGA) != self.__FPGA_PROBE_VALUE): + raise LCMXO2PrimerException('Invalid FPGA ID value received') + + except LCMXO2PrimerException as e: + self.__cmd_execute(self.__CMD_ENABLE_FLASH) + self.__cmd_execute(self.__CMD_ERASE_FLASH) + self.__cmd_execute(self.__CMD_REFRESH) + self.__cmd_execute(self.__CMD_RESTART) + raise LCMXO2PrimerException(e) + + self.__cmd_execute(self.__CMD_RESTART) + + +class SC64BringUp: + __SERIAL_BAUD: int = 115200 + __SERIAL_TIMEOUT: float = 6.0 + __INTERVAL_TIME: float = 0.5 + + def __init__(self, progress: Callable[[int, int, str], None]) -> None: + self.__progress = progress + + def load_update_data(self, path: str) -> None: + self.__sc64_update_data = SC64UpdateData() + self.__sc64_update_data.load(path, require_all=True) + + def get_update_info(self) -> str: + return self.__sc64_update_data.get_update_info() + + def start_bring_up(self, port: str) -> None: + link = None + try: + link = serial.Serial( + port, + baudrate=self.__SERIAL_BAUD, + parity=serial.PARITY_EVEN, + timeout=self.__SERIAL_TIMEOUT, + write_timeout=self.__SERIAL_TIMEOUT + ) + + stm32_bootloader = STM32Bootloader(link.write, link.read, self.__progress) + lcmxo2_primer = LCMXO2Primer(link.write, link.read, self.__progress) + + stm32_bootloader.connect(stm32_bootloader.DEV_ID_STM32G030XX) + stm32_bootloader.load_ram_and_run(self.__sc64_update_data.get_primer_data(), 'FPGA primer -> STM32 RAM') + time.sleep(self.__INTERVAL_TIME) + link.read_all() + + lcmxo2_primer.connect(lcmxo2_primer.DEV_ID_LCMXO2_7000HC) + lcmxo2_primer.load_flash_and_run(self.__sc64_update_data.get_fpga_data(), 'FPGA configuration -> LCMXO2 FLASH') + time.sleep(self.__INTERVAL_TIME) + link.read_all() + + stm32_bootloader.connect(stm32_bootloader.DEV_ID_STM32G030XX) + stm32_bootloader.load_flash_and_run(self.__sc64_update_data.get_mcu_data(), 'MCU software -> STM32 FLASH') + time.sleep(self.__INTERVAL_TIME) + link.read_all() + + sc64 = SC64() + bootloader_description = 'Bootloader -> SC64 FLASH' + bootloader_data = self.__sc64_update_data.get_bootloader_data() + bootloader_length = len(bootloader_data) + self.__progress(bootloader_length, 0, bootloader_description) + sc64.upload_bootloader(bootloader_data) + self.__progress(bootloader_length, bootloader_length, bootloader_description) + finally: + if (link and link.is_open): + link.close() + + +if __name__ == '__main__': + if (len(sys.argv) != 3): + Utils.die(f'Usage: {sys.argv[0]} serial_port update_file') + + port = sys.argv[1] + update_data_path = sys.argv[2] + utils = Utils() + sc64_bring_up = SC64BringUp(progress=utils.progress) + + Utils.log() + Utils.info('[ Welcome to SC64 flashcart board bring-up! ]') + Utils.log() + + Utils.log(f'Serial port: {port}') + Utils.log(f'Update data path: {os.path.abspath(update_data_path)}') + try: + sc64_bring_up.load_update_data(update_data_path) + except SC64UpdateDataException as e: + Utils.die(f'Provided \'{update_data_path}\' file is invalid: {e}') + Utils.log_no_end('Update info: ') + Utils.log(sc64_bring_up.get_update_info()) + Utils.log() + + Utils.warning('[ CAUTION ]') + Utils.warning('Configure FTDI chip with provided ft232h_config.xml before continuing') + Utils.warning('Connect SC64 USB port to the same computer you\'re running this script') + Utils.warning('Make sure SC64 USB port is recognized in system before continuing') + Utils.log() + + Utils.warning('[ IMPORTANT ]') + Utils.warning('Unplug SC64 board from power and reconnect it before proceeding') + Utils.log() + + try: + if (input('Type YES to continue: ') != 'YES'): + Utils.die('No confirmation received. Exiting') + Utils.log() + except KeyboardInterrupt: + Utils.log() + Utils.die('Aborted') + + original_sigint_handler = signal.getsignal(signal.SIGINT) + try: + signal.signal(signal.SIGINT, lambda *kwargs: utils.exit_warning()) + sc64_bring_up.start_bring_up(port) + except (serial.SerialException, STM32BootloaderException, LCMXO2PrimerException) as e: + if (utils.get_progress_active): + Utils.log() + Utils.die(f'Error while running bring-up: {e}') + finally: + signal.signal(signal.SIGINT, original_sigint_handler) + + Utils.log() + Utils.info('[ SC64 flashcart board bring-up finished successfully! ]') + Utils.log() diff --git a/sw/pc/sc64.py b/sw/pc/sc64.py index 62ef329..d097c43 100755 --- a/sw/pc/sc64.py +++ b/sw/pc/sc64.py @@ -223,7 +223,7 @@ class SC64: EEPROM_16K = (2 * 1024) SRAM = (32 * 1024) FLASHRAM = (128 * 1024) - SRAM_3X = (3 * 32 * 1024) + SRAM_BANKED = (3 * 32 * 1024) class __CfgId(IntEnum): BOOTLOADER_SWITCH = 0 @@ -290,7 +290,7 @@ class SC64: EEPROM_16K = 2 SRAM = 3 FLASHRAM = 4 - SRAM_3X = 5 + SRAM_BANKED = 5 class CICSeed(IntEnum): DEFAULT = 0x3F