From 137403373384005e42cef06e4dc80e6ef707bfc7 Mon Sep 17 00:00:00 2001 From: Polprzewodnikowy Date: Wed, 27 Jul 2022 00:28:34 +0200 Subject: [PATCH] 64dd working yet again, isv brought back, dma fixes, usb path rewrite, pc code rewrite --- fw/rtl/memory/memory_bram.sv | 2 +- fw/rtl/memory/memory_dma.sv | 18 +- sw/controller/Makefile | 2 + sw/controller/src/cfg.c | 35 +-- sw/controller/src/dd.c | 405 ++++++++++++++++++++++++++++++----- sw/controller/src/dd.h | 18 ++ sw/controller/src/debug.c | 34 +++ sw/controller/src/debug.h | 14 ++ sw/controller/src/flash.c | 12 +- sw/controller/src/flashram.c | 3 +- sw/controller/src/fpga.c | 10 + sw/controller/src/fpga.h | 1 + sw/controller/src/gvr.c | 9 +- sw/controller/src/hw.c | 31 ++- sw/controller/src/hw.h | 2 + sw/controller/src/isv.c | 67 ++++++ sw/controller/src/isv.h | 14 ++ sw/controller/src/usb.c | 342 +++++++++++++++++++---------- sw/controller/src/usb.h | 20 ++ sw/pc/.gitignore | 2 +- sw/pc/lib/sc64.py | 254 ++++++++++++++++++++++ sw/pc/lib/sc64_64dd.py | 220 +++++++++++++++++++ sw/pc/lib/sc64_transport.py | 171 +++++++++++++++ sw/pc/sc64.py | 19 +- 24 files changed, 1475 insertions(+), 230 deletions(-) create mode 100644 sw/controller/src/debug.c create mode 100644 sw/controller/src/debug.h create mode 100644 sw/controller/src/isv.c create mode 100644 sw/controller/src/isv.h create mode 100644 sw/pc/lib/sc64.py create mode 100644 sw/pc/lib/sc64_64dd.py create mode 100644 sw/pc/lib/sc64_transport.py diff --git a/fw/rtl/memory/memory_bram.sv b/fw/rtl/memory/memory_bram.sv index 9505e17..a1ae1b7 100644 --- a/fw/rtl/memory/memory_bram.sv +++ b/fw/rtl/memory/memory_bram.sv @@ -123,7 +123,7 @@ module memory_bram ( // DD memory - logic [15:0] dd_bram [0:511]; + logic [15:0] dd_bram [0:1023]; logic [15:0] dd_bram_rdata; always_ff @(posedge clk) begin diff --git a/fw/rtl/memory/memory_dma.sv b/fw/rtl/memory/memory_dma.sv index 20faccc..0d7761d 100644 --- a/fw/rtl/memory/memory_dma.sv +++ b/fw/rtl/memory/memory_dma.sv @@ -197,26 +197,14 @@ module memory_dma ( // Mem bus controller always_ff @(posedge clk) begin - if (reset || dma_scb.stop) begin - dma_scb.busy <= 1'b0; - end else begin - if (dma_start) begin - dma_scb.busy <= 1'b1; - end - - if (dma_scb.busy) begin - if (!trx_enabled) begin - dma_scb.busy <= 1'b0; - end - end - end + dma_scb.busy <= mem_bus.request || trx_enabled; end always_ff @(posedge clk) begin - if (reset || dma_scb.stop) begin + if (reset) begin mem_bus.request <= 1'b0; end else begin - if (dma_scb.busy && !mem_bus.request) begin + if (!mem_bus.request) begin if (mem_bus.write) begin if (rx_buffer_valid) begin mem_bus.request <= 1'b1; diff --git a/sw/controller/Makefile b/sw/controller/Makefile index 55ab241..15c289e 100644 --- a/sw/controller/Makefile +++ b/sw/controller/Makefile @@ -17,11 +17,13 @@ SRC_FILES = \ cfg.c \ cic.c \ dd.c \ + debug.c \ flash.c \ flashram.c \ fpga.c \ gvr.c \ hw.c \ + isv.c \ main.c \ rtc.c \ task.c \ diff --git a/sw/controller/src/cfg.c b/sw/controller/src/cfg.c index d942983..d838797 100644 --- a/sw/controller/src/cfg.c +++ b/sw/controller/src/cfg.c @@ -1,7 +1,9 @@ #include #include "cfg.h" +#include "dd.h" #include "flash.h" #include "fpga.h" +#include "isv.h" #include "rtc.h" @@ -16,6 +18,8 @@ typedef enum { CFG_ID_CIC_SEED, CFG_ID_TV_TYPE, CFG_ID_FLASH_ERASE_BLOCK, + CFG_ID_DD_DRIVE_TYPE, + CFG_ID_DD_DISK_STATE, } cfg_id_t; typedef enum { @@ -111,6 +115,7 @@ void cfg_query (uint32_t *args) { args[1] = (fpga_reg_get(REG_CFG_SCR) & CFG_SCR_DD_ENABLED); break; case CFG_ID_ISV_ENABLE: + args[1] = isv_get_enabled(); break; case CFG_ID_BOOT_MODE: args[1] = p.boot_mode; @@ -126,6 +131,10 @@ void cfg_query (uint32_t *args) { break; case CFG_ID_FLASH_ERASE_BLOCK: break; + case CFG_ID_DD_DRIVE_TYPE: + break; + case CFG_ID_DD_DISK_STATE: + break; } } @@ -144,6 +153,7 @@ void cfg_update (uint32_t *args) { change_scr_bits(CFG_SCR_DD_ENABLED | CFG_SCR_DDIPL_ENABLED, args[1]); break; case CFG_ID_ISV_ENABLE: + isv_set_enabled(args[1]); break; case CFG_ID_BOOT_MODE: p.boot_mode = args[1]; @@ -161,6 +171,12 @@ void cfg_update (uint32_t *args) { case CFG_ID_FLASH_ERASE_BLOCK: flash_erase_block(args[1]); break; + case CFG_ID_DD_DRIVE_TYPE: + dd_set_drive_type(args[1]); + break; + case CFG_ID_DD_DISK_STATE: + dd_set_disk_state(args[1]); + break; } } @@ -185,7 +201,7 @@ void cfg_set_time (uint32_t *args) { void cfg_init (void) { - // fpga_reg_set(REG_CFG_SCR, 0); + fpga_reg_set(REG_CFG_SCR, 0); set_save_type(SAVE_TYPE_NONE); p.cic_seed = 0xFFFF; @@ -193,14 +209,6 @@ void cfg_init (void) { p.boot_mode = BOOT_MODE_MENU_SD; } -#include "stm32g030xx.h" - -void uart_print (const char *str) { - while (*str != '\0') { - while (!(USART1->ISR & USART_ISR_TXE_TXFNF)); - USART1->TDR = *str++; - } -} void cfg_process (void) { uint32_t args[2]; @@ -209,10 +217,7 @@ void cfg_process (void) { args[0] = fpga_reg_get(REG_CFG_DATA_0); args[1] = fpga_reg_get(REG_CFG_DATA_1); char cmd = (char) fpga_reg_get(REG_CFG_CMD); - uart_print("GOT_CMD"); - char tmp[2] = {cmd, 0}; - uart_print(tmp); - uart_print("\n"); + switch (cmd) { case 'v': args[0] = cfg_get_version(); @@ -234,10 +239,6 @@ void cfg_process (void) { cfg_set_time(args); break; - case 'U': - // uart_put((char) (args[0] & 0xFF)); - break; - default: fpga_reg_set(REG_CFG_CMD, CFG_CMD_ERROR | CFG_CMD_DONE); return; diff --git a/sw/controller/src/dd.c b/sw/controller/src/dd.c index 7f96c1b..2f0d5fc 100644 --- a/sw/controller/src/dd.c +++ b/sw/controller/src/dd.c @@ -1,13 +1,24 @@ #include #include +#include +#include "dd.h" #include "fpga.h" +#include "hw.h" #include "rtc.h" +#include "usb.h" +#define DD_SECTOR_MAX_SIZE (232) +#define DD_BLOCK_DATA_SECTORS_NUM (85) +#define DD_BLOCK_BUFFER_ADDRESS (0x03BC0000UL - (DD_SECTOR_MAX_SIZE * DD_BLOCK_DATA_SECTORS_NUM)) +#define DD_SECTOR_BUFFER_ADDRESS (0x06006000UL) + #define DD_DRIVE_ID_RETAIL (0x0003) #define DD_DRIVE_ID_DEVELOPMENT (0x0004) #define DD_VERSION_RETAIL (0x0114) +#define DD_SPIN_UP_TIME (2000) + typedef enum { DD_CMD_SEEK_READ = 0x01, @@ -27,89 +38,373 @@ typedef enum { } dd_cmd_t; -static rtc_time_t time; +enum state { + STATE_IDLE, + STATE_START, + STATE_BLOCK_READ_WAIT, + STATE_SECTOR_READ, + STATE_SECTOR_WRITE, + STATE_BLOCK_WRITE, + STATE_BLOCK_WRITE_WAIT, + STATE_NEXT_BLOCK, +}; -void dd_init (void) { - fpga_reg_set(REG_DD_SCR, 0); - fpga_reg_set(REG_DD_HEAD_TRACK, 0); - fpga_reg_set(REG_DD_DRIVE_ID, DD_DRIVE_ID_RETAIL); +typedef union sector_info { + uint32_t full; + struct { + uint8_t sector_num; + uint8_t sector_size; + uint8_t sector_size_full; + uint8_t sectors_in_block; + }; +} sector_info_t; + +struct process { + enum state state; + rtc_time_t time; + bool disk_spinning; + bool cmd_response_delayed; + bool cmd_response_ready; + bool bm_running; + bool transfer_mode; + bool full_track_transfer; + bool starting_block; + uint16_t head_track; + uint8_t current_sector; + sector_info_t sector_info; + bool block_ready; + bool block_valid; +}; + + +static struct process p; + + +static uint16_t dd_track_head_block (void) { + uint16_t track = ((p.head_track & DD_TRACK_MASK) << 2); + uint16_t head = (((p.head_track & DD_HEAD_MASK) ? 1 : 0) << 1); + uint16_t block = (p.starting_block ? 1 : 0); + return (track | head | block); +} + +static bool dd_block_read_request (void) { + usb_tx_info_t packet_info; + packet_info.cmd = PACKET_CMD_DD_REQUEST; + packet_info.data_length = 12; + packet_info.data[0] = 1; + packet_info.data[1] = DD_BLOCK_BUFFER_ADDRESS; + packet_info.data[2] = dd_track_head_block(); + packet_info.dma_length = 0; + p.block_ready = false; + return usb_enqueue_packet(&packet_info); +} + +static bool dd_block_write_request (void) { + usb_tx_info_t packet_info; + packet_info.cmd = PACKET_CMD_DD_REQUEST; + packet_info.data_length = 12; + packet_info.data[0] = 2; + packet_info.data[1] = DD_BLOCK_BUFFER_ADDRESS; + packet_info.data[2] = dd_track_head_block(); + packet_info.dma_length = (p.sector_info.sector_size + 1) * DD_BLOCK_DATA_SECTORS_NUM; + packet_info.dma_address = DD_BLOCK_BUFFER_ADDRESS; + p.block_ready = false; + return usb_enqueue_packet(&packet_info); +} + +static void dd_set_cmd_response_ready (void) { + p.cmd_response_ready = true; } -void dd_process (void) { +void dd_set_block_ready (bool valid) { + p.block_ready = true; + p.block_valid = valid; +} + +void dd_set_drive_type (dd_drive_type_t type) { + switch (type) { + case DD_DRIVE_TYPE_RETAIL: + fpga_reg_set(REG_DD_DRIVE_ID, DD_DRIVE_ID_RETAIL); + break; + + case DD_DRIVE_TYPE_DEVELOPMENT: + fpga_reg_set(REG_DD_DRIVE_ID, DD_DRIVE_ID_DEVELOPMENT); + break; + } +} + +void dd_set_disk_state (dd_disk_state_t state) { uint32_t scr = fpga_reg_get(REG_DD_SCR); + scr &= ~(DD_SCR_DISK_CHANGED | DD_SCR_DISK_INSERTED); + switch (state) { + case DD_DISK_STATE_EJECTED: + break; + + case DD_DISK_STATE_INSERTED: + scr |= DD_SCR_DISK_INSERTED; + break; + + case DD_DISK_STATE_CHANGED: + scr |= (DD_SCR_DISK_CHANGED | DD_SCR_DISK_INSERTED); + break; + } + fpga_reg_set(REG_DD_SCR, scr); +} + +void dd_init (void) { + fpga_reg_set(REG_DD_SCR, DD_SCR_DISK_INSERTED); + fpga_reg_set(REG_DD_HEAD_TRACK, 0); + fpga_reg_set(REG_DD_DRIVE_ID, DD_DRIVE_ID_RETAIL); + p.state = STATE_IDLE; + p.cmd_response_delayed = false; + p.cmd_response_ready = false; + p.disk_spinning = false; + p.bm_running = false; +} + +void dd_process (void) { + uint32_t starting_scr = fpga_reg_get(REG_DD_SCR); + uint32_t scr = starting_scr; if (scr & DD_SCR_HARD_RESET) { - fpga_reg_set(REG_DD_SCR, scr & ~(DD_SCR_DISK_CHANGED)); - fpga_reg_set(REG_DD_HEAD_TRACK, 0); + p.state = STATE_IDLE; + p.cmd_response_delayed = false; + p.cmd_response_ready = false; + p.disk_spinning = false; + p.bm_running = false; + p.head_track = 0; + scr &= ~(DD_SCR_DISK_CHANGED); } if (scr & DD_SCR_CMD_PENDING) { uint32_t cmd_data = fpga_reg_get(REG_DD_CMD_DATA); uint8_t cmd = (cmd_data >> 16) & 0xFF; uint16_t data = cmd_data & 0xFFFF; - fpga_reg_set(REG_DD_CMD_DATA, data); - switch (cmd) { - case DD_CMD_CLEAR_DISK_CHANGE: - fpga_reg_set(REG_DD_SCR, scr & ~(DD_SCR_DISK_CHANGED)); - break; + if (p.cmd_response_delayed) { + if (p.cmd_response_ready) { + p.cmd_response_delayed = false; + fpga_reg_set(REG_DD_HEAD_TRACK, DD_HEAD_TRACK_INDEX_LOCK | data); + scr |= DD_SCR_CMD_READY; + } + } else if ((cmd == DD_CMD_SEEK_READ) || (cmd == DD_CMD_SEEK_WRITE)) { + p.cmd_response_delayed = true; + p.cmd_response_ready = false; + if (!p.disk_spinning) { + p.disk_spinning = true; + hw_tim_setup(TIM_ID_DD, DD_SPIN_UP_TIME, dd_set_cmd_response_ready); + } else { + p.cmd_response_ready = true; + } + fpga_reg_set(REG_DD_HEAD_TRACK, p.head_track & ~(DD_HEAD_TRACK_INDEX_LOCK)); + p.head_track = data & DD_HEAD_TRACK_MASK; + } else { + switch (cmd) { + case DD_CMD_CLEAR_DISK_CHANGE: + scr &= ~(DD_SCR_DISK_CHANGED); + break; - case DD_CMD_CLEAR_RESET_STATE: - fpga_reg_set(REG_DD_SCR, scr & ~(DD_SCR_DISK_CHANGED)); - fpga_reg_set(REG_DD_SCR, scr | DD_SCR_HARD_RESET_CLEAR); - - break; + case DD_CMD_CLEAR_RESET_STATE: + scr &= ~(DD_SCR_DISK_CHANGED); + scr |= DD_SCR_HARD_RESET_CLEAR; + break; - case DD_CMD_READ_VERSION: - fpga_reg_set(REG_DD_CMD_DATA, DD_VERSION_RETAIL); - break; + case DD_CMD_READ_VERSION: + data = DD_VERSION_RETAIL; + break; - case DD_CMD_SET_DISK_TYPE: - break; + case DD_CMD_SET_DISK_TYPE: + break; - case DD_CMD_REQUEST_STATUS: - fpga_reg_set(REG_DD_CMD_DATA, 0); - break; + case DD_CMD_REQUEST_STATUS: + data = 0; + break; - case DD_CMD_SET_RTC_YEAR_MONTH: - time.year = ((data >> 8) & 0xFF); - time.month = (data & 0xFF); - break; + case DD_CMD_SET_RTC_YEAR_MONTH: + p.time.year = ((data >> 8) & 0xFF); + p.time.month = (data & 0xFF); + break; - case DD_CMD_SET_RTC_DAY_HOUR: - time.day = ((data >> 8) & 0xFF); - time.hour = (data & 0xFF); - break; + case DD_CMD_SET_RTC_DAY_HOUR: + p.time.day = ((data >> 8) & 0xFF); + p.time.hour = (data & 0xFF); + break; - case DD_CMD_SET_RTC_MINUTE_SECOND: - time.minute = ((data >> 8) & 0xFF); - time.second = (data & 0xFF); - rtc_set_time(&time); - break; + case DD_CMD_SET_RTC_MINUTE_SECOND: + p.time.minute = ((data >> 8) & 0xFF); + p.time.second = (data & 0xFF); + rtc_set_time(&p.time); + break; - case DD_CMD_GET_RTC_YEAR_MONTH: - fpga_reg_set(REG_DD_CMD_DATA, (time.year << 8) | time.month); - break; + case DD_CMD_GET_RTC_YEAR_MONTH: + data = (p.time.year << 8) | p.time.month; + break; - case DD_CMD_GET_RTC_DAY_HOUR: - fpga_reg_set(REG_DD_CMD_DATA, (time.day << 8) | time.hour); - break; + case DD_CMD_GET_RTC_DAY_HOUR: + data = (p.time.day << 8) | p.time.hour; + break; - case DD_CMD_GET_RTC_MINUTE_SECOND: - rtc_get_time(&time); - fpga_reg_set(REG_DD_CMD_DATA, (time.minute << 8) | time.second); - break; + case DD_CMD_GET_RTC_MINUTE_SECOND: + rtc_get_time(&p.time); + data = (p.time.minute << 8) | p.time.second; + break; - case DD_CMD_READ_PROGRAM_VERSION: - fpga_reg_set(REG_DD_CMD_DATA, 0); - break; + case DD_CMD_READ_PROGRAM_VERSION: + data = 0; + break; - default: - break; + default: + break; + } + + fpga_reg_set(REG_DD_CMD_DATA, data); + scr |= DD_SCR_CMD_READY; + } + } + + if (scr & DD_SCR_BM_STOP) { + scr |= DD_SCR_BM_STOP_CLEAR; + scr &= ~(DD_SCR_BM_MICRO_ERROR | DD_SCR_BM_TRANSFER_C2 | DD_SCR_BM_TRANSFER_DATA); + p.bm_running = false; + } else if (scr & DD_SCR_BM_START) { + scr |= DD_SCR_BM_CLEAR | DD_SCR_BM_ACK_CLEAR | DD_SCR_BM_START_CLEAR; + scr &= ~(DD_SCR_BM_MICRO_ERROR | DD_SCR_BM_TRANSFER_C2 | DD_SCR_BM_TRANSFER_DATA); + p.state = STATE_START; + p.bm_running = true; + p.transfer_mode = (scr & DD_SCR_BM_TRANSFER_MODE); + p.full_track_transfer = (scr & DD_SCR_BM_TRANSFER_BLOCKS); + p.sector_info.full = fpga_reg_get(REG_DD_SECTOR_INFO); + p.starting_block = (p.sector_info.sector_num == (p.sector_info.sectors_in_block + 1)); + } else if (p.bm_running) { + if (scr & DD_SCR_BM_PENDING) { + scr |= DD_SCR_BM_CLEAR; + if (p.transfer_mode) { + if (p.current_sector < (p.sector_info.sectors_in_block - 4)) { + p.state = STATE_SECTOR_READ; + } else if (p.current_sector == (p.sector_info.sectors_in_block - 4)) { + p.current_sector += 1; + scr &= ~(DD_SCR_BM_TRANSFER_DATA); + scr |= DD_SCR_BM_READY; + } else if (p.current_sector >= p.sector_info.sectors_in_block) { + p.state = STATE_NEXT_BLOCK; + } else { + } + } else { + if (p.current_sector < (p.sector_info.sectors_in_block - 4)) { + p.state = STATE_SECTOR_WRITE; + } + } + } + if (scr & DD_SCR_BM_ACK) { + scr |= DD_SCR_BM_ACK_CLEAR; + if (p.transfer_mode) { + if ((p.current_sector <= (p.sector_info.sectors_in_block - 4))) { + } else if (p.current_sector < p.sector_info.sectors_in_block) { + p.current_sector += 1; + scr |= DD_SCR_BM_READY; + } else if (p.current_sector == p.sector_info.sectors_in_block) { + p.current_sector += 1; + scr |= DD_SCR_BM_TRANSFER_C2 | DD_SCR_BM_READY; + } + } else { + if (p.current_sector == (p.sector_info.sectors_in_block - 4)) { + p.bm_running = false; + } + } } - fpga_reg_set(REG_DD_SCR, scr | DD_SCR_CMD_READY); + switch (p.state) { + case STATE_IDLE: + break; + + case STATE_START: + p.current_sector = 0; + if (dd_block_read_request()) { + p.state = STATE_BLOCK_READ_WAIT; + } + break; + + case STATE_BLOCK_READ_WAIT: + if (p.block_ready) { + if (p.transfer_mode) { + if (p.block_valid) { + p.state = STATE_SECTOR_READ; + scr |= DD_SCR_BM_TRANSFER_DATA; + } else { + p.state = STATE_SECTOR_READ; + scr |= DD_SCR_BM_MICRO_ERROR; + } + } else { + p.state = STATE_IDLE; + if (p.block_valid) { + scr |= DD_SCR_BM_TRANSFER_DATA | DD_SCR_BM_READY; + } else { + scr |= DD_SCR_BM_MICRO_ERROR | DD_SCR_BM_READY; + } + } + } + break; + + case STATE_SECTOR_READ: + fpga_mem_copy( + DD_BLOCK_BUFFER_ADDRESS + (p.current_sector * (p.sector_info.sector_size + 1)), + DD_SECTOR_BUFFER_ADDRESS, + p.sector_info.sector_size + 1 + ); + p.state = STATE_IDLE; + p.current_sector += 1; + scr |= DD_SCR_BM_READY; + break; + + case STATE_SECTOR_WRITE: + fpga_mem_copy( + DD_SECTOR_BUFFER_ADDRESS, + DD_BLOCK_BUFFER_ADDRESS + (p.current_sector * (p.sector_info.sector_size + 1)), + p.sector_info.sector_size + 1 + ); + p.current_sector += 1; + if (p.current_sector < (p.sector_info.sectors_in_block - 4)) { + p.state = STATE_IDLE; + scr |= DD_SCR_BM_READY; + } else { + p.state = STATE_BLOCK_WRITE; + } + break; + + case STATE_BLOCK_WRITE: + if (dd_block_write_request()) { + p.state = STATE_BLOCK_WRITE_WAIT; + } + break; + + case STATE_BLOCK_WRITE_WAIT: + if (p.block_ready) { + p.state = STATE_NEXT_BLOCK; + } + break; + + case STATE_NEXT_BLOCK: + if (p.full_track_transfer) { + p.state = STATE_START; + p.full_track_transfer = false; + p.starting_block = !p.starting_block; + scr &= ~(DD_SCR_BM_TRANSFER_C2); + } else { + if (p.transfer_mode) { + p.bm_running = false; + } else { + p.state = STATE_IDLE; + scr &= ~(DD_SCR_BM_TRANSFER_C2 | DD_SCR_BM_TRANSFER_DATA); + scr |= DD_SCR_BM_READY; + } + } + break; + } + } + + if (scr != starting_scr) { + fpga_reg_set(REG_DD_SCR, scr); } } diff --git a/sw/controller/src/dd.h b/sw/controller/src/dd.h index 3368741..57e731c 100644 --- a/sw/controller/src/dd.h +++ b/sw/controller/src/dd.h @@ -2,6 +2,24 @@ #define DD_H__ +#include + + +typedef enum { + DD_DRIVE_TYPE_RETAIL = 0, + DD_DRIVE_TYPE_DEVELOPMENT = 1, +} dd_drive_type_t; + +typedef enum { + DD_DISK_STATE_EJECTED = 0, + DD_DISK_STATE_INSERTED = 1, + DD_DISK_STATE_CHANGED = 2, +} dd_disk_state_t; + + +void dd_set_drive_type (dd_drive_type_t type); +void dd_set_disk_state (dd_disk_state_t state); +void dd_set_block_ready (bool valid); void dd_init (void); void dd_process (void); diff --git a/sw/controller/src/debug.c b/sw/controller/src/debug.c new file mode 100644 index 0000000..59c5b0f --- /dev/null +++ b/sw/controller/src/debug.c @@ -0,0 +1,34 @@ +#include +#include "debug.h" +#include "hw.h" + + +static char hex_chars[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +}; + + +void debug_print_text (char *text) { + hw_uart_write((uint8_t *) (text), strlen(text)); +} + +void debug_print_8bit (uint8_t value) { + char buffer[3] = { + hex_chars[(value >> 4) & 0x0F], + hex_chars[value & 0x0F], + '\0' + }; + debug_print_text(buffer); +} + +void debug_print_16bit (uint16_t value) { + debug_print_8bit((value >> 8) & 0xFF); + debug_print_8bit(value & 0xFF); +} + +void debug_print_32bit (uint32_t value) { + debug_print_8bit((value >> 24) & 0xFF); + debug_print_8bit((value >> 16) & 0xFF); + debug_print_8bit((value >> 8) & 0xFF); + debug_print_8bit(value & 0xFF); +} diff --git a/sw/controller/src/debug.h b/sw/controller/src/debug.h new file mode 100644 index 0000000..46a64c4 --- /dev/null +++ b/sw/controller/src/debug.h @@ -0,0 +1,14 @@ +#ifndef DEBUG_H__ +#define DEBUG_H__ + + +#include + + +void debug_print_text (char *text); +void debug_print_8bit (uint8_t value); +void debug_print_16bit (uint16_t value); +void debug_print_32bit (uint32_t value); + + +#endif diff --git a/sw/controller/src/flash.c b/sw/controller/src/flash.c index 9144689..1c0f602 100644 --- a/sw/controller/src/flash.c +++ b/sw/controller/src/flash.c @@ -2,10 +2,16 @@ #include "fpga.h" +#define ERASE_BLOCK_SIZE (64 * 1024) + + void flash_erase_block (uint32_t offset) { uint8_t dummy[2]; - while (fpga_reg_get(REG_FLASH_SCR) & FLASH_SCR_BUSY); - fpga_reg_set(REG_FLASH_SCR, offset); - fpga_mem_read(offset, 2, dummy); + for (int i = 0; i < 2; i++) { + while (fpga_reg_get(REG_FLASH_SCR) & FLASH_SCR_BUSY); + fpga_reg_set(REG_FLASH_SCR, offset); + fpga_mem_read(offset, 2, dummy); + offset += ERASE_BLOCK_SIZE; + } } diff --git a/sw/controller/src/flashram.c b/sw/controller/src/flashram.c index e0d6855..df3cb8e 100644 --- a/sw/controller/src/flashram.c +++ b/sw/controller/src/flashram.c @@ -62,8 +62,7 @@ void flashram_process (void) { break; case OP_WRITE_PAGE: - fpga_mem_read(FLASHRAM_BUFFER_OFFSET, FLASHRAM_PAGE_SIZE, buffer); - fpga_mem_write(address, FLASHRAM_PAGE_SIZE, buffer); + fpga_mem_copy(FLASHRAM_BUFFER_OFFSET, address, FLASHRAM_PAGE_SIZE); fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE); break; diff --git a/sw/controller/src/fpga.c b/sw/controller/src/fpga.c index 4182608..8c168d1 100644 --- a/sw/controller/src/fpga.c +++ b/sw/controller/src/fpga.c @@ -67,6 +67,16 @@ void fpga_mem_write (uint32_t address, size_t length, uint8_t *buffer) { while (fpga_reg_get(REG_MEM_SCR) & MEM_SCR_BUSY); } +void fpga_mem_copy (uint32_t src, uint32_t dst, size_t length) { + fpga_reg_set(REG_MEM_ADDRESS, src); + fpga_reg_set(REG_MEM_SCR, (length << MEM_SCR_LENGTH_BIT) | MEM_SCR_START); + while (fpga_reg_get(REG_MEM_SCR) & MEM_SCR_BUSY); + + fpga_reg_set(REG_MEM_ADDRESS, dst); + fpga_reg_set(REG_MEM_SCR, (length << MEM_SCR_LENGTH_BIT) | MEM_SCR_DIRECTION | MEM_SCR_START); + while (fpga_reg_get(REG_MEM_SCR) & MEM_SCR_BUSY); +} + uint8_t fpga_usb_status_get (void) { fpga_cmd_t cmd = CMD_USB_STATUS; uint8_t status; diff --git a/sw/controller/src/fpga.h b/sw/controller/src/fpga.h index 53e2a06..134da8a 100644 --- a/sw/controller/src/fpga.h +++ b/sw/controller/src/fpga.h @@ -152,6 +152,7 @@ uint32_t fpga_reg_get (fpga_reg_t reg); void fpga_reg_set (fpga_reg_t reg, uint32_t value); void fpga_mem_read (uint32_t address, size_t length, uint8_t *buffer); void fpga_mem_write (uint32_t address, size_t length, uint8_t *buffer); +void fpga_mem_copy (uint32_t src, uint32_t dst, size_t length); uint8_t fpga_usb_status_get (void); uint8_t fpga_usb_pop (void); void fpga_usb_push (uint8_t data); diff --git a/sw/controller/src/gvr.c b/sw/controller/src/gvr.c index a3ad97f..32770d1 100644 --- a/sw/controller/src/gvr.c +++ b/sw/controller/src/gvr.c @@ -2,6 +2,7 @@ #include "cfg.h" #include "flashram.h" #include "fpga.h" +#include "isv.h" #include "rtc.h" #include "usb.h" @@ -9,16 +10,18 @@ void gvr_task (void) { while (fpga_id_get() != FPGA_ID); - dd_init(); cfg_init(); + dd_init(); flashram_init(); + isv_init(); usb_init(); while (1) { - dd_process(); cfg_process(); + dd_process(); flashram_process(); - usb_process(); + isv_process(); rtc_process(); + usb_process(); } } diff --git a/sw/controller/src/hw.c b/sw/controller/src/hw.c index db5f8e7..113016e 100644 --- a/sw/controller/src/hw.c +++ b/sw/controller/src/hw.c @@ -52,8 +52,8 @@ static volatile uint8_t *i2c_data_rxptr; static volatile uint32_t i2c_next_cr2; static void (*volatile i2c_callback)(void); -static const TIM_TypeDef *tims[] = { TIM14, TIM16, TIM17 }; -static void (*volatile tim_callbacks[3])(void); +static const TIM_TypeDef *tims[] = { TIM14, TIM16, TIM17, TIM3 }; +static void (*volatile tim_callbacks[4])(void); void hw_gpio_init (gpio_id_t id, gpio_mode_t mode, gpio_ot_t ot, gpio_ospeed_t ospeed, gpio_pupd_t pupd, gpio_af_t af, int value) { @@ -113,6 +113,13 @@ void hw_gpio_reset (gpio_id_t id) { gpio->BSRR = (GPIO_BSRR_BR0 << pin); } +void hw_uart_write (uint8_t *data, int length) { + for (int i = 0; i < length; i++) { + while (!(USART1->ISR & USART_ISR_TXE_TXFNF)); + USART1->TDR = *data++; + } +} + void hw_spi_start (void) { hw_gpio_reset(GPIO_ID_SPI_CS); } @@ -213,6 +220,9 @@ void hw_tim_disable_irq (tim_id_t id) { case TIM_ID_GVR: NVIC_DisableIRQ(TIM17_IRQn); break; + case TIM_ID_DD: + NVIC_DisableIRQ(TIM3_IRQn); + break; default: break; } @@ -229,6 +239,9 @@ void hw_tim_enable_irq (tim_id_t id) { case TIM_ID_GVR: NVIC_EnableIRQ(TIM17_IRQn); break; + case TIM_ID_DD: + NVIC_EnableIRQ(TIM3_IRQn); + break; default: break; } @@ -261,7 +274,8 @@ void hw_init (void) { RCC->AHBENR |= RCC_AHBENR_DMA1EN; RCC->APBENR1 |= ( RCC_APBENR1_DBGEN | - RCC_APBENR1_I2C1EN + RCC_APBENR1_I2C1EN | + RCC_APBENR1_TIM3EN ); RCC->APBENR2 |= ( RCC_APBENR2_TIM17EN | @@ -317,7 +331,7 @@ void hw_init (void) { hw_gpio_init(GPIO_ID_SPI_CS, GPIO_OUTPUT, GPIO_PP, GPIO_SPEED_HIGH, GPIO_PULL_NONE, GPIO_AF_0, 1); hw_gpio_init(GPIO_ID_SPI_CLK, GPIO_ALT, GPIO_PP, GPIO_SPEED_HIGH, GPIO_PULL_NONE, GPIO_AF_0, 0); - hw_gpio_init(GPIO_ID_SPI_MISO, GPIO_ALT, GPIO_PP, GPIO_SPEED_HIGH, GPIO_PULL_NONE, GPIO_AF_0, 0); + hw_gpio_init(GPIO_ID_SPI_MISO, GPIO_ALT, GPIO_PP, GPIO_SPEED_HIGH, GPIO_PULL_DOWN, GPIO_AF_0, 0); hw_gpio_init(GPIO_ID_SPI_MOSI, GPIO_ALT, GPIO_PP, GPIO_SPEED_HIGH, GPIO_PULL_NONE, GPIO_AF_0, 0); hw_gpio_init(GPIO_ID_FPGA_INT, GPIO_INPUT, GPIO_PP, GPIO_SPEED_VLOW, GPIO_PULL_UP, GPIO_AF_0, 0); @@ -345,6 +359,7 @@ void hw_init (void) { NVIC_EnableIRQ(TIM14_IRQn); NVIC_EnableIRQ(TIM16_IRQn); NVIC_EnableIRQ(TIM17_IRQn); + NVIC_EnableIRQ(TIM3_IRQn); } void EXTI0_1_IRQHandler (void) { @@ -443,3 +458,11 @@ void TIM17_IRQHandler (void) { tim_callbacks[2] = 0; } } + +void TIM3_IRQHandler (void) { + TIM3->SR &= ~(TIM_SR_UIF); + if (tim_callbacks[3]) { + tim_callbacks[3](); + tim_callbacks[3] = 0; + } +} diff --git a/sw/controller/src/hw.h b/sw/controller/src/hw.h index c156370..e897d15 100644 --- a/sw/controller/src/hw.h +++ b/sw/controller/src/hw.h @@ -33,6 +33,7 @@ typedef enum { TIM_ID_CIC = 0, TIM_ID_RTC = 1, TIM_ID_GVR = 2, + TIM_ID_DD = 3, } tim_id_t; typedef enum { @@ -45,6 +46,7 @@ void hw_gpio_irq_setup (gpio_id_t id, gpio_irq_t irq, void (*callback)(void)); uint32_t hw_gpio_get (gpio_id_t id); void hw_gpio_set (gpio_id_t id); void hw_gpio_reset (gpio_id_t id); +void hw_uart_write (uint8_t *data, int length); void hw_spi_start (void); void hw_spi_stop (void); void hw_spi_trx (uint8_t *data, int length, spi_direction_t direction); diff --git a/sw/controller/src/isv.c b/sw/controller/src/isv.c new file mode 100644 index 0000000..99f6910 --- /dev/null +++ b/sw/controller/src/isv.c @@ -0,0 +1,67 @@ +#include "fpga.h" +#include "isv.h" +#include "usb.h" + + +#define ISV_READ_POINTER_ADDRESS (0x03FF0014) +#define ISV_BUFFER_ADDRESS (0x03FF0020) +#define ISV_BUFFER_SIZE ((64 * 1024) - 0x20) + + +struct process { + bool enabled; + uint32_t current_read_pointer; +}; + +static struct process p; + + +static uint32_t isv_get_read_pointer (void) { + uint32_t read_pointer; + fpga_mem_read(ISV_READ_POINTER_ADDRESS, 4, (uint8_t *) (&read_pointer)); + return ( + (read_pointer & 0x000000FF) << 24 | + (read_pointer & 0x0000FF00) << 8 | + (read_pointer & 0x00FF0000) >> 8 | + (read_pointer & 0xFF000000) >> 24 + ); +} + + +void isv_set_enabled (bool enabled) { + if (enabled) { + p.enabled = true; + p.current_read_pointer = 0; + } else { + p.enabled = false; + } +} + +bool isv_get_enabled (void) { + return p.enabled; +} + +void isv_init (void) { + p.enabled = false; +} + +void isv_process (void) { + if (p.enabled) { + uint32_t read_pointer = isv_get_read_pointer(); + if (read_pointer < ISV_BUFFER_SIZE && read_pointer != p.current_read_pointer) { + bool wrap = read_pointer < p.current_read_pointer; + + uint32_t length = ((wrap ? ISV_BUFFER_SIZE : read_pointer) - p.current_read_pointer); + uint32_t offset = ISV_BUFFER_ADDRESS + p.current_read_pointer; + + usb_tx_info_t packet_info; + packet_info.cmd = PACKET_CMD_ISV_OUTPUT; + packet_info.data_length = 0; + packet_info.dma_length = length; + packet_info.dma_address = offset; + if (usb_enqueue_packet(&packet_info)) { + p.current_read_pointer = wrap ? 0 : read_pointer; + } + } + } +} diff --git a/sw/controller/src/isv.h b/sw/controller/src/isv.h new file mode 100644 index 0000000..73807e2 --- /dev/null +++ b/sw/controller/src/isv.h @@ -0,0 +1,14 @@ +#ifndef ISV_H__ +#define ISV_H__ + + +#include + + +void isv_set_enabled (bool enabled); +bool isv_get_enabled (void); +void isv_init (void); +void isv_process (void); + + +#endif diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index 202aa1f..ea25f1c 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -1,5 +1,6 @@ #include #include +#include "dd.h" #include "cfg.h" #include "fpga.h" #include "rtc.h" @@ -7,28 +8,51 @@ #include "flash.h" -enum state { - STATE_IDLE, - STATE_ARGS, - STATE_DATA, - STATE_RESPONSE +enum rx_state { + RX_STATE_IDLE, + RX_STATE_ARGS, + RX_STATE_DATA, }; -struct process { - enum state state; - uint32_t counter; - uint8_t cmd; - uint32_t args[2]; - bool error; - bool dma_in_progress; +enum tx_state { + TX_STATE_IDLE, + TX_STATE_TOKEN, + TX_STATE_DATA, + TX_STATE_DMA, + TX_STATE_FLUSH, }; + +struct process { + enum rx_state rx_state; + uint8_t rx_counter; + uint8_t rx_cmd; + uint32_t rx_args[2]; + bool rx_dma_running; + + enum tx_state tx_state; + uint8_t tx_counter; + usb_tx_info_t tx_info; + uint32_t tx_token; + bool tx_dma_running; + + bool response_pending; + bool response_error; + usb_tx_info_t response_info; + + bool packet_pending; + usb_tx_info_t packet_info; +}; + + static struct process p; static const char CMD_TOKEN[3] = { 'C', 'M', 'D' }; static const uint32_t CMP_TOKEN = (0x434D5000UL); static const uint32_t ERR_TOKEN = (0x45525200UL); +static const uint32_t PKT_TOKEN = (0x504B5400UL); + static bool usb_rx_byte (uint8_t *data) { if (fpga_usb_status_get() & USB_STATUS_RXNE) { @@ -96,11 +120,201 @@ static bool usb_rx_cmd (uint8_t *cmd) { } +static void usb_rx_process (void) { + if (p.rx_state == RX_STATE_IDLE) { + if (!p.response_pending && usb_rx_cmd(&p.rx_cmd)) { + p.rx_state = RX_STATE_ARGS; + p.rx_counter = 0; + p.rx_dma_running = false; + p.response_error = false; + p.response_info.cmd = p.rx_cmd; + p.response_info.data_length = 0; + p.response_info.dma_length = 0; + } + } + + if (p.rx_state == RX_STATE_ARGS) { + while (usb_rx_word(&p.rx_args[p.rx_counter])) { + p.rx_counter += 1; + if (p.rx_counter == 2) { + p.rx_counter = 0; + p.rx_state = RX_STATE_DATA; + break; + } + } + } + + if (p.rx_state == RX_STATE_DATA) { + switch (p.rx_cmd) { + case 'v': + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_info.data_length = 4; + p.response_info.data[0] = cfg_get_version(); + break; + + case 'c': + cfg_query(p.rx_args); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_info.data_length = 4; + p.response_info.data[0] = p.rx_args[1]; + break; + + case 'C': + cfg_update(p.rx_args); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + break; + + case 't': + cfg_get_time(p.rx_args); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_info.data_length = 8; + p.response_info.data[0] = p.rx_args[0]; + p.response_info.data[1] = p.rx_args[1]; + break; + + case 'T': + cfg_set_time(p.rx_args); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + break; + + case 'd': + dd_set_block_ready(p.rx_args[0] == 0); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + break; + + case 'm': + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_info.dma_address = p.rx_args[0]; + p.response_info.dma_length = p.rx_args[1]; + break; + + case 'M': + case 'D': + if (!((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY))) { + if (!p.rx_dma_running) { + fpga_reg_set(REG_USB_DMA_ADDRESS, p.rx_args[0]); + fpga_reg_set(REG_USB_DMA_LENGTH, p.rx_args[1]); + fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_DIRECTION | DMA_SCR_START); + p.rx_dma_running = true; + } else { + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + if (p.rx_cmd == 'D') { + dd_set_block_ready(true); + } + } + } + break; + + default: + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_error = true; + p.response_info.data_length = 4; + p.response_info.data[0] = 0xFF; + } + } +} + +static void usb_tx_process (void) { + if (p.tx_state == TX_STATE_IDLE) { + if (p.response_pending) { + p.response_pending = false; + p.tx_state = TX_STATE_TOKEN; + p.tx_counter = 0; + p.tx_info = p.response_info; + p.tx_token = p.response_error ? ERR_TOKEN : CMP_TOKEN; + p.tx_dma_running = false; + } else if (p.packet_pending) { + p.packet_pending = false; + p.tx_state = TX_STATE_TOKEN; + p.tx_counter = 0; + p.tx_info = p.packet_info; + p.tx_token = PKT_TOKEN; + p.tx_dma_running = false; + } + } + + if (p.tx_state == TX_STATE_TOKEN) { + if (p.tx_counter == 0) { + if (usb_tx_word(p.tx_token | p.tx_info.cmd)) { + p.tx_counter += 1; + } + } + if (p.tx_counter == 1) { + if (usb_tx_word(p.tx_info.data_length + p.tx_info.dma_length)) { + p.tx_state = TX_STATE_DATA; + p.tx_counter = 0; + } + } + } + + if (p.tx_state == TX_STATE_DATA) { + if (p.tx_info.data_length > 0) { + while (usb_tx_word(p.tx_info.data[p.tx_counter])) { + p.tx_counter += 1; + if (p.tx_counter == (p.tx_info.data_length / 4)) { + p.tx_state = TX_STATE_DMA; + p.tx_counter = 0; + break; + } + } + } else { + p.tx_state = TX_STATE_DMA; + } + } + + if (p.tx_state == TX_STATE_DMA) { + if (p.tx_info.dma_length > 0) { + if (!((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY))) { + if (!p.tx_dma_running) { + p.tx_dma_running = true; + fpga_reg_set(REG_USB_DMA_ADDRESS, p.tx_info.dma_address); + fpga_reg_set(REG_USB_DMA_LENGTH, p.tx_info.dma_length); + fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_START); + } else { + p.tx_state = TX_STATE_FLUSH; + } + } + } else { + p.tx_state = TX_STATE_FLUSH; + } + } + + if (p.tx_state == TX_STATE_FLUSH) { + fpga_reg_set(REG_USB_SCR, USB_SCR_WRITE_FLUSH); + p.tx_state = TX_STATE_IDLE; + } +} + + +bool usb_enqueue_packet (usb_tx_info_t *info) { + if (p.packet_pending) { + return false; + } + + p.packet_pending = true; + p.packet_info = *info; + + return true; +} + void usb_init (void) { fpga_reg_set(REG_USB_DMA_SCR, DMA_SCR_STOP); fpga_reg_set(REG_USB_SCR, USB_SCR_FIFO_FLUSH); - p.state = STATE_IDLE; + p.rx_state = RX_STATE_IDLE; + p.tx_state = TX_STATE_IDLE; + + p.response_pending = false; + p.packet_pending = false; usb_rx_word_counter = 0; usb_rx_word_buffer = 0; @@ -109,105 +323,11 @@ void usb_init (void) { } void usb_process (void) { - uint32_t scr = fpga_reg_get(REG_USB_SCR); - - if (scr & USB_SCR_RESET_PENDING) { + if (fpga_reg_get(REG_USB_SCR) & USB_SCR_RESET_PENDING) { usb_init(); fpga_reg_set(REG_USB_SCR, USB_SCR_RESET_ACK); - return; - } - - switch (p.state) { - case STATE_IDLE: { - if (usb_rx_cmd(&p.cmd)) { - p.counter = 0; - p.error = false; - p.dma_in_progress = false; - p.state = STATE_ARGS; - } - break; - } - - case STATE_ARGS: { - if (usb_rx_word(&p.args[p.counter])) { - p.counter += 1; - if (p.counter == 2) { - p.counter = 0; - p.state = STATE_DATA; - } - } - break; - } - - case STATE_DATA: { - switch (p.cmd) { - case 'v': - if (usb_tx_word(cfg_get_version())) { - p.state = STATE_RESPONSE; - } - break; - - case 'c': - if (p.counter == 0) { - cfg_query(p.args); - p.counter += 1; - } - if (usb_tx_word(p.args[1])) { - p.state = STATE_RESPONSE; - } - break; - - case 'C': - cfg_update(p.args); - p.state = STATE_RESPONSE; - break; - - case 't': - if (p.counter == 0) { - cfg_get_time(p.args); - p.counter += 1; - } - if ((p.counter == 1) && usb_tx_word(p.args[0])) { - p.counter += 1; - } - if ((p.counter == 2) && usb_tx_word(p.args[1])) { - p.state = STATE_RESPONSE; - } - break; - - case 'T': - cfg_set_time(p.args); - p.state = STATE_RESPONSE; - break; - - case 'm': - case 'M': - if (!((fpga_reg_get(REG_USB_DMA_SCR) & DMA_SCR_BUSY))) { - if (!p.dma_in_progress) { - fpga_reg_set(REG_USB_DMA_ADDRESS, p.args[0]); - fpga_reg_set(REG_USB_DMA_LENGTH, p.args[1]); - fpga_reg_set(REG_USB_DMA_SCR, (p.cmd == 'M' ? DMA_SCR_DIRECTION : 0) | DMA_SCR_START); - p.dma_in_progress = true; - } else { - p.state = STATE_RESPONSE; - } - } - break; - - default: - p.error = true; - p.state = STATE_RESPONSE; - break; - } - break; - } - - case STATE_RESPONSE: { - if (usb_tx_word((p.error ? ERR_TOKEN : CMP_TOKEN) | p.cmd)) { - p.state = STATE_IDLE; - fpga_reg_set(REG_USB_SCR, USB_SCR_WRITE_FLUSH); - } - break; - } + } else { + usb_rx_process(); + usb_tx_process(); } } diff --git a/sw/controller/src/usb.h b/sw/controller/src/usb.h index 915f1bb..8765624 100644 --- a/sw/controller/src/usb.h +++ b/sw/controller/src/usb.h @@ -2,6 +2,26 @@ #define USB_H__ +#include +#include + + +typedef enum packet_cmd { + PACKET_CMD_DD_REQUEST = 'D', + PACKET_CMD_ISV_OUTPUT = 'I', +} usb_packet_cmd_e; + + +typedef struct usb_tx_info { + uint8_t cmd; + uint32_t data_length; + uint32_t data[4]; + uint32_t dma_length; + uint32_t dma_address; +} usb_tx_info_t; + + +bool usb_enqueue_packet (usb_tx_info_t *info); void usb_init (void); void usb_process (void); diff --git a/sw/pc/.gitignore b/sw/pc/.gitignore index 139d033..a6ea7e6 100644 --- a/sw/pc/.gitignore +++ b/sw/pc/.gitignore @@ -1,4 +1,4 @@ -/__pycache__ +**/__pycache__ /backup /roms /saves diff --git a/sw/pc/lib/sc64.py b/sw/pc/lib/sc64.py new file mode 100644 index 0000000..4935e9c --- /dev/null +++ b/sw/pc/lib/sc64.py @@ -0,0 +1,254 @@ +from io import BufferedReader +from time import sleep +from sc64_64dd import BadBlockError, SixtyFourDiskDrive +from sc64_transport import SC64Transport + +class SC64Exception(Exception): + pass + + +class SC64: + __VERSION_V2 = b'SCv2' + + __CFG_ID_BOOTLOADER_SWITCH = 0 + __CFG_ID_ROM_WRITE_ENABLE = 1 + __CFG_ID_ROM_SHADOW_ENABLE = 2 + __CFG_ID_DD_MODE = 3 + __CFG_ID_ISV_ENABLE = 4 + __CFG_ID_BOOT_MODE = 5 + __CFG_ID_SAVE_TYPE = 6 + __CFG_ID_CIC_SEED = 7 + __CFG_ID_TV_TYPE = 8 + __CFG_ID_FLASH_ERASE_BLOCK = 9 + __CFG_ID_DD_DRIVE_TYPE = 10 + __CFG_ID_DD_DISK_STATE = 11 + + + __SDRAM_ADDRESS = 0x00000000 + __SDRAM_LENGTH = (64 * 1024 * 1024) + __DDIPL_ADDRESS = 0x03BC0000 + __DDIPL_LENGTH = (4 * 1024 * 1024) + __SAVE_ADDRESS = 0x03FE0000 + __SAVE_LENGTH = (128 * 1024) + __FLASH_ADDRESS = 0x04000000 + __FLASH_LENGTH = (16 * 1024 * 1024) + __EXTENDED_ROM_ADDRESS = 0x04000000 + __EXTENDED_ROM_LENGTH = (14 * 1024 * 1024) + __BOOTLOADER_ADDRESS = 0x04E00000 + __BOOTLOADER_LENGTH = (1920 * 1024) + __SHADOW_ADDRESS = 0x04FE0000 + __SHADOW_LENGTH = (128 * 1024) + __BUFFER_ADDRESS = 0x06000000 + __BUFFER_LENGTH = (8 * 1024) + __EEPROM_ADDRESS = 0x06002000 + __EEPROM_LENGTH = (2 * 1024) + __DD_SECTOR_ADDRESS = 0x06006000 + __DD_SECTOR_LENGTH = (2 * 1024) + + __FLASH_ERASE_SIZE = (128 * 1024) + + __CHUNK_SIZE = (128 * 1024) + + __MAX_ROM_SIZE = __SDRAM_LENGTH + __EXTENDED_ROM_LENGTH + + __BOOT_MODE = [ + 'sd', + 'usb', + 'rom', + 'ddipl', + 'direct', + ] + __SAVE_TYPE = [ + 'none', + 'eeprom4k', + 'eeprom16k', + 'sram', + 'flashram', + 'sram3x', + ] + __SAVE_LENGTH = [ + 0, + 512, + (2 * 1024), + (32 * 1024), + (128 * 1024), + (3 * 32 * 1024), + ] + __SAVE_ADDRESS = [ + 0, + __EEPROM_ADDRESS, + __EEPROM_ADDRESS, + __SAVE_ADDRESS, + __SAVE_ADDRESS, + __SAVE_ADDRESS, + ] + __DD_MODE = [ + 'none', + 'full', + 'ddipl', + ] + __DD_DRIVE_TYPE = [ + 'retail', + 'development', + ] + __DD_DISK_STATE = [ + 'ejected', + 'inserted', + 'changed', + ] + + def __init__(self) -> None: + self.__transport = SC64Transport() + self.__transport.connect() + version = self.__transport.execute_cmd(cmd=b'v', args=[0, 0]) + if (version != self.__VERSION_V2): + raise SC64Exception("SC64 version different than expected") + + def __get_int(self, data: bytes) -> int: + return int.from_bytes(data[:4], byteorder="big") + + def __set_config(self, config: int, value: int) -> None: + self.__transport.execute_cmd(b'C', [config, value]) + + def __get_config(self, config: int) -> int: + return int.from_bytes(self.__transport.execute_cmd(b'c', args=[config, 0]), byteorder="big") + + def __write_memory(self, address: int, data: bytes) -> None: + self.__transport.execute_cmd( + cmd=b'M', args=[address, len(data)], data=data) + + def __read_memory(self, address: int, length: int): + return self.__transport.execute_cmd(cmd=b'm', args=[address, length]) + + def __erase_flash_block(self, address: int): + self.__set_config(self.__CFG_ID_FLASH_ERASE_BLOCK, address) + + def __erase_flash(self, address: int, length: int): + if (address < self.__FLASH_ADDRESS): + raise ValueError + if (address + length >= self.__FLASH_ADDRESS + self.__FLASH_LENGTH): + raise ValueError + if (address % self.__FLASH_ERASE_SIZE != 0): + raise ValueError + if (length % self.__FLASH_ERASE_SIZE != 0): + raise ValueError + for offset in range(address, address + length, self.__FLASH_ERASE_SIZE): + self.__erase_flash_block(offset) + + def set_boot_mode(self, boot_mode: str) -> None: + value = self.__BOOT_MODE.index(boot_mode) + self.__set_config(self.__CFG_ID_BOOT_MODE, value) + + def set_save_type(self, save_type: str) -> None: + value = self.__SAVE_TYPE.index(save_type) + self.__set_config(self.__CFG_ID_SAVE_TYPE, value) + + def set_dd_mode(self, dd_mode: str) -> None: + value = self.__DD_MODE.index(dd_mode) + self.__set_config(self.__CFG_ID_DD_MODE, value) + + def set_dd_drive_type(self, dd_drive_type: str) -> None: + value = self.__DD_DRIVE_TYPE.index(dd_drive_type) + self.__set_config(self.__CFG_ID_DD_DRIVE_TYPE, value) + + def set_dd_disk_state(self, dd_disk_state: str) -> None: + value = self.__DD_DISK_STATE.index(dd_disk_state) + self.__set_config(self.__CFG_ID_DD_DISK_STATE, value) + + def upload_rom(self, data: bytes): + if (len(data) > self.__SDRAM_LENGTH): + raise ValueError + self.__write_memory(self.__SDRAM_ADDRESS, data) + + def upload_ddipl(self, data: bytes) -> None: + if (len(data) > self.__DDIPL_LENGTH): + raise ValueError + self.__write_memory(self.__DDIPL_ADDRESS, data) + + def upload_save(self, data: bytes) -> None: + save_type = self.__get_config(self.__CFG_ID_SAVE_TYPE) + if (save_type < 0 or save_type >= len(self.__SAVE_TYPE)): + raise ValueError + if (len(data) != self.__SAVE_LENGTH[save_type]): + raise ValueError + self.__write_memory(self.__SAVE_ADDRESS[save_type], data) + + def upload_bootloader(self, data: bytes) -> None: + if (len(data) > self.__BOOTLOADER_LENGTH): + raise ValueError + self.__erase_flash(self.__BOOTLOADER_ADDRESS, self.__BOOTLOADER_LENGTH) + self.__write_memory(self.__BOOTLOADER_ADDRESS, data) + + def __handle_dd_packet(self, dd: SixtyFourDiskDrive, data: bytes) -> None: + CMD_READ_BLOCK = 1 + CMD_WRITE_BLOCK = 2 + cmd = self.__get_int(data[0:]) + address = self.__get_int(data[4:]) + track_head_block = self.__get_int(data[8:]) + track = (track_head_block >> 2) & 0xFFF + head = (track_head_block >> 1) & 0x1 + block = track_head_block & 0x1 + try: + if (cmd == CMD_READ_BLOCK): + block_data = dd.read_block(track, head, block) + self.__transport.execute_cmd(cmd=b'D', args=[address, len(block_data)], data=block_data) + elif (cmd == CMD_WRITE_BLOCK): + dd.write_block(track, head, block, data[12:]) + self.__transport.execute_cmd(cmd=b'd', args=[0, 0]) + else: + self.__transport.execute_cmd(cmd=b'd', args=[-1, 0]) + except BadBlockError: + self.__transport.execute_cmd(cmd=b'd', args=[1, 0]) + + def __handle_isv_packet(self, data: bytes) -> None: + print(data.decode("EUC-JP", errors="backslashreplace"), end="") + + def debug_server(self, disk_path: str) -> None: + dd = SixtyFourDiskDrive() + dd.load(disk_path) + self.set_dd_drive_type(dd.get_drive_type()) + self.set_dd_disk_state('inserted') + + self.__set_config(self.__CFG_ID_ROM_WRITE_ENABLE, 1) + self.__set_config(self.__CFG_ID_ISV_ENABLE, 1) + self.__set_config(self.__CFG_ID_TV_TYPE, 1) + + while (True): + packet = self.__transport.get_packet() + if (packet != None): + (cmd, data) = packet + if (cmd == b'D'): + self.__handle_dd_packet(dd, data) + if (cmd == b'I'): + self.__handle_isv_packet(data) + + +if __name__ == "__main__": + sc64 = SC64() + + sc64.set_boot_mode('rom') + print('set boot mode') + + sc64.set_dd_mode('none') + print('set dd mode') + + with open('S:/n64/roms/ZELOOTD.z64', 'rb') as f: + sc64.upload_rom(f.read()) + print('uploaded rom') + + # with open('S:/n64/64dd/ipl/NDDJ2.n64', 'rb') as f: + # sc64.upload_ddipl(f.read()) + # print('uploaded ddipl') + + sc64.set_save_type('sram') + print('set save type') + + # with open('S:/n64/saves/SM64.eep', 'rb') as f: + # sc64.upload_save(f.read()) + # print('uploaded save') + + try: + print('starting debug server') + sc64.debug_server(disk_path='S:/n64/64dd/rtl/Mario Artist Polygon Studio.ndd') + except KeyboardInterrupt: + pass diff --git a/sw/pc/lib/sc64_64dd.py b/sw/pc/lib/sc64_64dd.py new file mode 100644 index 0000000..e2eb2b8 --- /dev/null +++ b/sw/pc/lib/sc64_64dd.py @@ -0,0 +1,220 @@ +import sys +from io import BufferedReader +from typing import Optional + + +class BadBlockError(Exception): + pass + + +class SixtyFourDiskDrive: + __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]] + + 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() + + def unload(self) -> None: + if (self.__file != None and not self.__file.closed): + self.__file.close() + self.__drive_type = None + + 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 = SixtyFourDiskDrive() + 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}]") + dd.unload() + else: + print(f"[{sys.argv[0]}]: Expected disk image path as first argument") diff --git a/sw/pc/lib/sc64_transport.py b/sw/pc/lib/sc64_transport.py new file mode 100644 index 0000000..d1e2246 --- /dev/null +++ b/sw/pc/lib/sc64_transport.py @@ -0,0 +1,171 @@ +from serial.tools import list_ports +from threading import Thread +from typing import Optional +import queue +import serial +import time + + +class ConnectionException(Exception): + pass + + +class SC64Transport: + __disconnect = False + __serial = None + __thread_read = None + __thread_write = None + __queue_output = queue.Queue() + __queue_input = queue.Queue() + __queue_packet = queue.Queue() + + def __del__(self) -> None: + self.__disconnect = True + if (self.__thread_read.is_alive()): + self.__thread_read.join(5) + if (self.__thread_write.is_alive()): + self.__thread_write.join(5) + if (self.__serial != None and self.__serial.is_open): + self.__serial.close() + + def connect(self) -> None: + ports = list_ports.comports() + device_found = False + + if (self.__serial != None and self.__serial.is_open): + raise ConnectionException("Serial port is already open") + + for p in ports: + if (p.vid == 0x0403 and p.pid == 0x6014 and p.serial_number.startswith("SC64")): + try: + self.__serial = serial.Serial( + p.device, timeout=1.0, write_timeout=5.0) + self.__reset_link() + except (serial.SerialException, ConnectionException): + if (self.__serial): + self.__serial.close() + continue + device_found = True + break + + if (not device_found): + raise ConnectionException("No SummerCart64 device was found") + + self.__thread_read = Thread( + target=self.__serial_process_input, daemon=True) + self.__thread_write = Thread( + target=self.__serial_process_output, daemon=True) + + self.__thread_read.start() + self.__thread_write.start() + + def __reset_link(self) -> None: + self.__serial.reset_output_buffer() + + retry_counter = 0 + self.__serial.dtr = 1 + while (self.__serial.dsr == 0): + time.sleep(0.1) + retry_counter += 1 + if (retry_counter >= 10): + raise ConnectionException + + self.__serial.reset_input_buffer() + + retry_counter = 0 + self.__serial.dtr = 0 + while (self.__serial.dsr == 1): + time.sleep(0.1) + retry_counter += 1 + if (retry_counter >= 10): + raise ConnectionException + + def __write(self, data: bytes) -> None: + try: + if (self.__disconnect): + raise ConnectionException + self.__serial.write(data) + self.__serial.flush() + except serial.SerialTimeoutException: + raise ConnectionException + except serial.SerialException: + raise ConnectionException + + def __read(self, length: int) -> bytes: + try: + data = b'' + while (len(data) < length and not self.__disconnect): + data += self.__serial.read(length - len(data)) + if (self.__disconnect): + raise ConnectionException + return data + except serial.SerialException: + raise ConnectionException + + def __read_int(self) -> int: + return int.from_bytes(self.__read(4), byteorder="big") + + def __serial_process_output(self) -> None: + while (not self.__disconnect and self.__serial != None and self.__serial.is_open): + try: + packet: bytes = self.__queue_output.get(timeout=0.1) + self.__write(packet) + self.__queue_output.task_done() + except queue.Empty: + continue + except ConnectionException: + break + + def __serial_process_input(self) -> None: + while (not self.__disconnect and self.__serial != None and self.__serial.is_open): + try: + token = self.__read(4) + if (len(token) == 4): + identifier = token[0:3] + command = token[3:4] + if (identifier == b'PKT'): + lenss = self.__read_int() + data = self.__read(lenss) + self.__queue_packet.put((command, data)) + elif (identifier == b'CMP' or identifier == b'ERR'): + data = self.__read(self.__read_int()) + success = identifier == b'CMP' + self.__queue_input.put((command, data, success)) + else: + raise Exception + except ConnectionException: + break + + def __queue_cmd(self, command: bytes, args: list[int] = [], data: bytes = b''): + packet: bytes = b'CMD' + packet += command + for arg in args: + packet += arg.to_bytes(4, byteorder="big") + packet += data + self.__queue_output.put(packet) + + def __pop_response(self, command: bytes) -> bytes: + try: + (response_command, data, success) = self.__queue_input.get(timeout=5) + if (command != response_command or success == False): + raise ConnectionException + return data + except queue.Empty: + raise ConnectionException + + def execute_cmd(self, cmd: bytes, args: list[int] = [], data: bytes = b'', response: bool = True) -> Optional[bytes]: + if (len(cmd) != 1): + raise ValueError("Length of command is different than 1 byte") + command = cmd[0:1] + if (len(args) != 2): + raise ValueError("Number of arguments is different than 2") + self.__queue_cmd(command, args, data) + if (response): + return self.__pop_response(command) + return None + + def get_packet(self) -> Optional[tuple[bytes, bytes]]: + try: + return self.__queue_packet.get(timeout=1) + except queue.Empty: + return None diff --git a/sw/pc/sc64.py b/sw/pc/sc64.py index f51d560..224e595 100644 --- a/sw/pc/sc64.py +++ b/sw/pc/sc64.py @@ -22,23 +22,6 @@ class SC64Exception(Exception): class SC64: - # __CFG_ID_DD_ENABLE = 3 - # __CFG_ID_SAVE_TYPE = 4 - # __CFG_ID_CIC_SEED = 5 - # __CFG_ID_TV_TYPE = 6 - # __CFG_ID_SAVE_OFFEST = 7 - # __CFG_ID_DDIPL_OFFEST = 8 - # __CFG_ID_BOOT_MODE = 9 - # __CFG_ID_FLASH_SIZE = 10 - # __CFG_ID_FLASH_READ = 11 - # __CFG_ID_FLASH_PROGRAM = 12 - # __CFG_ID_RECONFIGURE = 13 - # __CFG_ID_DD_DRIVE_ID = 14 - # __CFG_ID_DD_DISK_STATE = 15 - # __CFG_ID_DD_THB_TABLE_OFFSET = 16 - # __CFG_ID_IS_VIEWER_ENABLE = 17 - - __CFG_ID_BOOTLOADER_SWITCH = 0 __CFG_ID_ROM_WRITE_ENABLE = 1 __CFG_ID_ROM_SHADOW_ENABLE = 2 @@ -57,7 +40,7 @@ class SC64: __BOOTLOADER_OFFSET = (64 + 14) * 1024 * 1024 __SAVE_OFFSET = (64 * 1024 * 1024) - (128 * 1024) __EEPROM_OFFSET = (96 * 1024 * 1024) + 8192 - __DDIPL_OFFSET = 0x3bc0000 # (64 - 16 - 4) * 1024 * 1024 + __DDIPL_OFFSET = 0x03BC0000 __CHUNK_SIZE = 256 * 1024