From fc42292af0b56f0a64c90a1af79cec96f5fee319 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Tue, 21 Feb 2023 20:45:51 +0100 Subject: [PATCH] [SC64][DOCS][SW] Adjusted documentation / merged dd64.py into sc64.py --- build.sh | 7 +- docs/00_quick_startup_guide.md | 4 +- docs/06_manufacturing_guidelines.md | 10 +- sw/pc/dd64.py | 236 ---------------------------- sw/pc/sc64.py | 206 +++++++++++++++++++++++- 5 files changed, 216 insertions(+), 247 deletions(-) delete mode 100755 sw/pc/dd64.py diff --git a/build.sh b/build.sh index 3087b3a..c3583d1 100755 --- a/build.sh +++ b/build.sh @@ -5,11 +5,10 @@ set -e PACKAGE_FILE_NAME="SC64" TOP_FILES=( - "./sw/pc/dd64.py" "./sw/pc/primer.py" "./sw/pc/requirements.txt" "./sw/pc/sc64.py" - "./sw/update/sc64_update_package.bin" + "./sw/update/sc64_firmware.bin" ) FILES=( @@ -87,7 +86,7 @@ build_update () { pushd sw/update > /dev/null if [ "$FORCE_CLEAN" = true ]; then - rm -f ./sc64_update_package.bin + rm -f ./sc64_firmware.bin fi GIT_INFO="" if [ ! -z "${GIT_BRANCH}" ]; then GIT_INFO+="branch: [$GIT_BRANCH] "; fi @@ -101,7 +100,7 @@ build_update () { --fpga ../../fw/project/lcmxo2/impl1/sc64_impl1.jed \ --boot ../bootloader/build/bootloader.bin \ --primer ../controller/build/primer/primer.bin \ - sc64_update_package.bin + sc64_firmware.bin popd > /dev/null BUILT_UPDATE=true diff --git a/docs/00_quick_startup_guide.md b/docs/00_quick_startup_guide.md index 7bcb35f..499e04a 100644 --- a/docs/00_quick_startup_guide.md +++ b/docs/00_quick_startup_guide.md @@ -24,9 +24,9 @@ Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version. -To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_backup_package.bin` +To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_firmware_backup.bin` -To update SC64 firmware run `python3 sc64.py --update-firmware sc64_update_package.bin` +To update SC64 firmware run `python3 sc64.py --update-firmware sc64_firmware.bin` --- diff --git a/docs/06_manufacturing_guidelines.md b/docs/06_manufacturing_guidelines.md index a44adf3..4bfbf2a 100644 --- a/docs/06_manufacturing_guidelines.md +++ b/docs/06_manufacturing_guidelines.md @@ -57,6 +57,8 @@ There are no special requirements for soldering components to board. All chips a ### **Initial programming** +**Please read the following instructions carefully before proceeding with programming.** + For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher). As for software here's list of required applications: @@ -83,11 +85,11 @@ Second, program FPGA, microcontroller and Flash memory: 4. Check in device manager which port number `COMx` is assigned to serial adapter 5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter) 6. Locate `primer.py` script in root folder - 7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `dd64.py`, `sc64_update_package.bin` + 7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `sc64_firmware.bin` 8. Run `pip3 install -r requirements.txt` to install required python packages - 9. Run `python3 primer.py COMx sc64_update_package.bin` (replace `COMx` with port located in step **4**) + 9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**) 10. Follow the instructions on the screen - 11. Wait until programming process has finished + 11. Wait until programming process has finished (**DO NOT STOP PROGRAMMING PROCESS OR DISCONNECT SC64 BOARD FROM PC**, doing so might irrecoverably break programming through UART header and you would need to program microcontroller, FPGA and bootloader with separate dedicated programming interfaces through *Tag-Connect* connector on the PCB) Congratulations! Your SC64 flashcart should be ready for use! @@ -97,4 +99,4 @@ Congratulations! Your SC64 flashcart should be ready for use! *`primer.py` threw error on `Bootloader -> SC64 FLASH` step* -This issue can be attributed to incorrectly programmed FT232H EEPROM in the first programming step. Check again in `FT_PROG` program if device was configured properly. Once FPGA and microcontroller has been programmed successfully `primer.py` script needs to be run in special mode. Please use command `python3 primer.py COMx sc64_update_package.bin --only-bootloader` to try programming bootloader again. +This issue can be attributed to incorrectly programmed FT232H EEPROM in the first programming step. Check again in `FT_PROG` program if device was configured properly. Once FPGA and microcontroller has been programmed successfully `primer.py` script needs to be run in special mode. Please use command `python3 primer.py COMx sc64_firmware.bin --only-bootloader` to try programming bootloader again. diff --git a/sw/pc/dd64.py b/sw/pc/dd64.py deleted file mode 100755 index d16aac3..0000000 --- a/sw/pc/dd64.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from io import BufferedReader -from typing import Optional - - - -class BadBlockError(Exception): - pass - - -class DD64Image: - __DISK_HEADS = 2 - __DISK_TRACKS = 1175 - __DISK_BLOCKS_PER_TRACK = 2 - __DISK_SECTORS_PER_BLOCK = 85 - __DISK_BAD_TRACKS_PER_ZONE = 12 - __DISK_SYSTEM_SECTOR_SIZE = 232 - __DISK_ZONES = [ - (0, 232, 158, 0), - (0, 216, 158, 158), - (0, 208, 149, 316), - (0, 192, 149, 465), - (0, 176, 149, 614), - (0, 160, 149, 763), - (0, 144, 149, 912), - (0, 128, 114, 1061), - (1, 216, 158, 157), - (1, 208, 158, 315), - (1, 192, 149, 464), - (1, 176, 149, 613), - (1, 160, 149, 762), - (1, 144, 149, 911), - (1, 128, 149, 1060), - (1, 112, 114, 1174), - ] - __DISK_VZONE_TO_PZONE = [ - [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, 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, 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, 15, 14, 13, 12, 11, 10, 9, 8], - ] - __DISK_DRIVE_TYPES = [( - 'development', - 192, - [11, 10, 3, 2], - [0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23], - ), ( - 'retail', - 232, - [9, 8, 1, 0], - [2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23], - )] - - __file: Optional[BufferedReader] - __drive_type: Optional[str] - __block_info_table: list[tuple[int, int]] - loaded: bool = False - - def __init__(self) -> None: - self.__file = None - self.__drive_type = None - block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK - self.__block_info_table = [None] * block_info_table_length - - def __del__(self) -> None: - self.unload() - - 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) - system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK) - system_data = system_block_data[:sector_size] - for sector in range(1, self.__DISK_SECTORS_PER_BLOCK): - sector_data = system_block_data[(sector * sector_size):][:sector_size] - if (system_data != sector_data): - return (False, None) - if (check_disk_type): - if (system_data[4] != 0x10): - return (False, None) - if ((system_data[5] & 0xF0) != 0x10): - return (False, None) - return (True, system_data) - - def __parse_disk(self) -> None: - disk_system_data = None - disk_id_data = None - disk_bad_lbas = [] - - drive_index = 0 - 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] - disk_bad_lbas.clear() - disk_bad_lbas.extend(bad_lbas) - for system_lba in system_data_lbas: - (valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True) - if (valid): - self.__drive_type = drive_type - disk_system_data = system_data - else: - disk_bad_lbas.append(system_lba) - drive_index += 1 - - for id_lba in [15, 14]: - (valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False) - if (valid): - disk_id_data = id_data - else: - disk_bad_lbas.append(id_lba) - - if not (disk_system_data and disk_id_data): - raise ValueError('Provided 64DD disk file is not valid') - - disk_zone_bad_tracks = [] - - for zone in range(len(self.__DISK_ZONES)): - zone_bad_tracks = [] - start = 0 if zone == 0 else system_data[0x07 + zone] - stop = system_data[0x07 + zone + 1] - for offset in range(start, stop): - zone_bad_tracks.append(system_data[0x20 + offset]) - 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) - disk_zone_bad_tracks.append(zone_bad_tracks) - - disk_type = disk_system_data[5] & 0x0F - - current_lba = 0 - starting_block = 0 - disk_file_offset = 0 - - for zone in self.__DISK_VZONE_TO_PZONE[disk_type]: - (head, sector_size, tracks, track) = self.__DISK_ZONES[zone] - - for zone_track in range(tracks): - current_zone_track = ( - (tracks - 1) - zone_track) if head else zone_track - - if (current_zone_track in disk_zone_bad_tracks[zone]): - track += (-1) if head else 1 - continue - - for block in range(self.__DISK_BLOCKS_PER_TRACK): - index = (track << 2) | (head << 1) | (starting_block ^ block) - if (current_lba not in disk_bad_lbas): - self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK) - else: - self.__block_info_table[index] = None - disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK - current_lba += 1 - - track += (-1) if head else 1 - starting_block ^= 1 - - def __check_track_head_block(self, track: int, head: int, block: int) -> None: - if (track < 0 or track >= self.__DISK_TRACKS): - raise ValueError('Track outside of possible range') - if (head < 0 or head >= self.__DISK_HEADS): - raise ValueError('Head outside of possible range') - if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK): - raise ValueError('Block outside of possible range') - - def __get_table_index(self, track: int, head: int, block: int) -> int: - return (track << 2) | (head << 1) | (block) - - def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]: - if (self.__file.closed): - return None - self.__check_track_head_block(track, head, block) - index = self.__get_table_index(track, head, block) - return self.__block_info_table[index] - - def load(self, path: str) -> None: - self.unload() - self.__file = open(path, 'rb+') - self.__parse_disk() - self.loaded = True - - def unload(self) -> None: - self.loaded = False - if (self.__file != None and not self.__file.closed): - self.__file.close() - self.__drive_type = None - - def get_block_info_table(self) -> list[tuple[int, int]]: - return self.__block_info_table - - def get_drive_type(self) -> str: - return self.__drive_type - - def read_block(self, track: int, head: int, block: int) -> bytes: - info = self.__get_block_info(track, head, block) - if (info == None): - raise BadBlockError - (offset, block_size) = info - self.__file.seek(offset) - return self.__file.read(block_size) - - def write_block(self, track: int, head: int, block: int, data: bytes) -> None: - info = self.__get_block_info(track, head, block) - if (info == None): - raise BadBlockError - (offset, block_size) = info - if (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.write(data) - - - -if __name__ == '__main__': - id_lba_locations = [ - (7, 0, 1), - (7, 0, 0) - ] - if (len(sys.argv) >= 2): - dd = DD64Image() - dd.load(sys.argv[1]) - print(dd.get_drive_type()) - for (track, head, block) in id_lba_locations: - try: - print(dd.read_block(track, head, block)[:4]) - except BadBlockError: - print(f'Bad ID block [track: {track}, head: {head}, block: {block}]') - if (len(sys.argv) >= 3): - with open(sys.argv[2], 'wb') as f: - block_info_table = dd.get_block_info_table() - for block in block_info_table: - offset = 0xFFFFFFFF if block == None else block[0] - f.write(offset.to_bytes(4, byteorder='big')) - dd.unload() - else: - print(f'[{sys.argv[0]}]: Expected disk image path as first argument') diff --git a/sw/pc/sc64.py b/sw/pc/sc64.py index 912b139..e352d29 100755 --- a/sw/pc/sc64.py +++ b/sw/pc/sc64.py @@ -9,8 +9,8 @@ import sys import time from binascii import crc32 from datetime import datetime -from dd64 import BadBlockError, DD64Image from enum import Enum, IntEnum +from io import BufferedReader from serial.tools import list_ports from threading import Thread from typing import Callable, Optional @@ -18,6 +18,210 @@ from PIL import Image +class BadBlockError(Exception): + pass + + +class DD64Image: + __DISK_HEADS = 2 + __DISK_TRACKS = 1175 + __DISK_BLOCKS_PER_TRACK = 2 + __DISK_SECTORS_PER_BLOCK = 85 + __DISK_BAD_TRACKS_PER_ZONE = 12 + __DISK_SYSTEM_SECTOR_SIZE = 232 + __DISK_ZONES = [ + (0, 232, 158, 0), + (0, 216, 158, 158), + (0, 208, 149, 316), + (0, 192, 149, 465), + (0, 176, 149, 614), + (0, 160, 149, 763), + (0, 144, 149, 912), + (0, 128, 114, 1061), + (1, 216, 158, 157), + (1, 208, 158, 315), + (1, 192, 149, 464), + (1, 176, 149, 613), + (1, 160, 149, 762), + (1, 144, 149, 911), + (1, 128, 149, 1060), + (1, 112, 114, 1174), + ] + __DISK_VZONE_TO_PZONE = [ + [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, 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, 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, 15, 14, 13, 12, 11, 10, 9, 8], + ] + __DISK_DRIVE_TYPES = [( + 'development', + 192, + [11, 10, 3, 2], + [0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23], + ), ( + 'retail', + 232, + [9, 8, 1, 0], + [2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23], + )] + + __file: Optional[BufferedReader] + __drive_type: Optional[str] + __block_info_table: list[tuple[int, int]] + loaded: bool = False + + def __init__(self) -> None: + self.__file = None + self.__drive_type = None + block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK + self.__block_info_table = [None] * block_info_table_length + + def __del__(self) -> None: + self.unload() + + 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) + system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK) + system_data = system_block_data[:sector_size] + for sector in range(1, self.__DISK_SECTORS_PER_BLOCK): + sector_data = system_block_data[(sector * sector_size):][:sector_size] + if (system_data != sector_data): + return (False, None) + if (check_disk_type): + if (system_data[4] != 0x10): + return (False, None) + if ((system_data[5] & 0xF0) != 0x10): + return (False, None) + return (True, system_data) + + def __parse_disk(self) -> None: + disk_system_data = None + disk_id_data = None + disk_bad_lbas = [] + + drive_index = 0 + 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] + disk_bad_lbas.clear() + disk_bad_lbas.extend(bad_lbas) + for system_lba in system_data_lbas: + (valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True) + if (valid): + self.__drive_type = drive_type + disk_system_data = system_data + else: + disk_bad_lbas.append(system_lba) + drive_index += 1 + + for id_lba in [15, 14]: + (valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False) + if (valid): + disk_id_data = id_data + else: + disk_bad_lbas.append(id_lba) + + if not (disk_system_data and disk_id_data): + raise ValueError('Provided 64DD disk file is not valid') + + disk_zone_bad_tracks = [] + + for zone in range(len(self.__DISK_ZONES)): + zone_bad_tracks = [] + start = 0 if zone == 0 else system_data[0x07 + zone] + stop = system_data[0x07 + zone + 1] + for offset in range(start, stop): + zone_bad_tracks.append(system_data[0x20 + offset]) + 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) + disk_zone_bad_tracks.append(zone_bad_tracks) + + disk_type = disk_system_data[5] & 0x0F + + current_lba = 0 + starting_block = 0 + disk_file_offset = 0 + + for zone in self.__DISK_VZONE_TO_PZONE[disk_type]: + (head, sector_size, tracks, track) = self.__DISK_ZONES[zone] + + for zone_track in range(tracks): + current_zone_track = ( + (tracks - 1) - zone_track) if head else zone_track + + if (current_zone_track in disk_zone_bad_tracks[zone]): + track += (-1) if head else 1 + continue + + for block in range(self.__DISK_BLOCKS_PER_TRACK): + index = (track << 2) | (head << 1) | (starting_block ^ block) + if (current_lba not in disk_bad_lbas): + self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK) + else: + self.__block_info_table[index] = None + disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK + current_lba += 1 + + track += (-1) if head else 1 + starting_block ^= 1 + + def __check_track_head_block(self, track: int, head: int, block: int) -> None: + if (track < 0 or track >= self.__DISK_TRACKS): + raise ValueError('Track outside of possible range') + if (head < 0 or head >= self.__DISK_HEADS): + raise ValueError('Head outside of possible range') + if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK): + raise ValueError('Block outside of possible range') + + def __get_table_index(self, track: int, head: int, block: int) -> int: + return (track << 2) | (head << 1) | (block) + + def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]: + if (self.__file.closed): + return None + self.__check_track_head_block(track, head, block) + index = self.__get_table_index(track, head, block) + return self.__block_info_table[index] + + def load(self, path: str) -> None: + self.unload() + self.__file = open(path, 'rb+') + self.__parse_disk() + self.loaded = True + + def unload(self) -> None: + self.loaded = False + if (self.__file != None and not self.__file.closed): + self.__file.close() + self.__drive_type = None + + def get_block_info_table(self) -> list[tuple[int, int]]: + return self.__block_info_table + + def get_drive_type(self) -> str: + return self.__drive_type + + def read_block(self, track: int, head: int, block: int) -> bytes: + info = self.__get_block_info(track, head, block) + if (info == None): + raise BadBlockError + (offset, block_size) = info + self.__file.seek(offset) + return self.__file.read(block_size) + + def write_block(self, track: int, head: int, block: int, data: bytes) -> None: + info = self.__get_block_info(track, head, block) + if (info == None): + raise BadBlockError + (offset, block_size) = info + if (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.write(data) + + class ConnectionException(Exception): pass