mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-25 15:16:53 +01:00
small cleanup, rpi testing
This commit is contained in:
parent
cbd7acdc50
commit
6b04dc3f56
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
454
sw/pc/dd64.py
Normal file → Executable 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
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
456
sw/update/update.py
Normal file → Executable 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user