diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6998d3f..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,29 +0,0 @@ -## Expected Behavior - - - -## Current Behavior - - -## Possible Solution - - -## Steps to Reproduce - - -1. -2. -3. -4. - -## Context (Environment) - - - - - -## Detailed Description - - -## Possible Implementation - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..25c04a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. +2. +3. +4. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Possible solution** +Not obligatory, but suggest a fix/reason for the bug. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d553ba3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: GitHub Discussions + url: https://github.com/Polprzewodnikowy/SummerCart64/discussions + about: Please use for QUESTIONS, conversations or discussions. + - name: N64brew Discord + url: https://discord.gg/8xvRPqCAFC + about: Alternative channel for asking QUESTIONS. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 8bec342..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,20 +0,0 @@ -## Description - - - -## Related Issue - - - - - -## Motivation and Context - - - -## How Has This Been Tested? - - - - -## Screenshots (if appropriate): diff --git a/.github/PULL_REQUEST_TEMPLATE/default.md b/.github/PULL_REQUEST_TEMPLATE/default.md new file mode 100644 index 0000000..d65ba6c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/default.md @@ -0,0 +1,20 @@ +**Description** +Provide a headline summary of your changes in the Title above. +Describe your changes in detail. + +**Related Issue** +This project only accepts pull requests related to open issues. +If suggesting a change, please discuss it in an issue first. +If fixing a bug, there should be an issue describing it with steps to reproduce. +Please link to the issue here: + +**Motivation and Context** +Why is this change required? What problem does it solve? +If it fixes an open issue, please link to the issue here. + +**How Has This Been Tested?** +Please describe in detail how you tested your changes. +Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc. + +**Screenshots** +If applicable, add screenshots to help demonstrate your feature/bugfix. diff --git a/.github/config.yml b/.github/config.yml deleted file mode 100644 index 32dfb6e..0000000 --- a/.github/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: GitHub Discussions - url: https://github.com/Polprzewodnikowy/SummerCart64/discussions - about: Please use for QUESTIONS, conversations or discussions. - - name: N64brew Discord - url: https://discord.gg/8xvRPqCAFC - about: Alternative channel for asking QUESTIONS. diff --git a/build.sh b/build.sh index 32c4fcf..c3583d1 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,6 @@ set -e PACKAGE_FILE_NAME="SC64" TOP_FILES=( - "./sw/pc/dd64.py" "./sw/pc/primer.py" "./sw/pc/requirements.txt" "./sw/pc/sc64.py" diff --git a/docs/00_quick_startup_guide.md b/docs/00_quick_startup_guide.md index 7bcb35f..9b11c4d 100644 --- a/docs/00_quick_startup_guide.md +++ b/docs/00_quick_startup_guide.md @@ -6,6 +6,7 @@ - [Running 64DD games](#running-64dd-games) - [Direct boot option](#direct-boot-option) - [Debug terminal](#debug-terminal) +- [LED blink patters](#led-blink-patters) --- @@ -24,9 +25,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` --- @@ -71,3 +72,21 @@ Run `python3 sc64.py --boot direct-rom --rom path_to_rom.n64` to disable bootloa ## Debug terminal `sc64.py` supports UNFLoader protocol and has same functionality implemented as aforementioned program. Use argument `--debug` to activate it. + +--- + +## LED blink patters + +LED on SC64 board can blink in certain situations. Most of them during normal use are related to SD card access. Here's list of blink patters meaning: + +| Pattern | Meaning | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| Nx [Short ON - Short OFF] | SD card is being accessed (initialization or data read/write) or save writeback is in progress | +| Nx [Medium ON - Long OFF] | CIC region didn't match, please power off console and power on again | +| 2x [Very short ON - Short OFF] | Pattern used during firmware update process, it means that specific part of firmware has started programming | +| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated | +| 30x [Long ON - Long OFF] | There was serious problem during firmware update, device is most likely bricked | + +Nx means that blink count is varied. + +LED blinking on SD card access can be disabled through `sc64.py` script. Please refer to included help for option to change the LED behavior. diff --git a/docs/01_memory_map.md b/docs/01_memory_map.md index d00a635..2912706 100644 --- a/docs/01_memory_map.md +++ b/docs/01_memory_map.md @@ -5,7 +5,7 @@ - [SC64 registers](#sc64-registers) - [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand) - [`0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0-and-0x1fff_0008-data1) - - [`0x1FFF_000C`: **VERSION**](#0x1fff_000c-version) + - [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier) - [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key) - [Command execution flow](#command-execution-flow) @@ -87,13 +87,13 @@ SC64 contains small register region used for communication between N64 and contr Protocol is command based with support for up to 256 diferrent commands and two 32-bit argument/result values per operation. Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately. -| name | address | size | access | usage | -| ------------------ | ------------- | ------- | ------ | -------------------------------- | -| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status | -| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 | -| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 | -| **VERSION** | `0x1FFF_000C` | 4 bytes | RW | Hardware version and IRQ clear | -| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock | +| name | address | size | access | usage | +| ------------------ | ------------- | ------- | ------ | ---------------------------------- | +| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status | +| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 | +| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 | +| **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier and IRQ clear | +| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock | --- @@ -121,11 +121,11 @@ Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset --- -#### `0x1FFF_000C`: **VERSION** +#### `0x1FFF_000C`: **IDENTIFIER** -| name | bits | access | meaning | -| --------- | ------ | ------ | --------------------------------------------- | -| `VERSION` | [31:0] | RW | Hardware version (ASCII `SCv2`) and IRQ clear | +| name | bits | access | meaning | +| ------------ | ------ | ------ | ------------------------------------------------- | +| `IDENTIFIER` | [31:0] | RW | Flashcart identifier (ASCII `SCv2`) and IRQ clear | Note: Writing any value to this register will clear pending flashcart interrupt. diff --git a/docs/02_usb_commands.md b/docs/02_usb_commands.md index a4c71d9..0e26692 100644 --- a/docs/02_usb_commands.md +++ b/docs/02_usb_commands.md @@ -4,25 +4,25 @@ ## USB commands -| id | name | arg0 | arg1 | data | response | description | -| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | --------------------------------------------------- | -| `v` | **HW_VERSION_GET** | --- | --- | --- | hw_version | Get HW version | -| `V` | **API_VERSION_GET** | --- | --- | --- | api_version | Get USB command API version | -| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset entire flashcart state | -| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC disable/mode/seed/checksum | -| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option | -| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option | -| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting | -| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting | -| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value | -| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set RTC value | -| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address | -| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address | -| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness | -| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 | -| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address | -| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address | -| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / get flash block erase size | -| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase | -| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info | -| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage | +| id | name | arg0 | arg1 | data | response | description | +| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | ------------------------------------------------------------- | +| `v` | **IDENTIFIER_GET** | --- | --- | --- | identifier | Get flashcart identifier `SCv2` | +| `V` | **VERSION_GET** | --- | --- | --- | version | Get flashcart firmware version | +| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) | +| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) | +| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option | +| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option | +| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option | +| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option | +| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value | +| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value | +| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address | +| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address | +| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 (no response!) | +| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness | +| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size | +| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase | +| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address | +| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address | +| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info | +| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage | diff --git a/docs/03_n64_commands.md b/docs/03_n64_commands.md index 5417ba8..c12d6bc 100644 --- a/docs/03_n64_commands.md +++ b/docs/03_n64_commands.md @@ -6,14 +6,14 @@ | id | name | arg0 | arg1 | rsp0 | rsp1 | description | | --- | --------------------- | ---------- | ------------ | ---------------- | -------------- | -------------------------------------------------- | -| `v` | **HW_VERSION_GET** | --- | --- | hw_version | --- | Get HW version | -| `V` | **API_VERSION_GET** | --- | --- | api_version | --- | Get N64 command API version | +| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` | +| `V` | **VERSION_GET** | --- | --- | version | --- | Get flashcart firmware version | | `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option | | `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value | | `c` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option | | `C` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option | | `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value | -| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set RTC value | +| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value | | `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart | | `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB | | `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length | @@ -27,4 +27,3 @@ | `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer | | `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size | | `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase | -| `?` | **DEBUG_GET** | --- | --- | debug_data_0 | debug_data_1 | Get internal FPGA debug info | diff --git a/docs/06_manufacturing_guidelines.md b/docs/06_manufacturing_guidelines.md index 03cf6a0..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_firmware.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_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! diff --git a/fw/rtl/mcu/mcu_top.sv b/fw/rtl/mcu/mcu_top.sv index ddd1e7a..3b80958 100644 --- a/fw/rtl/mcu/mcu_top.sv +++ b/fw/rtl/mcu/mcu_top.sv @@ -332,7 +332,7 @@ module mcu_top ( REG_CFG_DATA_0, REG_CFG_DATA_1, REG_CFG_CMD, - REG_CFG_VERSION, + REG_CFG_IDENTIFIER, REG_FLASHRAM_SCR, REG_FLASH_SCR, REG_RTC_SCR, @@ -363,7 +363,7 @@ module mcu_top ( logic bootloader_skip; - assign n64_scb.cfg_version = 32'h53437632; + assign n64_scb.cfg_identifier = 32'h53437632; logic dd_bm_ack; @@ -460,8 +460,8 @@ module mcu_top ( }; end - REG_CFG_VERSION: begin - reg_rdata <= n64_scb.cfg_version; + REG_CFG_IDENTIFIER: begin + reg_rdata <= n64_scb.cfg_identifier; end REG_FLASHRAM_SCR: begin diff --git a/fw/rtl/n64/n64_cfg.sv b/fw/rtl/n64/n64_cfg.sv index e5b4212..0301250 100644 --- a/fw/rtl/n64/n64_cfg.sv +++ b/fw/rtl/n64/n64_cfg.sv @@ -16,8 +16,8 @@ module n64_cfg ( REG_DATA_0_L, REG_DATA_1_H, REG_DATA_1_L, - REG_VERSION_H, - REG_VERSION_L, + REG_IDENTIFIER_H, + REG_IDENTIFIER_L, REG_KEY_H, REG_KEY_L } e_reg; @@ -39,8 +39,8 @@ module n64_cfg ( REG_DATA_0_L: reg_bus.rdata = n64_scb.cfg_wdata[0][15:0]; REG_DATA_1_H: reg_bus.rdata = n64_scb.cfg_wdata[1][31:16]; REG_DATA_1_L: reg_bus.rdata = n64_scb.cfg_wdata[1][15:0]; - REG_VERSION_H: reg_bus.rdata = n64_scb.cfg_version[31:16]; - REG_VERSION_L: reg_bus.rdata = n64_scb.cfg_version[15:0]; + REG_IDENTIFIER_H: reg_bus.rdata = n64_scb.cfg_identifier[31:16]; + REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0]; REG_KEY_H: reg_bus.rdata = 16'd0; REG_KEY_L: reg_bus.rdata = 16'd0; endcase @@ -83,7 +83,7 @@ module n64_cfg ( REG_DATA_0_L: n64_scb.cfg_rdata[0][15:0] <= reg_bus.wdata; REG_DATA_1_H: n64_scb.cfg_rdata[1][31:16] <= reg_bus.wdata; REG_DATA_1_L: n64_scb.cfg_rdata[1][15:0] <= reg_bus.wdata; - REG_VERSION_H: irq <= 1'b0; + REG_IDENTIFIER_H: irq <= 1'b0; REG_KEY_H, REG_KEY_L: begin lock_sequence_counter <= lock_sequence_counter + 1'd1; if (reg_bus.wdata != 16'hFFFF) begin diff --git a/fw/rtl/n64/n64_scb.sv b/fw/rtl/n64/n64_scb.sv index 4e3c00d..106b70a 100644 --- a/fw/rtl/n64/n64_scb.sv +++ b/fw/rtl/n64/n64_scb.sv @@ -51,7 +51,7 @@ interface n64_scb (); logic [7:0] cfg_cmd; logic [31:0] cfg_rdata [0:1]; logic [31:0] cfg_wdata [0:1]; - logic [31:0] cfg_version; + logic [31:0] cfg_identifier; logic [15:0] save_count; @@ -94,7 +94,7 @@ interface n64_scb (); input cfg_cmd, input cfg_rdata, output cfg_wdata, - output cfg_version, + output cfg_identifier, input save_count, @@ -194,7 +194,7 @@ interface n64_scb (); output cfg_cmd, output cfg_rdata, input cfg_wdata, - input cfg_version + input cfg_identifier ); modport save_counter ( diff --git a/sw/bootloader/src/sc64.c b/sw/bootloader/src/sc64.c index 16104b1..c36fab3 100644 --- a/sw/bootloader/src/sc64.c +++ b/sw/bootloader/src/sc64.c @@ -5,7 +5,7 @@ typedef struct { io32_t SR_CMD; io32_t DATA[2]; - io32_t VERSION; + io32_t IDENTIFIER; io32_t KEY; } sc64_regs_t; @@ -16,7 +16,7 @@ typedef struct { #define SC64_SR_CMD_ERROR (1 << 30) #define SC64_SR_CPU_BUSY (1 << 31) -#define SC64_VERSION_2 (0x53437632) +#define SC64_V2_IDENTIFIER (0x53437632) #define SC64_KEY_RESET (0x00000000UL) #define SC64_KEY_UNLOCK_1 (0x5F554E4CUL) @@ -24,8 +24,8 @@ typedef struct { #define SC64_KEY_LOCK (0xFFFFFFFFUL) typedef enum { - SC64_CMD_HW_VERSION_GET = 'v', - SC64_CMD_API_VERSION_GET = 'V', + SC64_CMD_IDENTIFIER_GET = 'v', + SC64_CMD_VERSION_GET = 'V', SC64_CMD_CONFIG_GET = 'c', SC64_CMD_CONFIG_SET = 'C', SC64_CMD_SETTING_GET = 'a', @@ -45,7 +45,6 @@ typedef enum { SC64_CMD_FLASH_PROGRAM = 'K', SC64_CMD_FLASH_WAIT_BUSY = 'p', SC64_CMD_FLASH_ERASE_BLOCK = 'P', - SC64_CMD_DEBUG_GET = '?', } cmd_id_t; typedef enum { @@ -98,8 +97,8 @@ void sc64_lock (void) { } bool sc64_check_presence (void) { - uint32_t version = pi_io_read(&SC64_REGS->VERSION); - if (version == SC64_VERSION_2) { + uint32_t identifier = pi_io_read(&SC64_REGS->IDENTIFIER); + if (identifier == SC64_V2_IDENTIFIER) { sc64_wait_cpu_busy(); return true; } @@ -114,7 +113,7 @@ bool sc64_irq_pending (void) { } void sc64_irq_clear (void) { - pi_io_write(&SC64_REGS->VERSION, 0); + pi_io_write(&SC64_REGS->IDENTIFIER, 0); } uint32_t sc64_get_config (sc64_cfg_id_t id) { diff --git a/sw/bootloader/src/test.c b/sw/bootloader/src/test.c index a9ce44e..392c860 100644 --- a/sw/bootloader/src/test.c +++ b/sw/bootloader/src/test.c @@ -1,92 +1,122 @@ -#include -#include "display.h" -#include "io.h" -#include "sc64.h" -#include "test.h" - - -static void test_rtc (void) { - sc64_rtc_time_t t; - const char *weekdays[8] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; - - sc64_get_time(&t); - - display_printf("RTC current time:\n"); - display_printf(" %02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second)); - display_printf(" %s ", weekdays[FROM_BCD(t.weekday)]); - display_printf("%d.%02d.%04d", FROM_BCD(t.day), FROM_BCD(t.month), 2000 + FROM_BCD(t.year)); - display_printf("\n"); -} - -static void test_sd_card (void) { - sc64_sd_card_status_t card_status; - uint8_t card_info[32] __attribute__((aligned(8))); - - card_status = sc64_sd_card_get_status(); - - if (card_status & SD_CARD_STATUS_INSERTED) { - display_printf("SD card is inserted\n"); - } else { - display_printf("SD card is not inserted\n"); - } - - if (sc64_sd_card_init()) { - display_printf("SD card init error!\n"); - return; - } - - card_status = sc64_sd_card_get_status(); - - if (card_status & SD_CARD_STATUS_INITIALIZED) { - display_printf("SD card is initialized\n"); - } - if (card_status & SD_CARD_STATUS_TYPE_BLOCK) { - display_printf("SD card type is block\n"); - } - if (card_status & SD_CARD_STATUS_50MHZ_MODE) { - display_printf("SD card runs at 50 MHz clock speed\n"); - } - - if (sc64_sd_card_get_info((uint32_t *) (SC64_BUFFERS->BUFFER))) { - display_printf("SD card get info error!\n"); - return; - } - - pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), card_info, sizeof(card_info)); - - display_printf("SD Card registers:\n CSD: 0x"); - for (int i = 0; i < 16; i++) { - display_printf("%02X", card_info[i]); - } - display_printf("\n CID: 0x"); - for (int i = 16; i < 32; i++) { - display_printf("%02X", card_info[i]); - } - display_printf("\n "); - for (int i = 16; i < 32; i++) { - display_printf("%c ", card_info[i] >= ' ' ? card_info[i] : 0xFF); - } -} - - -bool test_check (void) { - if (OS_INFO->reset_type != OS_INFO_RESET_TYPE_COLD) { - return false; - } - return sc64_get_config(CFG_ID_BUTTON_STATE); -} - -void test_execute (void) { - display_init(NULL); - display_printf("SC64 Test suite\n\n"); - - display_printf("[ RTC tests ]\n"); - test_rtc(); - display_printf("\n"); - - display_printf("[ SD card tests ]\n"); - test_sd_card(); - display_printf("\n"); - - while (1); -} +#include +#include "display.h" +#include "io.h" +#include "sc64.h" +#include "test.h" + + +static void test_rtc (void) { + sc64_rtc_time_t t; + const char *weekdays[8] = { + "", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + }; + + sc64_get_time(&t); + + display_printf("RTC current time:\n "); + display_printf("%04d-%02d-%02d", 2000 + FROM_BCD(t.year), FROM_BCD(t.month), FROM_BCD(t.day)); + display_printf("T"); + display_printf("%02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second)); + display_printf(" (%s)", weekdays[FROM_BCD(t.weekday)]); + display_printf("\n"); +} + +static void test_sd_card (void) { + sc64_sd_card_status_t card_status; + uint8_t card_info[32] __attribute__((aligned(8))); + uint8_t sector[512] __attribute__((aligned(8))); + + card_status = sc64_sd_card_get_status(); + + if (card_status & SD_CARD_STATUS_INSERTED) { + display_printf("SD card is inserted\n"); + } else { + display_printf("SD card is not inserted\n"); + } + + if (sc64_sd_card_init()) { + display_printf("SD card init error!\n"); + return; + } + + card_status = sc64_sd_card_get_status(); + + if (card_status & SD_CARD_STATUS_INITIALIZED) { + display_printf("SD card is initialized\n"); + } + if (card_status & SD_CARD_STATUS_TYPE_BLOCK) { + display_printf("SD card type is block\n"); + } + if (card_status & SD_CARD_STATUS_50MHZ_MODE) { + display_printf("SD card runs at 50 MHz clock speed\n"); + } + + if (sc64_sd_card_get_info((uint32_t *) (SC64_BUFFERS->BUFFER))) { + display_printf("SD card get info error!\n"); + return; + } + + pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), card_info, sizeof(card_info)); + + display_printf("SD Card registers:\n CSD: 0x"); + for (int i = 0; i < 16; i++) { + display_printf("%02X ", card_info[i]); + } + display_printf("\n CID: 0x"); + for (int i = 16; i < 32; i++) { + display_printf("%02X ", card_info[i]); + } + display_printf("\n "); + for (int i = 16; i < 32; i++) { + display_printf(" %c ", card_info[i] >= ' ' ? card_info[i] : 0xFF); + } + display_printf("\n"); + + if (sc64_sd_read_sectors((void *) (SC64_BUFFERS->BUFFER), 0, 1)) { + display_printf("SD card read sector 0 error!\n"); + return; + } + + pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), sector, sizeof(sector)); + + display_printf("Sector 0 (0x1BE-0x1DD), partition entry 1/2:\n 0x"); + for (int i = 0; i < 16; i++) { + display_printf("%02X ", sector[0x1BE + i]); + } + display_printf("\n 0x"); + for (int i = 0; i < 16; i++) { + display_printf("%02X ", sector[0x1CE + i]); + } + display_printf("\n"); + display_printf(" Boot signature: 0x%02X%02X\n", sector[510], sector[511]); +} + + +bool test_check (void) { + if (OS_INFO->reset_type != OS_INFO_RESET_TYPE_COLD) { + return false; + } + return sc64_get_config(CFG_ID_BUTTON_STATE); +} + +void test_execute (void) { + display_init(NULL); + display_printf("SC64 Test suite\n\n"); + + display_printf("[ RTC tests ]\n"); + test_rtc(); + display_printf("\n"); + + display_printf("[ SD card tests ]\n"); + test_sd_card(); + display_printf("\n"); + + while (1); +} diff --git a/sw/controller/src/cfg.c b/sw/controller/src/cfg.c index b6a6729..1e1c8e8 100644 --- a/sw/controller/src/cfg.c +++ b/sw/controller/src/cfg.c @@ -221,8 +221,8 @@ static bool cfg_set_save_type (save_type_t save_type) { } -uint32_t cfg_get_version (void) { - return fpga_reg_get(REG_CFG_VERSION); +uint32_t cfg_get_identifier (void) { + return fpga_reg_get(REG_CFG_IDENTIFIER); } bool cfg_query (uint32_t *args) { @@ -468,11 +468,11 @@ void cfg_process (void) { switch (cmd) { case 'v': - args[0] = cfg_get_version(); + args[0] = cfg_get_identifier(); break; case 'V': - args[0] = version_api(API_N64); + args[0] = version_firmware(); break; case 'c': @@ -666,11 +666,6 @@ void cfg_process (void) { } break; - case '?': - args[0] = fpga_reg_get(REG_DEBUG_0); - args[1] = fpga_reg_get(REG_DEBUG_1); - break; - default: cfg_set_error(CFG_ERROR_UNKNOWN_CMD); return; diff --git a/sw/controller/src/cfg.h b/sw/controller/src/cfg.h index ef9efac..cebc36f 100644 --- a/sw/controller/src/cfg.h +++ b/sw/controller/src/cfg.h @@ -16,7 +16,7 @@ typedef enum { } save_type_t; -uint32_t cfg_get_version (void); +uint32_t cfg_get_identifier (void); bool cfg_query (uint32_t *args); bool cfg_update (uint32_t *args); bool cfg_query_setting (uint32_t *args); diff --git a/sw/controller/src/fpga.h b/sw/controller/src/fpga.h index 37d22f3..84521a5 100644 --- a/sw/controller/src/fpga.h +++ b/sw/controller/src/fpga.h @@ -28,7 +28,7 @@ typedef enum { REG_CFG_DATA_0, REG_CFG_DATA_1, REG_CFG_CMD, - REG_CFG_VERSION, + REG_CFG_IDENTIFIER, REG_FLASHRAM_SCR, REG_FLASH_SCR, REG_RTC_SCR, diff --git a/sw/controller/src/sd.c b/sw/controller/src/sd.c index 87682ac..f25df99 100644 --- a/sw/controller/src/sd.c +++ b/sw/controller/src/sd.c @@ -1,478 +1,473 @@ -#include -#include -#include "fpga.h" -#include "hw.h" -#include "led.h" -#include "sd.h" - - -#define SD_INIT_BUFFER_ADDRESS (0x05002800UL) - -#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL) -#define CMD6_ARG_SWITCH_HS (0x80FFFFF1UL) - -#define CMD8_ARG_SUPPLY_VOLTAGE_27_36_V (1 << 8) -#define CMD8_ARG_CHECK_PATTERN (0xAA << 0) - -#define ACMD6_ARG_BUS_WIDTH_4BIT (2 << 0) - -#define ACMD41_ARG_OCR (0x300000 << 0) -#define ACMD41_ARG_HCS (1 << 30) - -#define R3_OCR (0x300000 << 0) -#define R3_CCS (1 << 30) -#define R3_BUSY (1 << 31) - -#define R6_RCA_MASK (0xFFFF0000UL) - -#define R7_SUPPLY_VOLTAGE_27_36_V (1 << 8) -#define R7_CHECK_PATTERN (0xAA << 0) - -#define SWITCH_FUNCTION_CURRENT_LIMIT (SD_INIT_BUFFER_ADDRESS + 0) -#define SWITCH_FUNCTION_GROUP_1 (SD_INIT_BUFFER_ADDRESS + 12) -#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1) - -#define DAT_BLOCK_MAX_COUNT (256) -#define DAT_TIMEOUT_MS (1000) - - -typedef enum { - CLOCK_STOP, - CLOCK_400KHZ, - CLOCK_25MHZ, - CLOCK_50MHZ, -} sd_clock_t; - -typedef enum { - RSP_NONE, - RSP_R1, - RSP_R1b, - RSP_R2, - RSP_R3, - RSP_R6, - RSP_R7, -} rsp_type_t; - -typedef enum { - DAT_READ, - DAT_WRITE, -} dat_mode_t; - - -struct process { - bool card_initialized; - bool card_type_block; - uint32_t rca; - uint8_t csd[16]; - uint8_t cid[16]; - volatile bool timeout; -}; - - -static struct process p; - - -static void sd_trigger_timeout (void) { - p.timeout = true; -} - -static void sd_prepare_timeout (uint16_t value) { - p.timeout = false; - hw_tim_setup(TIM_ID_SD, value, sd_trigger_timeout); -} - -static bool sd_did_timeout (void) { - return p.timeout; -} - -static void sd_clear_timeout (void) { - hw_tim_stop(TIM_ID_SD); - p.timeout = false; -} - -static void sd_set_clock (sd_clock_t mode) { - fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_OFF); - - switch (mode) { - case CLOCK_400KHZ: - fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_400KHZ); - break; - case CLOCK_25MHZ: - fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_25MHZ); - break; - case CLOCK_50MHZ: - fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_50MHZ); - break; - default: - break; - } -} - -static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) { - uint32_t scr; - uint32_t cmd_data; - - cmd_data = ((cmd << SD_CMD_INDEX_BIT) & SD_CMD_INDEX_MASK); - switch (rsp_type) { - case RSP_NONE: - cmd_data |= SD_CMD_SKIP_RESPONSE; - break; - case RSP_R2: - cmd_data |= (SD_CMD_LONG_RESPONSE | SD_CMD_RESERVED_RESPONSE); - break; - case RSP_R3: - cmd_data |= (SD_CMD_IGNORE_CRC | SD_CMD_RESERVED_RESPONSE); - break; - default: - break; - } - - fpga_reg_set(REG_SD_ARG, arg); - fpga_reg_set(REG_SD_CMD, cmd_data); - - do { - scr = fpga_reg_get(REG_SD_SCR); - } while (scr & SD_SCR_CMD_BUSY); - - if (rsp != NULL) { - if (cmd_data & SD_CMD_LONG_RESPONSE) { - uint8_t *rsp_8 = (uint8_t *) (rsp); - for (int i = 0; i < 4; i++) { - uint32_t rsp_data = fpga_reg_get(REG_SD_RSP_3 - i); - uint8_t *rsp_data_8 = (uint8_t *) (&rsp_data); - rsp_data = SWAP32(rsp_data); - for (int i = 0; i < 4; i++) { - *rsp_8++ = *rsp_data_8++; - } - } - } else { - (*(uint32_t *) (rsp)) = fpga_reg_get(REG_SD_RSP_0); - } - } - - if (rsp_type == RSP_R1b) { - do { - scr = fpga_reg_get(REG_SD_SCR); - } while (scr & SD_SCR_CARD_BUSY); - } - - return (scr & SD_SCR_CMD_ERROR); -} - -static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) { - if (sd_cmd(55, p.rca, RSP_R1, NULL)) { - return true; - } - if (sd_cmd(acmd, arg, rsp_type, rsp)) { - return true; - } - return false; -} - -static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) { - uint32_t length = (count * SD_SECTOR_SIZE); - uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH); - uint32_t sd_dma_scr = DMA_SCR_START; - - if (mode == DAT_READ) { - sd_dat |= SD_DAT_START_READ; - sd_dma_scr |= DMA_SCR_DIRECTION; - } else { - sd_dat |= SD_DAT_START_WRITE; - } - - fpga_reg_set(REG_SD_DAT, sd_dat); - fpga_reg_set(REG_SD_DMA_ADDRESS, address); - fpga_reg_set(REG_SD_DMA_LENGTH, length); - fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr); -} - -static void sd_dat_abort (void) { - fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP); - fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH); -} - -static bool sd_dat_wait (uint16_t timeout) { - sd_prepare_timeout(timeout); - - do { - uint32_t sd_dat = fpga_reg_get(REG_SD_DAT); - uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR); - led_blink_act(); - if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) { - sd_clear_timeout(); - return (sd_dat & SD_DAT_ERROR); - } - } while (!sd_did_timeout()); - - sd_dat_abort(); - - return true; -} - - -bool sd_card_init (void) { - uint32_t arg; - uint32_t rsp; - uint16_t tmp; - - if (p.card_initialized) { - return false; - } - - p.card_initialized = true; - p.rca = 0; - - led_blink_act(); - - sd_set_clock(CLOCK_400KHZ); - - sd_cmd(0, 0, RSP_NONE, NULL); - - arg = (CMD8_ARG_SUPPLY_VOLTAGE_27_36_V | CMD8_ARG_CHECK_PATTERN); - if (sd_cmd(8, arg, RSP_R7, &rsp)) { - arg = ACMD41_ARG_OCR; - } else { - if (rsp != (R7_SUPPLY_VOLTAGE_27_36_V | R7_CHECK_PATTERN)) { - sd_card_deinit(); - return true; - } - arg = (ACMD41_ARG_HCS | ACMD41_ARG_OCR); - } - - sd_prepare_timeout(1000); - do { - if (sd_did_timeout()) { - sd_card_deinit(); - return true; - } - if (sd_acmd(41, arg, RSP_R3, &rsp)) { - sd_card_deinit(); - return true; - } - if (rsp & R3_BUSY) { - if ((rsp & R3_OCR) == 0) { - sd_card_deinit(); - return true; - } - p.card_type_block = (rsp & R3_CCS); - break; - } - } while (1); - sd_clear_timeout(); - - if (sd_cmd(2, 0, RSP_R2, NULL)) { - sd_card_deinit(); - return true; - } - - if (sd_cmd(3, 0, RSP_R6, &rsp)) { - sd_card_deinit(); - return true; - } - p.rca = (rsp & R6_RCA_MASK); - - if (sd_cmd(9, p.rca, RSP_R2, p.csd)) { - sd_card_deinit(); - return true; - } - - if (sd_cmd(10, p.rca, RSP_R2, p.cid)) { - sd_card_deinit(); - return true; - } - - if (sd_cmd(7, p.rca, RSP_R1b, NULL)) { - sd_card_deinit(); - return true; - } - - sd_set_clock(CLOCK_25MHZ); - - if (sd_acmd(6, ACMD6_ARG_BUS_WIDTH_4BIT, RSP_R1, NULL)) { - sd_card_deinit(); - return true; - } - - sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ); - if (sd_cmd(6, CMD6_ARG_CHECK_HS, RSP_R1, NULL)) { - sd_dat_abort(); - sd_card_deinit(); - return true; - } - sd_dat_wait(DAT_TIMEOUT_MS); - if (sd_did_timeout()) { - sd_card_deinit(); - return true; - } - fpga_mem_read(SWITCH_FUNCTION_CURRENT_LIMIT, 2, (uint8_t *) (&tmp)); - if (SWAP16(tmp) == 0) { - sd_card_deinit(); - return true; - } - fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp)); - if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) { - sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ); - if (sd_cmd(6, CMD6_ARG_SWITCH_HS, RSP_R1, NULL)) { - sd_dat_abort(); - sd_card_deinit(); - return true; - } - sd_dat_wait(DAT_TIMEOUT_MS); - if (sd_did_timeout()) { - sd_card_deinit(); - return true; - } - fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp)); - if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) { - sd_set_clock(CLOCK_50MHZ); - } - } - - return false; -} - -void sd_card_deinit (void) { - if (p.card_initialized) { - p.card_initialized = false; - sd_set_clock(CLOCK_400KHZ); - sd_cmd(0, 0, RSP_NONE, NULL); - sd_set_clock(CLOCK_STOP); - } -} - -bool sd_card_is_inserted (void) { - return (fpga_reg_get(REG_SD_SCR) & SD_SCR_CARD_INSERTED); -} - -uint32_t sd_card_get_status (void) { - uint32_t scr = fpga_reg_get(REG_SD_SCR); - uint32_t clock_mode_50mhz = ((scr & SD_SCR_CLOCK_MODE_MASK) == SD_SCR_CLOCK_MODE_50MHZ) ? 1 : 0; - uint32_t card_type_block = p.card_type_block ? 1 : 0; - uint32_t card_initialized = p.card_initialized ? 1 : 0; - uint32_t card_inserted = (scr & SD_SCR_CARD_INSERTED) ? 1 : 0; - return ( - (clock_mode_50mhz << 3) | - (card_type_block << 2) | - (card_initialized << 1) | - (card_inserted << 0) - ); -} - -bool sd_card_get_info (uint32_t address) { - if (!p.card_initialized) { - return true; - } - fpga_mem_write(address, sizeof(p.csd), p.csd); - address += sizeof(p.csd); - fpga_mem_write(address, sizeof(p.cid), p.cid); - address += sizeof(p.cid); - return false; -} - -bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) { - if (!p.card_initialized || (count == 0)) { - return true; - } - - if (!p.card_type_block) { - sector *= SD_SECTOR_SIZE; - } - - while (count > 0) { - uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count); - led_blink_act(); - if (sd_cmd(23, blocks, RSP_R1, NULL)) { - return true; - } - if (sd_cmd(25, sector, RSP_R1, NULL)) { - return true; - } - sd_dat_prepare(address, blocks, DAT_WRITE); - if (sd_dat_wait(DAT_TIMEOUT_MS)) { - sd_dat_abort(); - sd_cmd(12, 0, RSP_R1b, NULL); - return true; - } - sd_cmd(12, 0, RSP_R1b, NULL); - address += (blocks * SD_SECTOR_SIZE); - sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE)); - count -= blocks; - } - - return false; -} - -bool sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) { - if (!p.card_initialized || (count == 0)) { - return true; - } - - if (!p.card_type_block) { - sector *= SD_SECTOR_SIZE; - } - - while (count > 0) { - uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count); - led_blink_act(); - sd_dat_prepare(address, blocks, DAT_READ); - if (sd_cmd(23, blocks, RSP_R1, NULL)) { - sd_dat_abort(); - return true; - } - if (sd_cmd(18, sector, RSP_R1, NULL)) { - sd_dat_abort(); - return true; - } - if (sd_dat_wait(DAT_TIMEOUT_MS)) { - if (sd_did_timeout()) { - sd_cmd(12, 0, RSP_R1b, NULL); - } - return true; - } - address += (blocks * SD_SECTOR_SIZE); - sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE)); - count -= blocks; - } - - return false; -} - -bool sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors) { - uint32_t starting_sector = 0; - uint32_t sectors_to_process = 0; - - if (count == 0) { - return true; - } - - for (uint32_t i = 0; i < count; i++) { - if (sector_table[i] == 0) { - return true; - } - sectors_to_process += 1; - if ((i < (count - 1)) && ((sector_table[i] + 1) == sector_table[i + 1])) { - continue; - } - bool error = sd_process_sectors(address, sector_table[starting_sector], sectors_to_process); - if (error) { - return true; - } - address += (sectors_to_process * SD_SECTOR_SIZE); - starting_sector += sectors_to_process; - sectors_to_process = 0; - } - - return false; -} - -void sd_init (void) { - p.card_initialized = false; - sd_set_clock(CLOCK_STOP); -} - -void sd_process (void) { - if (!sd_card_is_inserted()) { - sd_card_deinit(); - } -} +#include +#include +#include "fpga.h" +#include "hw.h" +#include "led.h" +#include "sd.h" + + +#define SD_INIT_BUFFER_ADDRESS (0x05002800UL) + +#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL) +#define CMD6_ARG_SWITCH_HS (0x80FFFFF1UL) + +#define CMD8_ARG_SUPPLY_VOLTAGE_27_36_V (1 << 8) +#define CMD8_ARG_CHECK_PATTERN (0xAA << 0) + +#define ACMD6_ARG_BUS_WIDTH_4BIT (2 << 0) + +#define ACMD41_ARG_OCR (0x300000 << 0) +#define ACMD41_ARG_HCS (1 << 30) + +#define R3_OCR (0x300000 << 0) +#define R3_CCS (1 << 30) +#define R3_BUSY (1 << 31) + +#define R6_RCA_MASK (0xFFFF0000UL) + +#define R7_SUPPLY_VOLTAGE_27_36_V (1 << 8) +#define R7_CHECK_PATTERN (0xAA << 0) + +#define SWITCH_FUNCTION_CURRENT_LIMIT (SD_INIT_BUFFER_ADDRESS + 0) +#define SWITCH_FUNCTION_GROUP_1 (SD_INIT_BUFFER_ADDRESS + 12) +#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1) + +#define DAT_BLOCK_MAX_COUNT (256) +#define DAT_TIMEOUT_INIT_MS (2000) +#define DAT_TIMEOUT_DATA_MS (5000) + + +typedef enum { + CLOCK_STOP, + CLOCK_400KHZ, + CLOCK_25MHZ, + CLOCK_50MHZ, +} sd_clock_t; + +typedef enum { + RSP_NONE, + RSP_R1, + RSP_R1b, + RSP_R2, + RSP_R3, + RSP_R6, + RSP_R7, +} rsp_type_t; + +typedef enum { + DAT_READ, + DAT_WRITE, +} dat_mode_t; + + +struct process { + bool card_initialized; + bool card_type_block; + uint32_t rca; + uint8_t csd[16]; + uint8_t cid[16]; + volatile bool timeout; +}; + + +static struct process p; + + +static void sd_trigger_timeout (void) { + p.timeout = true; +} + +static void sd_prepare_timeout (uint16_t value) { + p.timeout = false; + hw_tim_setup(TIM_ID_SD, value, sd_trigger_timeout); +} + +static bool sd_did_timeout (void) { + return p.timeout; +} + +static void sd_clear_timeout (void) { + hw_tim_stop(TIM_ID_SD); + p.timeout = false; +} + +static void sd_set_clock (sd_clock_t mode) { + fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_OFF); + + switch (mode) { + case CLOCK_400KHZ: + fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_400KHZ); + break; + case CLOCK_25MHZ: + fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_25MHZ); + break; + case CLOCK_50MHZ: + fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_50MHZ); + break; + default: + break; + } +} + +static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) { + uint32_t scr; + uint32_t cmd_data; + + cmd_data = ((cmd << SD_CMD_INDEX_BIT) & SD_CMD_INDEX_MASK); + switch (rsp_type) { + case RSP_NONE: + cmd_data |= SD_CMD_SKIP_RESPONSE; + break; + case RSP_R2: + cmd_data |= (SD_CMD_LONG_RESPONSE | SD_CMD_RESERVED_RESPONSE); + break; + case RSP_R3: + cmd_data |= (SD_CMD_IGNORE_CRC | SD_CMD_RESERVED_RESPONSE); + break; + default: + break; + } + + fpga_reg_set(REG_SD_ARG, arg); + fpga_reg_set(REG_SD_CMD, cmd_data); + + do { + scr = fpga_reg_get(REG_SD_SCR); + } while (scr & SD_SCR_CMD_BUSY); + + if (rsp != NULL) { + if (cmd_data & SD_CMD_LONG_RESPONSE) { + uint8_t *rsp_8 = (uint8_t *) (rsp); + for (int i = 0; i < 4; i++) { + uint32_t rsp_data = fpga_reg_get(REG_SD_RSP_3 - i); + uint8_t *rsp_data_8 = (uint8_t *) (&rsp_data); + rsp_data = SWAP32(rsp_data); + for (int i = 0; i < 4; i++) { + *rsp_8++ = *rsp_data_8++; + } + } + } else { + (*(uint32_t *) (rsp)) = fpga_reg_get(REG_SD_RSP_0); + } + } + + if (rsp_type == RSP_R1b) { + do { + scr = fpga_reg_get(REG_SD_SCR); + } while (scr & SD_SCR_CARD_BUSY); + } + + return (scr & SD_SCR_CMD_ERROR); +} + +static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) { + if (sd_cmd(55, p.rca, RSP_R1, NULL)) { + return true; + } + if (sd_cmd(acmd, arg, rsp_type, rsp)) { + return true; + } + return false; +} + +static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) { + uint32_t length = (count * SD_SECTOR_SIZE); + uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH); + uint32_t sd_dma_scr = DMA_SCR_START; + + if (mode == DAT_READ) { + sd_dat |= SD_DAT_START_READ; + sd_dma_scr |= DMA_SCR_DIRECTION; + } else { + sd_dat |= SD_DAT_START_WRITE; + } + + fpga_reg_set(REG_SD_DAT, sd_dat); + fpga_reg_set(REG_SD_DMA_ADDRESS, address); + fpga_reg_set(REG_SD_DMA_LENGTH, length); + fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr); +} + +static void sd_dat_abort (void) { + fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP); + fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH); +} + +static bool sd_dat_wait (uint16_t timeout) { + sd_prepare_timeout(timeout); + + do { + uint32_t sd_dat = fpga_reg_get(REG_SD_DAT); + uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR); + led_blink_act(); + if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) { + sd_clear_timeout(); + return (sd_dat & SD_DAT_ERROR); + } + } while (!sd_did_timeout()); + + sd_dat_abort(); + + return true; +} + + +bool sd_card_init (void) { + uint32_t arg; + uint32_t rsp; + uint16_t tmp; + + if (p.card_initialized) { + return false; + } + + p.card_initialized = true; + p.rca = 0; + + led_blink_act(); + + sd_set_clock(CLOCK_400KHZ); + + sd_cmd(0, 0, RSP_NONE, NULL); + + arg = (CMD8_ARG_SUPPLY_VOLTAGE_27_36_V | CMD8_ARG_CHECK_PATTERN); + if (sd_cmd(8, arg, RSP_R7, &rsp)) { + arg = ACMD41_ARG_OCR; + } else { + if (rsp != (R7_SUPPLY_VOLTAGE_27_36_V | R7_CHECK_PATTERN)) { + sd_card_deinit(); + return true; + } + arg = (ACMD41_ARG_HCS | ACMD41_ARG_OCR); + } + + sd_prepare_timeout(1000); + do { + if (sd_did_timeout()) { + sd_card_deinit(); + return true; + } + if (sd_acmd(41, arg, RSP_R3, &rsp)) { + sd_card_deinit(); + return true; + } + if (rsp & R3_BUSY) { + if ((rsp & R3_OCR) == 0) { + sd_card_deinit(); + return true; + } + p.card_type_block = (rsp & R3_CCS); + break; + } + } while (1); + sd_clear_timeout(); + + if (sd_cmd(2, 0, RSP_R2, NULL)) { + sd_card_deinit(); + return true; + } + + if (sd_cmd(3, 0, RSP_R6, &rsp)) { + sd_card_deinit(); + return true; + } + p.rca = (rsp & R6_RCA_MASK); + + if (sd_cmd(9, p.rca, RSP_R2, p.csd)) { + sd_card_deinit(); + return true; + } + + if (sd_cmd(10, p.rca, RSP_R2, p.cid)) { + sd_card_deinit(); + return true; + } + + if (sd_cmd(7, p.rca, RSP_R1b, NULL)) { + sd_card_deinit(); + return true; + } + + sd_set_clock(CLOCK_25MHZ); + + if (sd_acmd(6, ACMD6_ARG_BUS_WIDTH_4BIT, RSP_R1, NULL)) { + sd_card_deinit(); + return true; + } + + sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ); + if (sd_cmd(6, CMD6_ARG_CHECK_HS, RSP_R1, NULL)) { + sd_dat_abort(); + sd_card_deinit(); + return true; + } + sd_dat_wait(DAT_TIMEOUT_INIT_MS); + if (sd_did_timeout()) { + sd_card_deinit(); + return true; + } + fpga_mem_read(SWITCH_FUNCTION_CURRENT_LIMIT, 2, (uint8_t *) (&tmp)); + if (SWAP16(tmp) == 0) { + sd_card_deinit(); + return true; + } + fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp)); + if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) { + sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ); + if (sd_cmd(6, CMD6_ARG_SWITCH_HS, RSP_R1, NULL)) { + sd_dat_abort(); + sd_card_deinit(); + return true; + } + sd_dat_wait(DAT_TIMEOUT_INIT_MS); + if (sd_did_timeout()) { + sd_card_deinit(); + return true; + } + fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp)); + if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) { + sd_set_clock(CLOCK_50MHZ); + } + } + + return false; +} + +void sd_card_deinit (void) { + if (p.card_initialized) { + p.card_initialized = false; + sd_set_clock(CLOCK_400KHZ); + sd_cmd(0, 0, RSP_NONE, NULL); + sd_set_clock(CLOCK_STOP); + } +} + +bool sd_card_is_inserted (void) { + return (fpga_reg_get(REG_SD_SCR) & SD_SCR_CARD_INSERTED); +} + +uint32_t sd_card_get_status (void) { + uint32_t scr = fpga_reg_get(REG_SD_SCR); + uint32_t clock_mode_50mhz = ((scr & SD_SCR_CLOCK_MODE_MASK) == SD_SCR_CLOCK_MODE_50MHZ) ? 1 : 0; + uint32_t card_type_block = p.card_type_block ? 1 : 0; + uint32_t card_initialized = p.card_initialized ? 1 : 0; + uint32_t card_inserted = (scr & SD_SCR_CARD_INSERTED) ? 1 : 0; + return ( + (clock_mode_50mhz << 3) | + (card_type_block << 2) | + (card_initialized << 1) | + (card_inserted << 0) + ); +} + +bool sd_card_get_info (uint32_t address) { + if (!p.card_initialized) { + return true; + } + fpga_mem_write(address, sizeof(p.csd), p.csd); + address += sizeof(p.csd); + fpga_mem_write(address, sizeof(p.cid), p.cid); + address += sizeof(p.cid); + return false; +} + +bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) { + if (!p.card_initialized || (count == 0)) { + return true; + } + + if (!p.card_type_block) { + sector *= SD_SECTOR_SIZE; + } + + while (count > 0) { + uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count); + led_blink_act(); + if (sd_cmd(25, sector, RSP_R1, NULL)) { + return true; + } + sd_dat_prepare(address, blocks, DAT_WRITE); + if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) { + sd_dat_abort(); + sd_cmd(12, 0, RSP_R1b, NULL); + return true; + } + sd_cmd(12, 0, RSP_R1b, NULL); + address += (blocks * SD_SECTOR_SIZE); + sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE)); + count -= blocks; + } + + return false; +} + +bool sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) { + if (!p.card_initialized || (count == 0)) { + return true; + } + + if (!p.card_type_block) { + sector *= SD_SECTOR_SIZE; + } + + while (count > 0) { + uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count); + led_blink_act(); + sd_dat_prepare(address, blocks, DAT_READ); + if (sd_cmd(18, sector, RSP_R1, NULL)) { + sd_dat_abort(); + return true; + } + if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) { + if (sd_did_timeout()) { + sd_cmd(12, 0, RSP_R1b, NULL); + } + return true; + } + sd_cmd(12, 0, RSP_R1b, NULL); + address += (blocks * SD_SECTOR_SIZE); + sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE)); + count -= blocks; + } + + return false; +} + +bool sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors) { + uint32_t starting_sector = 0; + uint32_t sectors_to_process = 0; + + if (count == 0) { + return true; + } + + for (uint32_t i = 0; i < count; i++) { + if (sector_table[i] == 0) { + return true; + } + sectors_to_process += 1; + if ((i < (count - 1)) && ((sector_table[i] + 1) == sector_table[i + 1])) { + continue; + } + bool error = sd_process_sectors(address, sector_table[starting_sector], sectors_to_process); + if (error) { + return true; + } + address += (sectors_to_process * SD_SECTOR_SIZE); + starting_sector += sectors_to_process; + sectors_to_process = 0; + } + + return false; +} + +void sd_init (void) { + p.card_initialized = false; + sd_set_clock(CLOCK_STOP); +} + +void sd_process (void) { + if (!sd_card_is_inserted()) { + sd_card_deinit(); + } +} diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index f10385a..a694694 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -160,14 +160,14 @@ static void usb_rx_process (void) { p.rx_state = RX_STATE_IDLE; p.response_pending = true; p.response_info.data_length = 4; - p.response_info.data[0] = cfg_get_version(); + p.response_info.data[0] = cfg_get_identifier(); break; case 'V': p.rx_state = RX_STATE_IDLE; p.response_pending = true; p.response_info.data_length = 4; - p.response_info.data[0] = version_api(API_USB); + p.response_info.data[0] = version_firmware(); break; case 'R': @@ -247,12 +247,6 @@ static void usb_rx_process (void) { } break; - case 'D': - dd_set_block_ready(p.rx_args[0] == 0); - p.rx_state = RX_STATE_IDLE; - p.response_pending = true; - break; - case 'U': if ((p.read_length > 0) && usb_dma_ready()) { uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length; @@ -274,6 +268,28 @@ static void usb_rx_process (void) { } break; + case 'D': + dd_set_block_ready(p.rx_args[0] == 0); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + break; + + case 'p': + if (p.rx_args[0]) { + flash_wait_busy(); + } + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + p.response_info.data_length = 4; + p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE; + break; + + case 'P': + p.response_error = flash_erase_block(p.rx_args[0]); + p.rx_state = RX_STATE_IDLE; + p.response_pending = true; + break; + case 'f': { bool rom_write_enable_restore = cfg_set_rom_write_enable(false); p.response_info.data[0] = update_backup(p.rx_args[0], &p.response_info.data[1]); @@ -300,22 +316,6 @@ static void usb_rx_process (void) { break; } - case 'p': - if (p.rx_args[0]) { - flash_wait_busy(); - } - p.rx_state = RX_STATE_IDLE; - p.response_pending = true; - p.response_info.data_length = 4; - p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE; - break; - - case 'P': - p.response_error = flash_erase_block(p.rx_args[0]); - p.rx_state = RX_STATE_IDLE; - p.response_pending = true; - break; - case '?': p.rx_state = RX_STATE_IDLE; p.response_pending = true; diff --git a/sw/controller/src/version.c b/sw/controller/src/version.c index 3186464..fb992ad 100644 --- a/sw/controller/src/version.c +++ b/sw/controller/src/version.c @@ -1,17 +1,10 @@ #include "version.h" -#define VERSION_API_USB (2) -#define VERSION_API_N64 (2) +#define VERSION_MAJOR (2) +#define VERSION_MINOR (12) -uint32_t version_api (version_api_type_t type) { - switch (type) { - case API_USB: - return VERSION_API_USB; - case API_N64: - return VERSION_API_N64; - default: - return 0; - } +uint32_t version_firmware (void) { + return ((VERSION_MAJOR << 16) | (VERSION_MINOR)); } diff --git a/sw/controller/src/version.h b/sw/controller/src/version.h index be0581d..b5a3c0a 100644 --- a/sw/controller/src/version.h +++ b/sw/controller/src/version.h @@ -5,13 +5,7 @@ #include -typedef enum { - API_USB, - API_N64, -} version_api_type_t; - - -uint32_t version_api (version_api_type_t type); +uint32_t version_firmware (void); #endif 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 e3eab5f..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 @@ -200,6 +404,7 @@ class SC64: FLASH = 0x0400_0000 BUFFER = 0x0500_0000 EEPROM = 0x0500_2000 + END = 0x0500_297F FIRMWARE = 0x0200_0000 DDIPL = 0x03BC_0000 SAVE = 0x03FE_0000 @@ -321,7 +526,8 @@ class SC64: SCREENSHOT = 4 GDB = 0xDB - __MIN_SUPPORTED_API_VERSION = 2 + __SUPPORTED_MAJOR_VERSION = 2 + __SUPPORTED_MINOR_VERSION = 12 __isv_line_buffer: bytes = b'' __debug_header: Optional[bytes] = None @@ -329,21 +535,25 @@ class SC64: def __init__(self) -> None: self.__link = SC64Serial() - version = self.__link.execute_cmd(cmd=b'v') - if (version != b'SCv2'): - raise ConnectionException('Unknown SC64 HW version') + identifier = self.__link.execute_cmd(cmd=b'v') + if (identifier != b'SCv2'): + raise ConnectionException('Unknown SC64 v2 identifier') def __get_int(self, data: bytes) -> int: return int.from_bytes(data[:4], byteorder='big') - def check_api_version(self) -> None: + def check_firmware_version(self) -> tuple[str, bool]: try: - data = self.__link.execute_cmd(cmd=b'V') + version = self.__link.execute_cmd(cmd=b'V') + major = self.__get_int(version[0:2]) + minor = self.__get_int(version[2:4]) + if (major != self.__SUPPORTED_MAJOR_VERSION): + raise ConnectionException() + if (minor < self.__SUPPORTED_MINOR_VERSION): + raise ConnectionException() + return (f'{major}.{minor}', minor > self.__SUPPORTED_MINOR_VERSION) except ConnectionException: - raise ConnectionException('Outdated SC64 API, please update firmware') - version = self.__get_int(data) - if (version < self.__MIN_SUPPORTED_API_VERSION): - raise ConnectionException('Unsupported SC64 API version, please update firmware') + raise ConnectionException(f'Unsupported SC64 version [{major}.{minor}], please update firmware') def __set_config(self, config: __CfgId, value: int) -> None: try: @@ -461,8 +671,10 @@ class SC64: raise ValueError('Debug data size too big') self.__link.execute_cmd(cmd=b'U', args=[datatype, len(data)], data=data, response=False) - def download_memory(self) -> bytes: - return self.__read_memory(self.__Address.MEMORY, self.__Length.MEMORY) + def download_memory(self, address: int, length: int) -> bytes: + if ((address < 0) or (length < 0) or ((address + length) > self.__Address.END)): + raise ValueError('Invalid address or length') + return self.__read_memory(address, length) def upload_rom(self, data: bytes, use_shadow: bool=True) -> None: rom_length = len(data) @@ -582,7 +794,7 @@ class SC64: raise ConnectionException('Error while getting firmware backup') return self.__read_memory(address, length) - def set_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]: + def update_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]: if ((seed != None) and (seed < 0 or seed > 0xFF)): raise ValueError('CIC seed outside of allowed values') boot_mode = self.__get_config(self.__CfgId.BOOT_MODE) @@ -884,7 +1096,7 @@ class SC64: current_image = 0 next_image = 0 - self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, isv) + self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, 1 if (isv != 0) else 0) self.__set_config(self.__CfgId.ISV_ADDRESS, isv) if (isv != 0): print(f'IS-Viewer64 support set to [ENABLED] at ROM offset [0x{(isv):08X}]') @@ -992,16 +1204,24 @@ if __name__ == '__main__': disabled = bool(int(params[1])) if len(params) >= 2 else None return (seed, disabled) + def download_memory_type(argument: str): + params = argument.split(',') + if (len(params) < 2 or len(params) > 3): + raise argparse.ArgumentError() + address = int(params[0], 0) + length = int(params[1], 0) + file = params[2] if len(params) >= 3 else 'sc64dump.bin' + return (address, length, file) + parser = argparse.ArgumentParser(description='SC64 control software') parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file') parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file') - parser.add_argument('--update-bootloader', metavar='file', help='update SC64 bootloader (not recommended, use --update-firmware instead)') parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state') parser.add_argument('--print-state', action='store_true', help='print SC64 internal state') - parser.add_argument('--led-enabled', metavar='{true,false}', help='disable or enable LED I/O activity blinking') + parser.add_argument('--led-blink', metavar='{yes,no}', help='disable or enable LED I/O activity blinking') parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode') - parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value') - parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value') + parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value, not used when direct boot mode is enabled') + parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value, not used when direct boot mode is enabled') parser.add_argument('--cic-params', metavar='seed,[disabled]', type=cic_params_type, help='set CIC emulation parameters') parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time') parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)') @@ -1014,7 +1234,7 @@ if __name__ == '__main__': parser.add_argument('--isv', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset') parser.add_argument('--gdb', metavar='port', type=int, help='expose socket port for GDB debugging') parser.add_argument('--debug', action='store_true', help='run debug loop') - parser.add_argument('--download-memory', metavar='file', help='download whole memory space and write it to specified file') + parser.add_argument('--download-memory', metavar='address,length,[file]', type=download_memory_type, help='download specified memory region and write it to file') if (len(sys.argv) <= 1): parser.print_help() @@ -1039,13 +1259,15 @@ if __name__ == '__main__': sc64.update_firmware(f.read(), status_callback) print('done') - sc64.check_api_version() + (version, script_outdated) = sc64.check_firmware_version() - if (args.update_bootloader): - with open(args.update_bootloader, 'rb') as f: - print('Uploading Bootloader... ', end='', flush=True) - sc64.upload_bootloader(f.read()) - print('done') + print(f'SC64 firmware version: [{version}]') + if (script_outdated): + print('\x1b[33m') + print('[ SC64 firmware is newer than last known version. ]') + print('[ Consider downloading latest script from ]') + print('[ https://github.com/Polprzewodnikowy/SummerCart64/releases ]') + print('\x1b[0m') if (args.reset_state): sc64.reset_state() @@ -1059,10 +1281,10 @@ if __name__ == '__main__': value = getattr(value, 'name') print(f' {key}: {value}') - if (args.led_enabled != None): - value = (args.led_enabled == 'true') - sc64.set_led_enable(value) - print(f'LED blinking set to [{"ENABLED" if value else "DISABLED"}]') + if (args.led_blink): + blink = (args.led_blink == 'yes') + sc64.set_led_enable(blink) + print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]') if (args.tv != None): sc64.set_tv_type(args.tv) @@ -1085,6 +1307,12 @@ if __name__ == '__main__': autodetected_save_type = sc64.autodetect_save_type(rom_data) print('done') + if (args.ddipl): + with open(args.ddipl, 'rb') as f: + print('Uploading 64DD IPL... ', end='', flush=True) + sc64.upload_ddipl(f.read()) + print('done') + if (args.save_type != None or autodetected_save_type != None): save_type = args.save_type if args.save_type != None else autodetected_save_type sc64.set_save_type(save_type) @@ -1096,26 +1324,20 @@ if __name__ == '__main__': sc64.upload_save(f.read()) print('done') - if (args.ddipl): - with open(args.ddipl, 'rb') as f: - print('Uploading 64DD IPL... ', end='', flush=True) - sc64.upload_ddipl(f.read()) - print('done') - if (args.boot != None): sc64.set_boot_mode(args.boot) print(f'Boot mode set to [{args.boot.name}]') if (args.cic_params != None): (seed, disabled) = args.cic_params - (seed, checksum, dd_mode) = sc64.set_cic_parameters(seed, disabled) + (seed, checksum, dd_mode) = sc64.update_cic_parameters(seed, disabled) print('CIC parameters set to [', end='') print(f'{"DISABLED" if disabled else "ENABLED"}, ', end='') print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='') print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='') print(']') else: - sc64.set_cic_parameters() + sc64.update_cic_parameters() if (args.debug or args.isv or args.disk or args.gdb): sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb) @@ -1126,12 +1348,13 @@ if __name__ == '__main__': f.write(sc64.download_save()) print('done') - if (args.download_memory): - with open(args.download_memory, 'wb') as f: + if (args.download_memory != None): + (address, length, file) = args.download_memory + with open(file, 'wb') as f: print('Downloading memory... ', end='', flush=True) - f.write(sc64.download_memory()) + f.write(sc64.download_memory(address, length)) print('done') except ValueError as e: - print(f'\nValue error: {e}') + print(f'\n\x1b[31mValue error: {e}\x1b[0m\n') except ConnectionException as e: - print(f'\nSC64 error: {e}') + print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n')