small cleanup, rpi testing

This commit is contained in:
Polprzewodnikowy 2022-08-29 01:15:39 +02:00
parent cbd7acdc50
commit 6b04dc3f56
7 changed files with 1214 additions and 1200 deletions

View File

@ -18,12 +18,12 @@ void init (void) {
exception_enable_watchdog(); exception_enable_watchdog();
exception_enable_interrupts(); exception_enable_interrupts();
sc64_init();
if (test_check()) { if (test_check()) {
exception_disable_watchdog(); exception_disable_watchdog();
test_execute(); test_execute();
} }
sc64_set_config(CFG_ID_BOOTLOADER_SWITCH, false);
} }
void deinit (void) { void deinit (void) {

View File

@ -6,15 +6,15 @@
typedef enum { typedef enum {
SC64_CMD_GET_VERSION = 'v', SC64_CMD_VERSION_GET = 'v',
SC64_CMD_CONFIG_QUERY = 'c', SC64_CMD_CONFIG_SET = 'C',
SC64_CMD_CONFIG_CHANGE = 'C', SC64_CMD_CONFIG_GET = 'c',
SC64_CMD_TIME_GET = 't',
SC64_CMD_TIME_SET = 'T', SC64_CMD_TIME_SET = 'T',
SC64_CMD_USB_READ = 'm', SC64_CMD_TIME_GET = 't',
SC64_CMD_USB_WRITE_STATUS = 'U',
SC64_CMD_USB_WRITE = 'M', SC64_CMD_USB_WRITE = 'M',
SC64_CMD_USB_READ_STATUS = 'u', SC64_CMD_USB_READ_STATUS = 'u',
SC64_CMD_USB_WRITE_STATUS = 'U', SC64_CMD_USB_READ = 'm',
} cmd_id_t; } cmd_id_t;
@ -45,26 +45,37 @@ bool sc64_check_presence (void) {
return (version == SC64_VERSION_2); return (version == SC64_VERSION_2);
} }
void sc64_init (void) { cmd_error_t sc64_get_error (void) {
sc64_change_config(CFG_ID_BOOTLOADER_SWITCH, false); if (pi_io_read(&SC64_REGS->SR_CMD) & SC64_SR_CMD_ERROR) {
return (cmd_error_t) pi_io_read(&SC64_REGS->DATA[0]);
}
return CMD_OK;
} }
uint32_t sc64_query_config (cfg_id_t id) { void sc64_set_config (cfg_id_t id, uint32_t value) {
uint32_t args[2] = { id, value };
sc64_execute_cmd(SC64_CMD_CONFIG_SET, args, NULL);
}
uint32_t sc64_get_config (cfg_id_t id) {
uint32_t args[2] = { id, 0 }; uint32_t args[2] = { id, 0 };
uint32_t result[2]; uint32_t result[2];
sc64_execute_cmd(SC64_CMD_CONFIG_QUERY, args, result); sc64_execute_cmd(SC64_CMD_CONFIG_GET, args, result);
return result[1]; return result[1];
} }
void sc64_change_config (cfg_id_t id, uint32_t value) { void sc64_get_boot_info (sc64_boot_info_t *info) {
uint32_t args[2] = { id, value }; info->cic_seed = (uint16_t) sc64_get_config(CFG_ID_CIC_SEED);
sc64_execute_cmd(SC64_CMD_CONFIG_CHANGE, args, NULL); 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);
} }
void sc64_get_boot_info (sc64_boot_info_t *info) { void sc64_set_time (rtc_time_t *t) {
info->cic_seed = (uint16_t) sc64_query_config(CFG_ID_CIC_SEED); uint32_t args[2] = {
info->tv_type = (tv_type_t) sc64_query_config(CFG_ID_TV_TYPE); ((t->hour << 16) | (t->minute << 8) | t->second),
info->boot_mode = (boot_mode_t) sc64_query_config(CFG_ID_BOOT_MODE); ((t->weekday << 24) | (t->year << 16) | (t->month << 8) | t->day),
};
sc64_execute_cmd(SC64_CMD_TIME_SET, args, NULL);
} }
void sc64_get_time (rtc_time_t *t) { void sc64_get_time (rtc_time_t *t) {
@ -79,14 +90,6 @@ void sc64_get_time (rtc_time_t *t) {
t->year = ((result[1] >> 16) & 0xFF); t->year = ((result[1] >> 16) & 0xFF);
} }
void sc64_set_time (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),
};
sc64_execute_cmd(SC64_CMD_TIME_SET, args, NULL);
}
bool sc64_usb_write_ready (void) { bool sc64_usb_write_ready (void) {
uint32_t result[2]; uint32_t result[2];
sc64_execute_cmd(SC64_CMD_USB_WRITE_STATUS, NULL, result); sc64_execute_cmd(SC64_CMD_USB_WRITE_STATUS, NULL, result);

View File

@ -6,6 +6,14 @@
#include <stdint.h> #include <stdint.h>
typedef enum {
CMD_OK = 0,
CMD_ERROR_BAD_ADDRESS = 1,
CMD_ERROR_BAD_CONFIG_ID = 2,
CMD_ERROR_TIMEOUT = 3,
CMD_ERROR_UNKNOWN_CMD = -1,
} cmd_error_t;
typedef enum { typedef enum {
CFG_ID_BOOTLOADER_SWITCH, CFG_ID_BOOTLOADER_SWITCH,
CFG_ID_ROM_WRITE_ENABLE, CFG_ID_ROM_WRITE_ENABLE,
@ -84,14 +92,14 @@ typedef struct {
bool sc64_check_presence (void); bool sc64_check_presence (void);
void sc64_init (void); cmd_error_t sc64_get_error (void);
uint32_t sc64_query_config (cfg_id_t id); void sc64_set_config (cfg_id_t id, uint32_t value);
void sc64_change_config (cfg_id_t id, uint32_t value); uint32_t sc64_get_config (cfg_id_t id);
void sc64_get_boot_info (sc64_boot_info_t *info); 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_set_time (rtc_time_t *t);
bool sc64_write_usb_ready (void); void sc64_get_time (rtc_time_t *t);
bool sc64_write_usb (uint32_t *address, uint32_t length); bool sc64_usb_write_ready (void);
bool sc64_usb_write (uint32_t *address, uint32_t length);
bool sc64_usb_read_ready (uint8_t *type, uint32_t *length); bool sc64_usb_read_ready (uint8_t *type, uint32_t *length);
bool sc64_usb_read (uint32_t *address, uint32_t length); bool sc64_usb_read (uint32_t *address, uint32_t length);

View File

@ -5,7 +5,7 @@
bool test_check (void) { bool test_check (void) {
if (sc64_query_config(CFG_ID_BUTTON_STATE)) { if (sc64_get_config(CFG_ID_BUTTON_STATE)) {
return true; return true;
} }
return false; return false;

454
sw/pc/dd64.py Normal file → Executable file
View File

@ -1,227 +1,227 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
from io import BufferedReader from io import BufferedReader
from typing import Optional from typing import Optional
class BadBlockError(Exception): class BadBlockError(Exception):
pass pass
class DD64Image: class DD64Image:
__DISK_HEADS = 2 __DISK_HEADS = 2
__DISK_TRACKS = 1175 __DISK_TRACKS = 1175
__DISK_BLOCKS_PER_TRACK = 2 __DISK_BLOCKS_PER_TRACK = 2
__DISK_SECTORS_PER_BLOCK = 85 __DISK_SECTORS_PER_BLOCK = 85
__DISK_BAD_TRACKS_PER_ZONE = 12 __DISK_BAD_TRACKS_PER_ZONE = 12
__DISK_SYSTEM_SECTOR_SIZE = 232 __DISK_SYSTEM_SECTOR_SIZE = 232
__DISK_ZONES = [ __DISK_ZONES = [
(0, 232, 158, 0), (0, 232, 158, 0),
(0, 216, 158, 158), (0, 216, 158, 158),
(0, 208, 149, 316), (0, 208, 149, 316),
(0, 192, 149, 465), (0, 192, 149, 465),
(0, 176, 149, 614), (0, 176, 149, 614),
(0, 160, 149, 763), (0, 160, 149, 763),
(0, 144, 149, 912), (0, 144, 149, 912),
(0, 128, 114, 1061), (0, 128, 114, 1061),
(1, 216, 158, 157), (1, 216, 158, 157),
(1, 208, 158, 315), (1, 208, 158, 315),
(1, 192, 149, 464), (1, 192, 149, 464),
(1, 176, 149, 613), (1, 176, 149, 613),
(1, 160, 149, 762), (1, 160, 149, 762),
(1, 144, 149, 911), (1, 144, 149, 911),
(1, 128, 149, 1060), (1, 128, 149, 1060),
(1, 112, 114, 1174), (1, 112, 114, 1174),
] ]
__DISK_VZONE_TO_PZONE = [ __DISK_VZONE_TO_PZONE = [
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10], [0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11], [0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12], [0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13], [0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14], [0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15], [0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8], [0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
] ]
__DISK_DRIVE_TYPES = [( __DISK_DRIVE_TYPES = [(
'development', 'development',
192, 192,
[11, 10, 3, 2], [11, 10, 3, 2],
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23], [0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
), ( ), (
'retail', 'retail',
232, 232,
[9, 8, 1, 0], [9, 8, 1, 0],
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23], [2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
)] )]
__file: Optional[BufferedReader] __file: Optional[BufferedReader]
__drive_type: Optional[str] __drive_type: Optional[str]
__block_info_table: list[tuple[int, int]] __block_info_table: list[tuple[int, int]]
loaded: bool = False loaded: bool = False
def __init__(self) -> None: def __init__(self) -> None:
self.__file = None self.__file = None
self.__drive_type = None self.__drive_type = None
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
self.__block_info_table = [None] * block_info_table_length self.__block_info_table = [None] * block_info_table_length
def __del__(self) -> None: def __del__(self) -> None:
self.unload() self.unload()
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]: def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK) self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK) system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
system_data = system_block_data[:sector_size] system_data = system_block_data[:sector_size]
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK): for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
sector_data = system_block_data[(sector * sector_size):][:sector_size] sector_data = system_block_data[(sector * sector_size):][:sector_size]
if (system_data != sector_data): if (system_data != sector_data):
return (False, None) return (False, None)
if (check_disk_type): if (check_disk_type):
if (system_data[4] != 0x10): if (system_data[4] != 0x10):
return (False, None) return (False, None)
if ((system_data[5] & 0xF0) != 0x10): if ((system_data[5] & 0xF0) != 0x10):
return (False, None) return (False, None)
return (True, system_data) return (True, system_data)
def __parse_disk(self) -> None: def __parse_disk(self) -> None:
disk_system_data = None disk_system_data = None
disk_id_data = None disk_id_data = None
disk_bad_lbas = [] disk_bad_lbas = []
drive_index = 0 drive_index = 0
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)): while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index] (drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
disk_bad_lbas.clear() disk_bad_lbas.clear()
disk_bad_lbas.extend(bad_lbas) disk_bad_lbas.extend(bad_lbas)
for system_lba in system_data_lbas: for system_lba in system_data_lbas:
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True) (valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
if (valid): if (valid):
self.__drive_type = drive_type self.__drive_type = drive_type
disk_system_data = system_data disk_system_data = system_data
else: else:
disk_bad_lbas.append(system_lba) disk_bad_lbas.append(system_lba)
drive_index += 1 drive_index += 1
for id_lba in [15, 14]: for id_lba in [15, 14]:
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False) (valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
if (valid): if (valid):
disk_id_data = id_data disk_id_data = id_data
else: else:
disk_bad_lbas.append(id_lba) disk_bad_lbas.append(id_lba)
if not (disk_system_data and disk_id_data): if not (disk_system_data and disk_id_data):
raise ValueError('Provided 64DD disk file is not valid') raise ValueError('Provided 64DD disk file is not valid')
disk_zone_bad_tracks = [] disk_zone_bad_tracks = []
for zone in range(len(self.__DISK_ZONES)): for zone in range(len(self.__DISK_ZONES)):
zone_bad_tracks = [] zone_bad_tracks = []
start = 0 if zone == 0 else system_data[0x07 + zone] start = 0 if zone == 0 else system_data[0x07 + zone]
stop = system_data[0x07 + zone + 1] stop = system_data[0x07 + zone + 1]
for offset in range(start, stop): for offset in range(start, stop):
zone_bad_tracks.append(system_data[0x20 + offset]) zone_bad_tracks.append(system_data[0x20 + offset])
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)): for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1) zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
disk_zone_bad_tracks.append(zone_bad_tracks) disk_zone_bad_tracks.append(zone_bad_tracks)
disk_type = disk_system_data[5] & 0x0F disk_type = disk_system_data[5] & 0x0F
current_lba = 0 current_lba = 0
starting_block = 0 starting_block = 0
disk_file_offset = 0 disk_file_offset = 0
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]: for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone] (head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
for zone_track in range(tracks): for zone_track in range(tracks):
current_zone_track = ( current_zone_track = (
(tracks - 1) - zone_track) if head else zone_track (tracks - 1) - zone_track) if head else zone_track
if (current_zone_track in disk_zone_bad_tracks[zone]): if (current_zone_track in disk_zone_bad_tracks[zone]):
track += (-1) if head else 1 track += (-1) if head else 1
continue continue
for block in range(self.__DISK_BLOCKS_PER_TRACK): for block in range(self.__DISK_BLOCKS_PER_TRACK):
index = (track << 2) | (head << 1) | (starting_block ^ block) index = (track << 2) | (head << 1) | (starting_block ^ block)
if (current_lba not in disk_bad_lbas): if (current_lba not in disk_bad_lbas):
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK) self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
else: else:
self.__block_info_table[index] = None self.__block_info_table[index] = None
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
current_lba += 1 current_lba += 1
track += (-1) if head else 1 track += (-1) if head else 1
starting_block ^= 1 starting_block ^= 1
def __check_track_head_block(self, track: int, head: int, block: int) -> None: def __check_track_head_block(self, track: int, head: int, block: int) -> None:
if (track < 0 or track >= self.__DISK_TRACKS): if (track < 0 or track >= self.__DISK_TRACKS):
raise ValueError('Track outside of possible range') raise ValueError('Track outside of possible range')
if (head < 0 or head >= self.__DISK_HEADS): if (head < 0 or head >= self.__DISK_HEADS):
raise ValueError('Head outside of possible range') raise ValueError('Head outside of possible range')
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK): if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
raise ValueError('Block outside of possible range') raise ValueError('Block outside of possible range')
def __get_table_index(self, track: int, head: int, block: int) -> int: def __get_table_index(self, track: int, head: int, block: int) -> int:
return (track << 2) | (head << 1) | (block) return (track << 2) | (head << 1) | (block)
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]: def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
if (self.__file.closed): if (self.__file.closed):
return None return None
self.__check_track_head_block(track, head, block) self.__check_track_head_block(track, head, block)
index = self.__get_table_index(track, head, block) index = self.__get_table_index(track, head, block)
return self.__block_info_table[index] return self.__block_info_table[index]
def load(self, path: str) -> None: def load(self, path: str) -> None:
self.unload() self.unload()
self.__file = open(path, 'rb+') self.__file = open(path, 'rb+')
self.__parse_disk() self.__parse_disk()
self.loaded = True self.loaded = True
def unload(self) -> None: def unload(self) -> None:
self.loaded = False self.loaded = False
if (self.__file != None and not self.__file.closed): if (self.__file != None and not self.__file.closed):
self.__file.close() self.__file.close()
self.__drive_type = None self.__drive_type = None
def get_drive_type(self) -> str: def get_drive_type(self) -> str:
return self.__drive_type return self.__drive_type
def read_block(self, track: int, head: int, block: int) -> bytes: def read_block(self, track: int, head: int, block: int) -> bytes:
info = self.__get_block_info(track, head, block) info = self.__get_block_info(track, head, block)
if (info == None): if (info == None):
raise BadBlockError raise BadBlockError
(offset, block_size) = info (offset, block_size) = info
self.__file.seek(offset) self.__file.seek(offset)
return self.__file.read(block_size) return self.__file.read(block_size)
def write_block(self, track: int, head: int, block: int, data: bytes) -> None: def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
info = self.__get_block_info(track, head, block) info = self.__get_block_info(track, head, block)
if (info == None): if (info == None):
raise BadBlockError raise BadBlockError
(offset, block_size) = info (offset, block_size) = info
if (len(data) != block_size): if (len(data) != block_size):
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})') raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
self.__file.seek(offset) self.__file.seek(offset)
self.__file.write(data) self.__file.write(data)
if __name__ == '__main__': if __name__ == '__main__':
id_lba_locations = [ id_lba_locations = [
(7, 0, 1), (7, 0, 1),
(7, 0, 0) (7, 0, 0)
] ]
if (len(sys.argv) >= 2): if (len(sys.argv) >= 2):
dd = DD64Image() dd = DD64Image()
dd.load(sys.argv[1]) dd.load(sys.argv[1])
print(dd.get_drive_type()) print(dd.get_drive_type())
for (track, head, block) in id_lba_locations: for (track, head, block) in id_lba_locations:
try: try:
print(dd.read_block(track, head, block)[:4]) print(dd.read_block(track, head, block)[:4])
except BadBlockError: except BadBlockError:
print(f'Bad ID block [track: {track}, head: {head}, block: {block}]') print(f'Bad ID block [track: {track}, head: {head}, block: {block}]')
dd.unload() dd.unload()
else: else:
print(f'[{sys.argv[0]}]: Expected disk image path as first argument') print(f'[{sys.argv[0]}]: Expected disk image path as first argument')

1425
sw/pc/sc64.py Normal file → Executable file

File diff suppressed because it is too large Load Diff

456
sw/update/update.py Normal file → Executable file
View File

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